[gtk/wip/otte/listview: 998/1040] listbase: Take over anchor handling
- From: Benjamin Otte <otte src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gtk/wip/otte/listview: 998/1040] listbase: Take over anchor handling
- Date: Sun, 2 Feb 2020 23:46:34 +0000 (UTC)
commit a1540da7d3a22caaf0f5e115163fec465adf0a17
Author: Benjamin Otte <otte redhat com>
Date: Sat Oct 26 08:49:27 2019 +0200
listbase: Take over anchor handling
With that, pretty much all code but allocating the widgets is gone from
the gridview and listview.
gtk/gtkgridview.c | 450 +++-----------------------------
gtk/gtklistbase.c | 651 ++++++++++++++++++++++++++++++++++-------------
gtk/gtklistbaseprivate.h | 34 ++-
gtk/gtklistview.c | 359 ++------------------------
4 files changed, 544 insertions(+), 950 deletions(-)
---
diff --git a/gtk/gtkgridview.c b/gtk/gtkgridview.c
index cb3dea3a84..4184552d5e 100644
--- a/gtk/gtkgridview.c
+++ b/gtk/gtkgridview.c
@@ -57,7 +57,6 @@ struct _GtkGridView
{
GtkListBase parent_instance;
- GListModel *model;
GtkListItemManager *item_manager;
guint min_columns;
guint max_columns;
@@ -65,14 +64,6 @@ struct _GtkGridView
guint n_columns;
double column_width;
int unknown_row_height;
-
- GtkListItemTracker *anchor;
- double anchor_xalign;
- double anchor_yalign;
- guint anchor_xstart : 1;
- guint anchor_ystart : 1;
- /* the item that has input focus */
- GtkListItemTracker *focus;
};
struct _GtkGridViewClass
@@ -275,33 +266,6 @@ gtk_grid_view_get_cell_at_y (GtkGridView *self,
return cell;
}
-static void
-gtk_grid_view_set_anchor (GtkGridView *self,
- guint position,
- double xalign,
- gboolean xstart,
- double yalign,
- gboolean ystart)
-{
- gtk_list_item_tracker_set_position (self->item_manager,
- self->anchor,
- position,
- (ceil (GTK_GRID_VIEW_MAX_VISIBLE_ROWS * yalign) + 1) *
self->max_columns,
- (ceil (GTK_GRID_VIEW_MAX_VISIBLE_ROWS * (1 - yalign)) + 1) *
self->max_columns);
-
- if (self->anchor_xalign != xalign ||
- self->anchor_xstart != xstart ||
- self->anchor_yalign != yalign ||
- self->anchor_ystart != ystart)
- {
- self->anchor_xalign = xalign;
- self->anchor_xstart = xstart;
- self->anchor_yalign = yalign;
- self->anchor_ystart = ystart;
- gtk_widget_queue_allocate (GTK_WIDGET (self));
- }
-}
-
static gboolean
gtk_grid_view_get_allocation_along (GtkListBase *base,
guint pos,
@@ -417,7 +381,7 @@ gtk_grid_view_get_position_from_allocation (GtkListBase *base,
if (across >= self->column_width * self->n_columns)
return FALSE;
- n_items = self->model ? g_list_model_get_n_items (self->model) : 0;
+ n_items = gtk_list_base_get_n_items (base);
if (!gtk_grid_view_get_cell_at_y (self,
along,
&pos,
@@ -463,7 +427,7 @@ gtk_grid_view_move_focus_along (GtkListBase *base,
}
else
{
- guint n_items = self->model ? g_list_model_get_n_items (self->model) : 0;
+ guint n_items = gtk_list_base_get_n_items (base);
if (n_items / self->n_columns > pos / self->n_columns)
pos += MIN (n_items - pos - 1, steps);
}
@@ -476,202 +440,17 @@ gtk_grid_view_move_focus_across (GtkListBase *base,
guint pos,
int steps)
{
- GtkGridView *self = GTK_GRID_VIEW (base);
-
if (steps < 0)
return pos - MIN (pos, -steps);
else
{
- guint n_items = self->model ? g_list_model_get_n_items (self->model) : 0;
+ guint n_items = gtk_list_base_get_n_items (base);
pos += MIN (n_items - pos - 1, steps);
}
return pos;
}
-static void
-gtk_grid_view_adjustment_value_changed (GtkListBase *base,
- GtkOrientation orientation)
-{
- GtkGridView *self = GTK_GRID_VIEW (base);
- int page_size, total_size, value, from_start;
- guint pos, anchor_pos, n_items;
- int offset, height, top, bottom;
- double xalign, yalign;
- gboolean xstart, ystart;
-
- gtk_list_base_get_adjustment_values (base, orientation, &value, &total_size, &page_size);
- anchor_pos = gtk_list_item_tracker_get_position (self->item_manager, self->anchor);
- n_items = g_list_model_get_n_items (self->model);
-
- if (orientation == gtk_list_base_get_orientation (GTK_LIST_BASE (self)))
- {
- /* Compute how far down we've scrolled. That's the height
- * we want to align to. */
- yalign = (double) value / (total_size - page_size);
- from_start = round (yalign * page_size);
-
- /* We want the cell that far down the page */
- if (gtk_grid_view_get_cell_at_y (self,
- value + from_start,
- &pos,
- &offset,
- &height))
- {
- /* offset from value - which is where we wanna scroll to */
- top = from_start - offset;
- bottom = top + height;
-
- /* find an anchor that is in the visible area */
- if (top > 0 && bottom < page_size)
- ystart = from_start - top <= bottom - from_start;
- else if (top > 0)
- ystart = TRUE;
- else if (bottom < page_size)
- ystart = FALSE;
- else
- {
- /* This is the case where the cell occupies the whole visible area.
- * It's also the only case where align will not end up in [0..1] */
- ystart = from_start - top <= bottom - from_start;
- }
-
- /* Now compute the align so that when anchoring to the looked
- * up cell, the position is pixel-exact.
- */
- yalign = (double) (ystart ? top : bottom) / page_size;
- }
- else
- {
- /* Happens if we scroll down to the end - we will query
- * exactly the pixel behind the last one we can get a cell for.
- * So take the last row. */
- pos = n_items - 1;
- pos = pos - pos % self->n_columns;
- yalign = 1.0;
- ystart = FALSE;
- }
-
- /* And finally, keep the column anchor intact. */
- anchor_pos %= self->n_columns;
- pos += anchor_pos;
- xstart = self->anchor_xstart;
- xalign = self->anchor_xalign;
- }
- else
- {
- xalign = (double) value / (total_size - page_size);
- from_start = round (xalign * page_size);
- pos = floor ((value + from_start) / self->column_width);
- if (pos >= self->n_columns)
- {
- /* scrolling to the end sets pos to exactly self->n_columns */
- pos = self->n_columns - 1;
- xstart = FALSE;
- xalign = 1.0;
- }
- else
- {
- top = ceil (self->column_width * pos) - value;
- bottom = ceil (self->column_width * (pos + 1)) - value;
-
- /* offset from value - which is where we wanna scroll to */
-
- /* find an anchor that is in the visible area */
- if (top > 0 && bottom < page_size)
- xstart = from_start - top <= bottom - from_start;
- else if (top > 0)
- xstart = TRUE;
- else if (bottom < page_size)
- xstart = FALSE;
- else
- xstart = from_start - top <= bottom - from_start;
-
- xalign = (double) (xstart ? top : bottom) / page_size;
- }
-
- /* And finally, keep the row anchor intact. */
- pos += (anchor_pos - anchor_pos % self->n_columns);
- yalign = self->anchor_yalign;
- ystart = self->anchor_ystart;
- }
-
- if (pos >= n_items)
- {
- /* Ugh, we're in the last row and don't have enough items
- * to fill the row.
- * Do it the hard way then... */
- gtk_list_base_get_adjustment_values (base,
- gtk_list_base_get_opposite_orientation (base),
- &value, &total_size, &page_size);
-
- pos = n_items - 1;
- xstart = FALSE;
- xalign = (ceil (self->column_width * (pos % self->n_columns + 1)) - value) / page_size;
- }
-
- gtk_grid_view_set_anchor (self, pos, xalign, xstart, yalign, ystart);
-
- gtk_widget_queue_allocate (GTK_WIDGET (self));
-}
-
-static int
-gtk_grid_view_update_adjustment (GtkGridView *self,
- GtkOrientation orientation)
-{
- int value, page_size, cell_size, total_size;
- guint anchor_pos;
-
- anchor_pos = gtk_list_item_tracker_get_position (self->item_manager, self->anchor);
- if (anchor_pos == GTK_INVALID_LIST_POSITION)
- return gtk_list_base_set_adjustment_values (GTK_LIST_BASE (self), orientation, 0, 0, 0);
-
- page_size = gtk_widget_get_size (GTK_WIDGET (self), orientation);
-
- if (gtk_list_base_get_orientation (GTK_LIST_BASE (self)) == orientation)
- {
- Cell *cell;
- CellAugment *aug;
-
- cell = gtk_list_item_manager_get_root (self->item_manager);
- g_assert (cell);
- aug = gtk_list_item_manager_get_item_augment (self->item_manager, cell);
- if (!gtk_list_base_get_allocation_along (GTK_LIST_BASE (self),
- anchor_pos,
- &value,
- &cell_size))
- {
- g_assert_not_reached ();
- }
- if (!self->anchor_ystart)
- value += cell_size;
-
- value = gtk_list_base_set_adjustment_values (GTK_LIST_BASE (self),
- orientation,
- value - self->anchor_yalign * page_size,
- aug->size,
- page_size);
- }
- else
- {
- guint i = anchor_pos % self->n_columns;
-
- if (self->anchor_xstart)
- value = ceil (self->column_width * i);
- else
- value = ceil (self->column_width * (i + 1));
- total_size = round (self->n_columns * self->column_width);
-
- value = gtk_list_base_set_adjustment_values (GTK_LIST_BASE (self),
- orientation,
- value - self->anchor_xalign * page_size,
- total_size,
- page_size);
- }
-
- return value;
-}
-
static int
compare_ints (gconstpointer first,
gconstpointer second)
@@ -904,6 +683,19 @@ gtk_grid_view_size_allocate_child (GtkGridView *self,
gtk_widget_size_allocate (child, &child_allocation, -1);
}
+static int
+gtk_grid_view_compute_total_height (GtkGridView *self)
+{
+ Cell *cell;
+ CellAugment *aug;
+
+ cell = gtk_list_item_manager_get_root (self->item_manager);
+ if (cell == NULL)
+ return 0;
+ aug = gtk_list_item_manager_get_item_augment (self->item_manager, cell);
+ return aug->size;
+}
+
static void
gtk_grid_view_size_allocate (GtkWidget *widget,
int width,
@@ -1017,8 +809,12 @@ gtk_grid_view_size_allocate (GtkWidget *widget,
cell_set_size (start, start->size + self->unknown_row_height);
/* step 4: update the adjustments */
- x = - gtk_grid_view_update_adjustment (self, opposite_orientation);
- y = - gtk_grid_view_update_adjustment (self, orientation);
+ gtk_list_base_update_adjustments (GTK_LIST_BASE (self),
+ self->column_width * self->n_columns,
+ gtk_grid_view_compute_total_height (self),
+ gtk_widget_get_size (widget, opposite_orientation),
+ gtk_widget_get_size (widget, orientation),
+ &x, &y);
/* step 5: run the size_allocate loop */
x = -x;
@@ -1082,18 +878,6 @@ gtk_grid_view_dispose (GObject *object)
{
GtkGridView *self = GTK_GRID_VIEW (object);
- g_clear_object (&self->model);
-
- if (self->anchor)
- {
- gtk_list_item_tracker_free (self->item_manager, self->anchor);
- self->anchor = NULL;
- }
- if (self->focus)
- {
- gtk_list_item_tracker_free (self->item_manager, self->focus);
- self->focus = NULL;
- }
self->item_manager = NULL;
G_OBJECT_CLASS (gtk_grid_view_parent_class)->dispose (object);
@@ -1122,7 +906,7 @@ gtk_grid_view_get_property (GObject *object,
break;
case PROP_MODEL:
- g_value_set_object (value, self->model);
+ g_value_set_object (value, gtk_list_base_get_model (GTK_LIST_BASE (self)));
break;
default:
@@ -1163,134 +947,6 @@ gtk_grid_view_set_property (GObject *object,
}
}
-static void
-gtk_grid_view_compute_scroll_align (GtkGridView *self,
- GtkOrientation orientation,
- int cell_start,
- int cell_end,
- double current_align,
- gboolean current_start,
- double *new_align,
- gboolean *new_start)
-{
- int visible_start, visible_size, visible_end;
- int cell_size;
-
- gtk_list_base_get_adjustment_values (GTK_LIST_BASE (self),
- orientation,
- &visible_start, NULL, &visible_size);
- visible_end = visible_start + visible_size;
- cell_size = cell_end - cell_start;
-
- if (cell_size <= visible_size)
- {
- if (cell_start < visible_start)
- {
- *new_align = 0.0;
- *new_start = TRUE;
- }
- else if (cell_end > visible_end)
- {
- *new_align = 1.0;
- *new_start = FALSE;
- }
- else
- {
- /* XXX: start or end here? */
- *new_start = TRUE;
- *new_align = (double) (cell_start - visible_start) / visible_size;
- }
- }
- else
- {
- /* This is the unlikely case of the cell being higher than the visible area */
- if (cell_start > visible_start)
- {
- *new_align = 0.0;
- *new_start = TRUE;
- }
- else if (cell_end < visible_end)
- {
- *new_align = 1.0;
- *new_start = FALSE;
- }
- else
- {
- /* the cell already covers the whole screen */
- *new_align = current_align;
- *new_start = current_start;
- }
- }
-}
-
-static void
-gtk_grid_view_update_focus_tracker (GtkGridView *self)
-{
- GtkWidget *focus_child;
- guint pos;
-
- focus_child = gtk_widget_get_focus_child (GTK_WIDGET (self));
- if (!GTK_IS_LIST_ITEM (focus_child))
- return;
-
- pos = gtk_list_item_get_position (GTK_LIST_ITEM (focus_child));
- if (pos != gtk_list_item_tracker_get_position (self->item_manager, self->focus))
- {
- gtk_list_item_tracker_set_position (self->item_manager,
- self->focus,
- pos,
- self->max_columns,
- self->max_columns);
- }
-}
-
-static void
-gtk_grid_view_scroll_to_item (GtkWidget *widget,
- const char *action_name,
- GVariant *parameter)
-{
- GtkGridView *self = GTK_GRID_VIEW (widget);
- int start, end;
- double xalign, yalign;
- gboolean xstart, ystart;
- guint pos;
-
- if (!g_variant_check_format_string (parameter, "u", FALSE))
- return;
-
- g_variant_get (parameter, "u", &pos);
-
- /* figure out primary orientation and if position is valid */
- if (!gtk_list_base_get_allocation_along (GTK_LIST_BASE (self), pos, &start, &end))
- return;
-
- end += start;
- gtk_grid_view_compute_scroll_align (self,
- gtk_list_base_get_orientation (GTK_LIST_BASE (self)),
- start, end,
- self->anchor_yalign, self->anchor_ystart,
- &yalign, &ystart);
-
- /* now do the same thing with the other orientation */
- start = floor (self->column_width * (pos % self->n_columns));
- end = floor (self->column_width * ((pos % self->n_columns) + 1));
- gtk_grid_view_compute_scroll_align (self,
- gtk_list_base_get_opposite_orientation (GTK_LIST_BASE (self)),
- start, end,
- self->anchor_xalign, self->anchor_xstart,
- &xalign, &xstart);
-
- gtk_grid_view_set_anchor (self, pos, xalign, xstart, yalign, ystart);
-
- /* HACK HACK HACK
- *
- * GTK has no way to track the focused child. But we now that when a listitem
- * gets focus, it calls this action. So we update our focus tracker from here
- * because it's the closest we can get to accurate tracking.
- */
- gtk_grid_view_update_focus_tracker (self);
-}
-
static void
gtk_grid_view_activate_item (GtkWidget *widget,
const char *action_name,
@@ -1303,7 +959,7 @@ gtk_grid_view_activate_item (GtkWidget *widget,
return;
g_variant_get (parameter, "u", &pos);
- if (self->model == NULL || pos >= g_list_model_get_n_items (self->model))
+ if (pos >= gtk_list_base_get_n_items (GTK_LIST_BASE (self)))
return;
g_signal_emit (widget, signals[ACTIVATE], 0, pos);
@@ -1320,7 +976,6 @@ gtk_grid_view_class_init (GtkGridViewClass *klass)
list_base_class->list_item_size = sizeof (Cell);
list_base_class->list_item_augment_size = sizeof (CellAugment);
list_base_class->list_item_augment_func = cell_augment;
- list_base_class->adjustment_value_changed = gtk_grid_view_adjustment_value_changed;
list_base_class->get_allocation_along = gtk_grid_view_get_allocation_along;
list_base_class->get_allocation_across = gtk_grid_view_get_allocation_across;
list_base_class->get_position_from_allocation = gtk_grid_view_get_position_from_allocation;
@@ -1424,18 +1079,6 @@ gtk_grid_view_class_init (GtkGridViewClass *klass)
"u",
gtk_grid_view_activate_item);
- /**
- * GtkGridView|list.scroll-to-item:
- * @position: position of item to scroll to
- *
- * Scrolls to the item given in @position with the minimum amount
- * of scrolling required. If the item is already visible, nothing happens.
- */
- gtk_widget_class_install_action (widget_class,
- "list.scroll-to-item",
- "u",
- gtk_grid_view_scroll_to_item);
-
gtk_widget_class_set_css_name (widget_class, I_("flowbox"));
}
@@ -1443,13 +1086,13 @@ static void
gtk_grid_view_init (GtkGridView *self)
{
self->item_manager = gtk_list_base_get_manager (GTK_LIST_BASE (self));
- self->anchor = gtk_list_item_tracker_new (self->item_manager);
- self->anchor_xstart = TRUE;
- self->anchor_ystart = TRUE;
- self->focus = gtk_list_item_tracker_new (self->item_manager);
self->min_columns = 1;
self->max_columns = DEFAULT_MAX_COLUMNS;
+
+ gtk_list_base_set_anchor_max_widgets (GTK_LIST_BASE (self),
+ self->max_columns * GTK_GRID_VIEW_MAX_VISIBLE_ROWS,
+ self->max_columns);
}
/**
@@ -1517,7 +1160,7 @@ gtk_grid_view_get_model (GtkGridView *self)
{
g_return_val_if_fail (GTK_IS_GRID_VIEW (self), NULL);
- return self->model;
+ return gtk_list_base_get_model (GTK_LIST_BASE (self));
}
/**
@@ -1534,32 +1177,9 @@ gtk_grid_view_set_model (GtkGridView *self,
g_return_if_fail (GTK_IS_GRID_VIEW (self));
g_return_if_fail (model == NULL || G_IS_LIST_MODEL (model));
- if (self->model == model)
+ if (!gtk_list_base_set_model (GTK_LIST_BASE (self), model))
return;
- g_clear_object (&self->model);
-
- if (model)
- {
- GtkSelectionModel *selection_model;
-
- self->model = g_object_ref (model);
-
- if (GTK_IS_SELECTION_MODEL (model))
- selection_model = GTK_SELECTION_MODEL (g_object_ref (model));
- else
- selection_model = GTK_SELECTION_MODEL (gtk_single_selection_new (model));
-
- gtk_list_item_manager_set_model (self->item_manager, selection_model);
- gtk_grid_view_set_anchor (self, 0, 0.0, TRUE, 0.0, TRUE);
-
- g_object_unref (selection_model);
- }
- else
- {
- gtk_list_item_manager_set_model (self->item_manager, NULL);
- }
-
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_MODEL]);
}
@@ -1639,13 +1259,9 @@ gtk_grid_view_set_max_columns (GtkGridView *self,
self->max_columns = max_columns;
- gtk_grid_view_set_anchor (self,
- gtk_list_item_tracker_get_position (self->item_manager, self->anchor),
- self->anchor_xalign,
- self->anchor_xstart,
- self->anchor_yalign,
- self->anchor_ystart);
- gtk_grid_view_update_focus_tracker (self);
+ gtk_list_base_set_anchor_max_widgets (GTK_LIST_BASE (self),
+ self->max_columns * GTK_GRID_VIEW_MAX_VISIBLE_ROWS,
+ self->max_columns);
gtk_widget_queue_resize (GTK_WIDGET (self));
diff --git a/gtk/gtklistbase.c b/gtk/gtklistbase.c
index 1580ef3c08..3f270bc223 100644
--- a/gtk/gtklistbase.c
+++ b/gtk/gtklistbase.c
@@ -26,6 +26,7 @@
#include "gtkintl.h"
#include "gtkorientableprivate.h"
#include "gtkscrollable.h"
+#include "gtksingleselection.h"
#include "gtktypebuiltins.h"
typedef struct _GtkListBasePrivate GtkListBasePrivate;
@@ -33,12 +34,22 @@ typedef struct _GtkListBasePrivate GtkListBasePrivate;
struct _GtkListBasePrivate
{
GtkListItemManager *item_manager;
+ GListModel *model;
GtkOrientation orientation;
GtkAdjustment *adjustment[2];
GtkScrollablePolicy scroll_policy[2];
+ GtkListItemTracker *anchor;
+ double anchor_align_along;
+ double anchor_align_across;
+ GtkPackType anchor_side_along;
+ GtkPackType anchor_side_across;
+ guint center_widgets;
+ guint above_below_widgets;
/* the last item that was selected - basically the location to extend selections from */
GtkListItemTracker *selected;
+ /* the item that has input focus */
+ GtkListItemTracker *focus;
};
enum
@@ -65,18 +76,144 @@ G_GNUC_UNUSED static void gtk_list_base_init (GtkListBase *self) { }
static GParamSpec *properties[N_PROPS] = { NULL, };
+/*
+ * gtk_list_base_get_position_from_allocation:
+ * @self: a #GtkListBase
+ * @across: position in pixels in the direction cross to the list
+ * @along: position in pixels in the direction of the list
+ * @pos: (out caller-allocates): set to the looked up position
+ * @area: (out caller-allocates) (allow-none): set to the area occupied
+ * by the returned position.
+ *
+ * Given a coordinate in list coordinates, determine the position of the
+ * item that occupies that position.
+ *
+ * It is possible for @area to not include the point given by (across, along).
+ * This will happen for example in the last row of a gridview, where the
+ * last item will be returned for the whole width, even if there are empty
+ * cells.
+ *
+ * Returns: %TRUE on success or %FALSE if no position occupies the given offset.
+ **/
+static guint
+gtk_list_base_get_position_from_allocation (GtkListBase *self,
+ int across,
+ int along,
+ guint *pos,
+ cairo_rectangle_int_t *area)
+{
+ return GTK_LIST_BASE_GET_CLASS (self)->get_position_from_allocation (self, across, along, pos, area);
+}
+
+static gboolean
+gtk_list_base_adjustment_is_flipped (GtkListBase *self,
+ GtkOrientation orientation)
+{
+ if (orientation == GTK_ORIENTATION_VERTICAL)
+ return FALSE;
+
+ return gtk_widget_get_direction (GTK_WIDGET (self)) == GTK_TEXT_DIR_RTL;
+}
+
+static void
+gtk_list_base_get_adjustment_values (GtkListBase *self,
+ GtkOrientation orientation,
+ int *value,
+ int *size,
+ int *page_size)
+{
+ GtkListBasePrivate *priv = gtk_list_base_get_instance_private (self);
+ int val, upper, ps;
+
+ val = gtk_adjustment_get_value (priv->adjustment[orientation]);
+ upper = gtk_adjustment_get_upper (priv->adjustment[orientation]);
+ ps = gtk_adjustment_get_page_size (priv->adjustment[orientation]);
+ if (gtk_list_base_adjustment_is_flipped (self, orientation))
+ val = upper - ps - val;
+
+ if (value)
+ *value = val;
+ if (size)
+ *size = upper;
+ if (page_size)
+ *page_size = ps;
+}
+
static void
gtk_list_base_adjustment_value_changed_cb (GtkAdjustment *adjustment,
GtkListBase *self)
{
GtkListBasePrivate *priv = gtk_list_base_get_instance_private (self);
- GtkOrientation orientation;
+ cairo_rectangle_int_t area, cell_area;
+ int along, across, total_size;
+ double align_across, align_along;
+ GtkPackType side_across, side_along;
+ guint pos;
+
+ gtk_list_base_get_adjustment_values (self, OPPOSITE_ORIENTATION (priv->orientation), &area.x, &total_size,
&area.width);
+ if (total_size == area.width)
+ align_across = 0.5;
+ else if (adjustment != priv->adjustment[priv->orientation])
+ align_across = CLAMP (priv->anchor_align_across, 0, 1);
+ else
+ align_across = (double) area.x / (total_size - area.width);
+ across = area.x + round (align_across * area.width);
+ across = CLAMP (across, 0, total_size - 1);
+
+ gtk_list_base_get_adjustment_values (self, priv->orientation, &area.y, &total_size, &area.height);
+ if (total_size == area.height)
+ align_along = 0.5;
+ else if (adjustment != priv->adjustment[OPPOSITE_ORIENTATION(priv->orientation)])
+ align_along = CLAMP (priv->anchor_align_along, 0, 1);
+ else
+ align_along = (double) area.y / (total_size - area.height);
+ along = area.y + round (align_along * area.height);
+ along = CLAMP (along, 0, total_size - 1);
+
+ if (!gtk_list_base_get_position_from_allocation (self,
+ across, along,
+ &pos,
+ &cell_area))
+ {
+ g_warning ("%s failed to scroll to given position. Ignoring...", G_OBJECT_TYPE_NAME (self));
+ return;
+ }
+
+ /* find an anchor that is in the visible area */
+ if (cell_area.x < area.x && cell_area.x + cell_area.width <= area.x + area.width)
+ side_across = GTK_PACK_END;
+ else if (cell_area.x >= area.x && cell_area.x + cell_area.width > area.x + area.width)
+ side_across = GTK_PACK_START;
+ else if (cell_area.x + cell_area.width / 2 > across)
+ side_across = GTK_PACK_END;
+ else
+ side_across = GTK_PACK_START;
+
+ if (cell_area.y < area.y && cell_area.y + cell_area.height <= area.y + area.height)
+ side_along = GTK_PACK_END;
+ else if (cell_area.y >= area.y && cell_area.y + cell_area.height > area.y + area.height)
+ side_along = GTK_PACK_START;
+ else if (cell_area.y + cell_area.height / 2 > along)
+ side_along = GTK_PACK_END;
+ else
+ side_along = GTK_PACK_START;
- orientation = adjustment == priv->adjustment[GTK_ORIENTATION_HORIZONTAL]
- ? GTK_ORIENTATION_HORIZONTAL
- : GTK_ORIENTATION_VERTICAL;
+ /* Compute the align based on side to keep the values identical */
+ if (side_across == GTK_PACK_START)
+ align_across = (double) (cell_area.x - area.x) / area.width;
+ else
+ align_across = (double) (cell_area.x + cell_area.height - area.x) / area.width;
+ if (side_along == GTK_PACK_START)
+ align_along = (double) (cell_area.y - area.y) / area.height;
+ else
+ align_along = (double) (cell_area.y + cell_area.height - area.y) / area.height;
- GTK_LIST_BASE_GET_CLASS (self)->adjustment_value_changed (self, orientation);
+ gtk_list_base_set_anchor (self,
+ pos,
+ align_across, side_across,
+ align_along, side_along);
+
+ gtk_widget_queue_allocate (GTK_WIDGET (self));
}
static void
@@ -170,7 +307,7 @@ gtk_list_base_move_focus (GtkListBase *self,
*
* Returns: %TRUE if the item exists and has an allocation, %FALSE otherwise
**/
-gboolean
+static gboolean
gtk_list_base_get_allocation_along (GtkListBase *self,
guint pos,
int *offset,
@@ -202,35 +339,6 @@ gtk_list_base_get_allocation_across (GtkListBase *self,
return GTK_LIST_BASE_GET_CLASS (self)->get_allocation_across (self, pos, offset, size);
}
-/*
- * gtk_list_base_get_position_from_allocation:
- * @self: a #GtkListBase
- * @across: position in pixels in the direction cross to the list
- * @along: position in pixels in the direction of the list
- * @pos: (out caller-allocates): set to the looked up position
- * @area: (out caller-allocates) (allow-none): set to the area occupied
- * by the returned position.
- *
- * Given a coordinate in list coordinates, determine the position of the
- * item that occupies that position.
- *
- * It is possible for @area to not include the point given by (across, along).
- * This will happen for example in the last row of a gridview, where the
- * last item will be returned for the whole width, even if there are empty
- * cells.
- *
- * Returns: %TRUE on success or %FALSE if no position occupies the given offset.
- **/
-static guint
-gtk_list_base_get_position_from_allocation (GtkListBase *self,
- int across,
- int along,
- guint *pos,
- cairo_rectangle_int_t *area)
-{
- return GTK_LIST_BASE_GET_CLASS (self)->get_position_from_allocation (self, across, along, pos, area);
-}
-
/*
* gtk_list_base_select_item:
* @self: a #GtkListBase
@@ -323,33 +431,23 @@ gtk_list_base_select_item (GtkListBase *self,
0, 0);
}
-static guint
+guint
gtk_list_base_get_n_items (GtkListBase *self)
{
GtkListBasePrivate *priv = gtk_list_base_get_instance_private (self);
- GtkSelectionModel *model;
- model = gtk_list_item_manager_get_model (priv->item_manager);
- if (model == NULL)
+ if (priv->model == NULL)
return 0;
- return g_list_model_get_n_items (G_LIST_MODEL (model));
+ return g_list_model_get_n_items (priv->model);
}
guint
gtk_list_base_get_focus_position (GtkListBase *self)
{
-#if 0
GtkListBasePrivate *priv = gtk_list_base_get_instance_private (self);
return gtk_list_item_tracker_get_position (priv->item_manager, priv->focus);
-#else
- GtkWidget *focus_child = gtk_widget_get_focus_child (GTK_WIDGET (self));
- if (focus_child)
- return gtk_list_item_get_position (GTK_LIST_ITEM (focus_child));
- else
- return GTK_INVALID_LIST_POSITION;
-#endif
}
static gboolean
@@ -434,13 +532,25 @@ gtk_list_base_dispose (GObject *object)
gtk_list_base_clear_adjustment (self, GTK_ORIENTATION_HORIZONTAL);
gtk_list_base_clear_adjustment (self, GTK_ORIENTATION_VERTICAL);
+ if (priv->anchor)
+ {
+ gtk_list_item_tracker_free (priv->item_manager, priv->anchor);
+ priv->anchor = NULL;
+ }
if (priv->selected)
{
gtk_list_item_tracker_free (priv->item_manager, priv->selected);
priv->selected = NULL;
}
+ if (priv->focus)
+ {
+ gtk_list_item_tracker_free (priv->item_manager, priv->focus);
+ priv->focus = NULL;
+ }
g_clear_object (&priv->item_manager);
+ g_clear_object (&priv->model);
+
G_OBJECT_CLASS (gtk_list_base_parent_class)->dispose (object);
}
@@ -573,77 +683,14 @@ gtk_list_base_set_property (GObject *object,
}
static void
-gtk_list_base_select_item_action (GtkWidget *widget,
- const char *action_name,
- GVariant *parameter)
-{
- GtkListBase *self = GTK_LIST_BASE (widget);
- guint pos;
- gboolean modify, extend;
-
- g_variant_get (parameter, "(ubb)", &pos, &modify, &extend);
-
- gtk_list_base_select_item (self, pos, modify, extend);
-}
-
-static void
-gtk_list_base_select_all (GtkWidget *widget,
- const char *action_name,
- GVariant *parameter)
-{
- GtkListBase *self = GTK_LIST_BASE (widget);
- GtkListBasePrivate *priv = gtk_list_base_get_instance_private (self);
- GtkSelectionModel *selection_model;
-
- selection_model = gtk_list_item_manager_get_model (priv->item_manager);
- if (selection_model == NULL)
- return;
-
- gtk_selection_model_select_all (selection_model);
-}
-
-static void
-gtk_list_base_unselect_all (GtkWidget *widget,
- const char *action_name,
- GVariant *parameter)
-{
- GtkListBase *self = GTK_LIST_BASE (widget);
- GtkListBasePrivate *priv = gtk_list_base_get_instance_private (self);
- GtkSelectionModel *selection_model;
-
- selection_model = gtk_list_item_manager_get_model (priv->item_manager);
- if (selection_model == NULL)
- return;
-
- gtk_selection_model_unselect_all (selection_model);
-}
-
-static void
-gtk_list_base_move_cursor_to_start (GtkWidget *widget,
- GVariant *args,
- gpointer unused)
-{
- GtkListBase *self = GTK_LIST_BASE (widget);
- gboolean select, modify, extend;
-
- if (gtk_list_base_get_n_items (self) == 0)
- return;
-
- g_variant_get (args, "(bbb)", &select, &modify, &extend);
-
- gtk_list_base_grab_focus_on_item (GTK_LIST_BASE (self), 0, select, modify, extend);
-}
-
-#if 0
-static void
-gtk_grid_view_compute_scroll_align (GtkGridView *self,
+gtk_list_base_compute_scroll_align (GtkListBase *self,
GtkOrientation orientation,
int cell_start,
int cell_end,
double current_align,
- gboolean current_start,
+ GtkPackType current_side,
double *new_align,
- gboolean *new_start)
+ GtkPackType *new_side)
{
int visible_start, visible_size, visible_end;
int cell_size;
@@ -659,17 +706,17 @@ gtk_grid_view_compute_scroll_align (GtkGridView *self,
if (cell_start < visible_start)
{
*new_align = 0.0;
- *new_start = TRUE;
+ *new_side = GTK_PACK_START;
}
else if (cell_end > visible_end)
{
*new_align = 1.0;
- *new_start = FALSE;
+ *new_side = GTK_PACK_END;
}
else
{
/* XXX: start or end here? */
- *new_start = TRUE;
+ *new_side = GTK_PACK_START;
*new_align = (double) (cell_start - visible_start) / visible_size;
}
}
@@ -679,25 +726,26 @@ gtk_grid_view_compute_scroll_align (GtkGridView *self,
if (cell_start > visible_start)
{
*new_align = 0.0;
- *new_start = TRUE;
+ *new_side = GTK_PACK_START;
}
else if (cell_end < visible_end)
{
*new_align = 1.0;
- *new_start = FALSE;
+ *new_side = GTK_PACK_END;
}
else
{
/* the cell already covers the whole screen */
*new_align = current_align;
- *new_start = current_start;
+ *new_side = current_side;
}
}
}
static void
-gtk_grid_view_update_focus_tracker (GtkGridView *self)
+gtk_list_base_update_focus_tracker (GtkListBase *self)
{
+ GtkListBasePrivate *priv = gtk_list_base_get_instance_private (self);
GtkWidget *focus_child;
guint pos;
@@ -706,25 +754,26 @@ gtk_grid_view_update_focus_tracker (GtkGridView *self)
return;
pos = gtk_list_item_get_position (GTK_LIST_ITEM (focus_child));
- if (pos != gtk_list_item_tracker_get_position (self->item_manager, self->focus))
+ if (pos != gtk_list_item_tracker_get_position (priv->item_manager, priv->focus))
{
- gtk_list_item_tracker_set_position (self->item_manager,
- self->focus,
+ gtk_list_item_tracker_set_position (priv->item_manager,
+ priv->focus,
pos,
- self->max_columns,
- self->max_columns);
+ 0,
+ 0);
}
}
static void
-gtk_grid_view_scroll_to_item (GtkWidget *widget,
+gtk_list_base_scroll_to_item (GtkWidget *widget,
const char *action_name,
GVariant *parameter)
{
- GtkGridView *self = GTK_GRID_VIEW (widget);
+ GtkListBase *self = GTK_LIST_BASE (widget);
+ GtkListBasePrivate *priv = gtk_list_base_get_instance_private (self);
int start, end;
- double xalign, yalign;
- gboolean xstart, ystart;
+ double align_along, align_across;
+ GtkPackType side_along, side_across;
guint pos;
if (!g_variant_check_format_string (parameter, "u", FALSE))
@@ -733,26 +782,31 @@ gtk_grid_view_scroll_to_item (GtkWidget *widget,
g_variant_get (parameter, "u", &pos);
/* figure out primary orientation and if position is valid */
- if (!gtk_grid_view_get_size_at_position (self, pos, &start, &end))
+ if (!gtk_list_base_get_allocation_along (GTK_LIST_BASE (self), pos, &start, &end))
return;
end += start;
- gtk_grid_view_compute_scroll_align (self,
+ gtk_list_base_compute_scroll_align (self,
gtk_list_base_get_orientation (GTK_LIST_BASE (self)),
start, end,
- self->anchor_yalign, self->anchor_ystart,
- &yalign, &ystart);
+ priv->anchor_align_along, priv->anchor_side_along,
+ &align_along, &side_along);
/* now do the same thing with the other orientation */
- start = floor (self->column_width * (pos % self->n_columns));
- end = floor (self->column_width * ((pos % self->n_columns) + 1));
- gtk_grid_view_compute_scroll_align (self,
+ if (!gtk_list_base_get_allocation_across (GTK_LIST_BASE (self), pos, &start, &end))
+ return;
+
+ end += start;
+ gtk_list_base_compute_scroll_align (self,
gtk_list_base_get_opposite_orientation (GTK_LIST_BASE (self)),
start, end,
- self->anchor_xalign, self->anchor_xstart,
- &xalign, &xstart);
+ priv->anchor_align_across, priv->anchor_side_across,
+ &align_across, &side_across);
- gtk_grid_view_set_anchor (self, pos, xalign, xstart, yalign, ystart);
+ gtk_list_base_set_anchor (self,
+ pos,
+ align_across, side_across,
+ align_along, side_along);
/* HACK HACK HACK
*
@@ -760,9 +814,70 @@ gtk_grid_view_scroll_to_item (GtkWidget *widget,
* gets focus, it calls this action. So we update our focus tracker from here
* because it's the closest we can get to accurate tracking.
*/
- gtk_grid_view_update_focus_tracker (self);
+ gtk_list_base_update_focus_tracker (self);
+}
+
+static void
+gtk_list_base_select_item_action (GtkWidget *widget,
+ const char *action_name,
+ GVariant *parameter)
+{
+ GtkListBase *self = GTK_LIST_BASE (widget);
+ guint pos;
+ gboolean modify, extend;
+
+ g_variant_get (parameter, "(ubb)", &pos, &modify, &extend);
+
+ gtk_list_base_select_item (self, pos, modify, extend);
+}
+
+static void
+gtk_list_base_select_all (GtkWidget *widget,
+ const char *action_name,
+ GVariant *parameter)
+{
+ GtkListBase *self = GTK_LIST_BASE (widget);
+ GtkListBasePrivate *priv = gtk_list_base_get_instance_private (self);
+ GtkSelectionModel *selection_model;
+
+ selection_model = gtk_list_item_manager_get_model (priv->item_manager);
+ if (selection_model == NULL)
+ return;
+
+ gtk_selection_model_select_all (selection_model);
+}
+
+static void
+gtk_list_base_unselect_all (GtkWidget *widget,
+ const char *action_name,
+ GVariant *parameter)
+{
+ GtkListBase *self = GTK_LIST_BASE (widget);
+ GtkListBasePrivate *priv = gtk_list_base_get_instance_private (self);
+ GtkSelectionModel *selection_model;
+
+ selection_model = gtk_list_item_manager_get_model (priv->item_manager);
+ if (selection_model == NULL)
+ return;
+
+ gtk_selection_model_unselect_all (selection_model);
+}
+
+static void
+gtk_list_base_move_cursor_to_start (GtkWidget *widget,
+ GVariant *args,
+ gpointer unused)
+{
+ GtkListBase *self = GTK_LIST_BASE (widget);
+ gboolean select, modify, extend;
+
+ if (gtk_list_base_get_n_items (self) == 0)
+ return;
+
+ g_variant_get (args, "(bbb)", &select, &modify, &extend);
+
+ gtk_list_base_grab_focus_on_item (GTK_LIST_BASE (self), 0, select, modify, extend);
}
-#endif
static void
gtk_list_base_move_cursor_page_up (GtkWidget *widget,
@@ -988,6 +1103,18 @@ gtk_list_base_class_init (GtkListBaseClass *klass)
g_object_class_install_properties (gobject_class, N_PROPS, properties);
+ /**
+ * GtkListBase|list.scroll-to-item:
+ * @position: position of item to scroll to
+ *
+ * Moves the visible area to the item given in @position with the minimum amount
+ * of scrolling required. If the item is already visible, nothing happens.
+ */
+ gtk_widget_class_install_action (widget_class,
+ "list.scroll-to-item",
+ "u",
+ gtk_list_base_scroll_to_item);
+
/**
* GtkListBase|list.select-item:
* @position: position of item to select
@@ -1070,7 +1197,11 @@ gtk_list_base_init_real (GtkListBase *self,
g_class->list_item_size,
g_class->list_item_augment_size,
g_class->list_item_augment_func);
+ priv->anchor = gtk_list_item_tracker_new (priv->item_manager);
+ priv->anchor_side_along = GTK_PACK_START;
+ priv->anchor_side_across = GTK_PACK_START;
priv->selected = gtk_list_item_tracker_new (priv->item_manager);
+ priv->focus = gtk_list_item_tracker_new (priv->item_manager);
priv->adjustment[GTK_ORIENTATION_HORIZONTAL] = gtk_adjustment_new (0.0, 0.0, 0.0, 0.0, 0.0, 0.0);
priv->adjustment[GTK_ORIENTATION_VERTICAL] = gtk_adjustment_new (0.0, 0.0, 0.0, 0.0, 0.0, 0.0);
@@ -1080,41 +1211,7 @@ gtk_list_base_init_real (GtkListBase *self,
gtk_widget_set_overflow (GTK_WIDGET (self), GTK_OVERFLOW_HIDDEN);
}
-static gboolean
-gtk_list_base_adjustment_is_flipped (GtkListBase *self,
- GtkOrientation orientation)
-{
- if (orientation == GTK_ORIENTATION_VERTICAL)
- return FALSE;
-
- return gtk_widget_get_direction (GTK_WIDGET (self)) == GTK_TEXT_DIR_RTL;
-}
-
-void
-gtk_list_base_get_adjustment_values (GtkListBase *self,
- GtkOrientation orientation,
- int *value,
- int *size,
- int *page_size)
-{
- GtkListBasePrivate *priv = gtk_list_base_get_instance_private (self);
- int val, upper, ps;
-
- val = gtk_adjustment_get_value (priv->adjustment[orientation]);
- upper = gtk_adjustment_get_upper (priv->adjustment[orientation]);
- ps = gtk_adjustment_get_page_size (priv->adjustment[orientation]);
- if (gtk_list_base_adjustment_is_flipped (self, orientation))
- val = upper - ps - val;
-
- if (value)
- *value = val;
- if (size)
- *size = upper;
- if (page_size)
- *page_size = ps;
-}
-
-int
+static int
gtk_list_base_set_adjustment_values (GtkListBase *self,
GtkOrientation orientation,
int value,
@@ -1146,6 +1243,61 @@ gtk_list_base_set_adjustment_values (GtkListBase *self,
return value;
}
+void
+gtk_list_base_update_adjustments (GtkListBase *self,
+ int total_across,
+ int total_along,
+ int page_across,
+ int page_along,
+ int *across,
+ int *along)
+{
+ GtkListBasePrivate *priv = gtk_list_base_get_instance_private (self);
+ int value_along, value_across, size;
+ guint pos;
+
+ pos = gtk_list_item_tracker_get_position (priv->item_manager, priv->anchor);
+ if (pos == GTK_INVALID_LIST_POSITION)
+ {
+ value_across = 0;
+ value_along = 0;
+ }
+ else
+ {
+ if (gtk_list_base_get_allocation_across (self, pos, &value_across, &size))
+ {
+ if (priv->anchor_side_across == GTK_PACK_END)
+ value_across += size;
+ value_across -= priv->anchor_align_across * page_across;
+ }
+ else
+ {
+ value_along = 0;
+ }
+ if (gtk_list_base_get_allocation_along (self, pos, &value_along, &size))
+ {
+ if (priv->anchor_side_along == GTK_PACK_END)
+ value_along += size;
+ value_along -= priv->anchor_align_along * page_along;
+ }
+ else
+ {
+ value_along = 0;
+ }
+ }
+
+ *across = gtk_list_base_set_adjustment_values (self,
+ OPPOSITE_ORIENTATION (priv->orientation),
+ value_across,
+ total_across,
+ page_across);
+ *along = gtk_list_base_set_adjustment_values (self,
+ priv->orientation,
+ value_along,
+ total_along,
+ page_along);
+}
+
GtkScrollablePolicy
gtk_list_base_get_scroll_policy (GtkListBase *self,
GtkOrientation orientation)
@@ -1171,6 +1323,108 @@ gtk_list_base_get_manager (GtkListBase *self)
return priv->item_manager;
}
+/*
+ * gtk_list_base_set_anchor:
+ * @self: a #GtkListBase
+ * @anchor_pos: position of the item to anchor
+ * @anchor_align_across: how far in the across direction to anchor
+ * @anchor_side_across: if the anchor should side to start or end
+ * of item
+ * @anchor_align_along: how far in the along direction to anchor
+ * @anchor_side_along: if the anchor should side to start or end
+ * of item
+ *
+ * Sets the anchor.
+ * The anchor is the item that is always kept on screen.
+ *
+ * In each dimension, anchoring uses 2 variables: The side of the
+ * item that gets anchored - either start or end - and where in
+ * the widget's allocation it should get anchored - here 0.0 means
+ * the start of the widget and 1.0 is the end of the widget.
+ * It is allowed to use values outside of this range. In particular,
+ * this is necessary when the items are larger than the list's
+ * allocation.
+ *
+ * Using this information, the adjustment's value and in turn widget
+ * offsets will then be computed. If the anchor is too far off, it
+ * will be clamped so that there are always visible items on screen.
+ *
+ * Making anchoring this complicated ensures that one item - one
+ * corner of one item to be exact - always stays at the same place
+ * (usually this item is the focused item). So when the list undergoes
+ * heavy changes (like sorting, filtering, removals, additions), this
+ * item will stay in place while everything around it will shuffle
+ * around.
+ *
+ * The anchor will also ensure that enough widgets are created according
+ * to gtk_list_base_set_anchor_max_widgets().
+ **/
+void
+gtk_list_base_set_anchor (GtkListBase *self,
+ guint anchor_pos,
+ double anchor_align_across,
+ GtkPackType anchor_side_across,
+ double anchor_align_along,
+ GtkPackType anchor_side_along)
+{
+ GtkListBasePrivate *priv = gtk_list_base_get_instance_private (self);
+ guint items_before;
+
+ items_before = round (priv->center_widgets * CLAMP (anchor_align_along, 0, 1));
+ gtk_list_item_tracker_set_position (priv->item_manager,
+ priv->anchor,
+ anchor_pos,
+ items_before + priv->above_below_widgets,
+ priv->center_widgets - items_before + priv->above_below_widgets);
+
+ priv->anchor_align_across = anchor_align_across;
+ priv->anchor_side_across = anchor_side_across;
+ priv->anchor_align_along = anchor_align_along;
+ priv->anchor_side_along = anchor_side_along;
+
+ gtk_widget_queue_allocate (GTK_WIDGET (self));
+}
+
+/**
+ * gtk_list_base_set_anchor_max_widgets:
+ * @self: a #GtkListBase
+ * @center: the number of widgets in the middle
+ * @above_below: extra widgets above and below
+ *
+ * Sets how many widgets should be kept alive around the anchor.
+ * The number of these widgets determines how many items can be
+ * displayed and must be chosen to be large enough to cover the
+ * allocation but should be kept as small as possible for
+ * performance reasons.
+ *
+ * There will be @center widgets allocated around the anchor
+ * evenly distributed according to the anchor's alignment - if
+ * the anchor is at the start, all these widgets will be allocated
+ * behind it, if it's at the end, all the widgets will be allocated
+ * in front of it.
+ *
+ * Addditionally, there will be @above_below widgets allocated both
+ * before and after the sencter widgets, so the total number of
+ * widgets kept alive is 2 * above_below + center + 1.
+ **/
+void
+gtk_list_base_set_anchor_max_widgets (GtkListBase *self,
+ guint n_center,
+ guint n_above_below)
+{
+ GtkListBasePrivate *priv = gtk_list_base_get_instance_private (self);
+
+ priv->center_widgets = n_center;
+ priv->above_below_widgets = n_above_below;
+
+ gtk_list_base_set_anchor (self,
+ gtk_list_item_tracker_get_position (priv->item_manager, priv->anchor),
+ priv->anchor_align_across,
+ priv->anchor_side_across,
+ priv->anchor_align_along,
+ priv->anchor_side_along);
+}
+
/*
* gtk_list_base_grab_focus_on_item:
* @self: a #GtkListBase
@@ -1231,3 +1485,46 @@ gtk_list_base_grab_focus_on_item (GtkListBase *self,
return TRUE;
}
+GListModel *
+gtk_list_base_get_model (GtkListBase *self)
+{
+ GtkListBasePrivate *priv = gtk_list_base_get_instance_private (self);
+
+ return priv->model;
+}
+
+gboolean
+gtk_list_base_set_model (GtkListBase *self,
+ GListModel *model)
+{
+ GtkListBasePrivate *priv = gtk_list_base_get_instance_private (self);
+
+ if (priv->model == model)
+ return FALSE;
+
+ g_clear_object (&priv->model);
+
+ if (model)
+ {
+ GtkSelectionModel *selection_model;
+
+ priv->model = g_object_ref (model);
+
+ if (GTK_IS_SELECTION_MODEL (model))
+ selection_model = GTK_SELECTION_MODEL (g_object_ref (model));
+ else
+ selection_model = GTK_SELECTION_MODEL (gtk_single_selection_new (model));
+
+ gtk_list_item_manager_set_model (priv->item_manager, selection_model);
+ gtk_list_base_set_anchor (self, 0, 0.0, GTK_PACK_START, 0.0, GTK_PACK_START);
+
+ g_object_unref (selection_model);
+ }
+ else
+ {
+ gtk_list_item_manager_set_model (priv->item_manager, NULL);
+ }
+
+ return TRUE;
+}
+
diff --git a/gtk/gtklistbaseprivate.h b/gtk/gtklistbaseprivate.h
index 8f3054acaa..fd4e8a2ea7 100644
--- a/gtk/gtklistbaseprivate.h
+++ b/gtk/gtklistbaseprivate.h
@@ -68,21 +68,27 @@ guint gtk_list_base_get_focus_position (GtkListBase
GtkListItemManager * gtk_list_base_get_manager (GtkListBase *self);
GtkScrollablePolicy gtk_list_base_get_scroll_policy (GtkListBase *self,
GtkOrientation orientation);
-gboolean gtk_list_base_get_allocation_along (GtkListBase *base,
- guint pos,
- int *offset,
- int *size);
-void gtk_list_base_get_adjustment_values (GtkListBase *self,
- GtkOrientation orientation,
- int *value,
- int *size,
- int *page_size);
-int gtk_list_base_set_adjustment_values (GtkListBase *self,
- GtkOrientation orientation,
- int value,
- int size,
- int page_size);
+guint gtk_list_base_get_n_items (GtkListBase *self);
+GListModel * gtk_list_base_get_model (GtkListBase *self);
+gboolean gtk_list_base_set_model (GtkListBase *self,
+ GListModel *model);
+void gtk_list_base_update_adjustments (GtkListBase *self,
+ int total_across,
+ int total_along,
+ int page_across,
+ int page_along,
+ int *across,
+ int *along);
+void gtk_list_base_set_anchor (GtkListBase *self,
+ guint anchor_pos,
+ double anchor_align_across,
+ GtkPackType anchor_side_across,
+ double anchor_align_along,
+ GtkPackType anchor_side_along);
+void gtk_list_base_set_anchor_max_widgets (GtkListBase *self,
+ guint n_center,
+ guint n_above_below);
void gtk_list_base_select_item (GtkListBase *self,
guint pos,
gboolean modify,
diff --git a/gtk/gtklistview.c b/gtk/gtklistview.c
index 69e81afbe7..fa104d9961 100644
--- a/gtk/gtklistview.c
+++ b/gtk/gtklistview.c
@@ -27,8 +27,6 @@
#include "gtkmain.h"
#include "gtkprivate.h"
#include "gtkrbtreeprivate.h"
-#include "gtkselectionmodel.h"
-#include "gtksingleselection.h"
#include "gtkstylecontext.h"
#include "gtkwidgetprivate.h"
@@ -57,17 +55,10 @@ struct _GtkListView
{
GtkListBase parent_instance;
- GListModel *model;
GtkListItemManager *item_manager;
gboolean show_separators;
int list_width;
-
- GtkListItemTracker *anchor;
- double anchor_align;
- gboolean anchor_start;
- /* the item that has input focus */
- GtkListItemTracker *focus;
};
struct _GtkListViewClass
@@ -194,37 +185,6 @@ gtk_list_view_get_row_at_y (GtkListView *self,
return row;
}
-static int
-gtk_list_view_get_position_at_y (GtkListView *self,
- int y,
- int *offset,
- int *height)
-{
- ListRow *row;
- guint pos;
- int remaining;
-
- row = gtk_list_view_get_row_at_y (self, y, &remaining);
- if (row == NULL)
- {
- pos = GTK_INVALID_LIST_POSITION;
- if (offset)
- *offset = 0;
- if (height)
- *height = 0;
- return GTK_INVALID_LIST_POSITION;
- }
- pos = gtk_list_item_manager_get_item_position (self->item_manager, row);
- g_assert (remaining < row->height * row->parent.n_items);
- pos += remaining / row->height;
- if (offset)
- *offset = remaining % row->height;
- if (height)
- *height = row->height;
-
- return pos;
-}
-
static int
list_row_get_y (GtkListView *self,
ListRow *row)
@@ -277,90 +237,6 @@ gtk_list_view_get_list_height (GtkListView *self)
return aug->height;
}
-static void
-gtk_list_view_set_anchor (GtkListView *self,
- guint position,
- double align,
- gboolean start)
-{
- gtk_list_item_tracker_set_position (self->item_manager,
- self->anchor,
- position,
- GTK_LIST_VIEW_EXTRA_ITEMS + GTK_LIST_VIEW_MAX_LIST_ITEMS * align,
- GTK_LIST_VIEW_EXTRA_ITEMS + GTK_LIST_VIEW_MAX_LIST_ITEMS - 1 -
GTK_LIST_VIEW_MAX_LIST_ITEMS * align);
- if (self->anchor_align != align || self->anchor_start != start)
- {
- self->anchor_align = align;
- self->anchor_start = start;
- gtk_widget_queue_allocate (GTK_WIDGET (self));
- }
-}
-
-static void
-gtk_list_view_adjustment_value_changed (GtkListBase *base,
- GtkOrientation orientation)
-{
- GtkListView *self = GTK_LIST_VIEW (base);
-
- if (orientation == gtk_list_base_get_orientation (GTK_LIST_BASE (self)))
- {
- int page_size, total_size, value, from_start;
- int row_start, row_end;
- double align;
- gboolean top;
- guint pos;
-
- gtk_list_base_get_adjustment_values (base, orientation, &value, &total_size, &page_size);
-
- /* Compute how far down we've scrolled. That's the height
- * we want to align to. */
- align = (double) value / (total_size - page_size);
- from_start = round (align * page_size);
-
- pos = gtk_list_view_get_position_at_y (self,
- value + from_start,
- &row_start, &row_end);
- if (pos != GTK_INVALID_LIST_POSITION)
- {
- /* offset from value - which is where we wanna scroll to */
- row_start = from_start - row_start;
- row_end += row_start;
-
- /* find an anchor that is in the visible area */
- if (row_start > 0 && row_end < page_size)
- top = from_start - row_start <= row_end - from_start;
- else if (row_start > 0)
- top = TRUE;
- else if (row_end < page_size)
- top = FALSE;
- else
- {
- /* This is the case where the row occupies the whole visible area.
- * It's also the only case where align will not end up in [0..1] */
- top = from_start - row_start <= row_end - from_start;
- }
-
- /* Now compute the align so that when anchoring to the looked
- * up row, the position is pixel-exact.
- */
- align = (double) (top ? row_start : row_end) / page_size;
- }
- else
- {
- /* Happens if we scroll down to the end - we will query
- * exactly the pixel behind the last one we can get a row for.
- * So take the last row. */
- pos = g_list_model_get_n_items (self->model) - 1;
- align = 1.0;
- top = FALSE;
- }
-
- gtk_list_view_set_anchor (self, pos, align, top);
- }
-
- gtk_widget_queue_allocate (GTK_WIDGET (self));
-}
-
static gboolean
gtk_list_view_get_allocation_along (GtkListBase *base,
guint pos,
@@ -414,14 +290,11 @@ gtk_list_view_move_focus_along (GtkListBase *base,
guint pos,
int steps)
{
- GtkListView *self = GTK_LIST_VIEW (base);
-
if (steps < 0)
return pos - MIN (pos, -steps);
else
{
- guint n_items = self->model ? g_list_model_get_n_items (self->model) : 0;
- pos += MIN (n_items - pos - 1, steps);
+ pos += MIN (gtk_list_base_get_n_items (base) - pos - 1, steps);
}
return pos;
@@ -468,43 +341,6 @@ gtk_list_view_move_focus_across (GtkListBase *base,
return pos;
}
-static int
-gtk_list_view_update_adjustments (GtkListView *self,
- GtkOrientation orientation)
-{
- int upper, page_size, value;
-
- page_size = gtk_widget_get_size (GTK_WIDGET (self), orientation);
-
- if (orientation == gtk_list_base_get_orientation (GTK_LIST_BASE (self)))
- {
- int offset, size;
- guint anchor_pos;
-
- upper = gtk_list_view_get_list_height (self);
- anchor_pos = gtk_list_item_tracker_get_position (self->item_manager, self->anchor);
-
- if (!gtk_list_base_get_allocation_along (GTK_LIST_BASE (self),
- anchor_pos,
- &offset,
- &size))
- {
- g_assert_not_reached ();
- }
- if (!self->anchor_start)
- offset += size;
-
- value = offset - self->anchor_align * page_size;
- }
- else
- {
- upper = self->list_width;
- gtk_list_base_get_adjustment_values (GTK_LIST_BASE (self), orientation, &value, NULL, NULL);
- }
-
- return gtk_list_base_set_adjustment_values (GTK_LIST_BASE (self), orientation, value, upper, page_size);
-}
-
static int
compare_ints (gconstpointer first,
gconstpointer second)
@@ -741,8 +577,14 @@ gtk_list_view_size_allocate (GtkWidget *widget,
}
/* step 3: update the adjustments */
- x = - gtk_list_view_update_adjustments (self, opposite_orientation);
- y = - gtk_list_view_update_adjustments (self, orientation);
+ gtk_list_base_update_adjustments (GTK_LIST_BASE (self),
+ self->list_width,
+ gtk_list_view_get_list_height (self),
+ gtk_widget_get_size (widget, opposite_orientation),
+ gtk_widget_get_size (widget, orientation),
+ &x, &y);
+ x = -x;
+ y = -y;
/* step 4: actually allocate the widgets */
@@ -769,18 +611,6 @@ gtk_list_view_dispose (GObject *object)
{
GtkListView *self = GTK_LIST_VIEW (object);
- g_clear_object (&self->model);
-
- if (self->anchor)
- {
- gtk_list_item_tracker_free (self->item_manager, self->anchor);
- self->anchor = NULL;
- }
- if (self->focus)
- {
- gtk_list_item_tracker_free (self->item_manager, self->focus);
- self->focus = NULL;
- }
self->item_manager = NULL;
G_OBJECT_CLASS (gtk_list_view_parent_class)->dispose (object);
@@ -801,7 +631,7 @@ gtk_list_view_get_property (GObject *object,
break;
case PROP_MODEL:
- g_value_set_object (value, self->model);
+ g_value_set_object (value, gtk_list_base_get_model (GTK_LIST_BASE (self)));
break;
case PROP_SHOW_SEPARATORS:
@@ -842,127 +672,6 @@ gtk_list_view_set_property (GObject *object,
}
}
-static void
-gtk_list_view_update_focus_tracker (GtkListView *self)
-{
- GtkWidget *focus_child;
- guint pos;
-
- focus_child = gtk_widget_get_focus_child (GTK_WIDGET (self));
- if (!GTK_IS_LIST_ITEM (focus_child))
- return;
-
- pos = gtk_list_item_get_position (GTK_LIST_ITEM (focus_child));
- if (pos != gtk_list_item_tracker_get_position (self->item_manager, self->focus))
- {
- gtk_list_item_tracker_set_position (self->item_manager,
- self->focus,
- pos,
- GTK_LIST_VIEW_EXTRA_ITEMS,
- GTK_LIST_VIEW_EXTRA_ITEMS);
- }
-}
-
-static void
-gtk_list_view_compute_scroll_align (GtkListView *self,
- GtkOrientation orientation,
- int cell_start,
- int cell_end,
- double current_align,
- gboolean current_start,
- double *new_align,
- gboolean *new_start)
-{
- int visible_start, visible_size, visible_end;
- int cell_size;
-
- gtk_list_base_get_adjustment_values (GTK_LIST_BASE (self),
- orientation,
- &visible_start,
- NULL,
- &visible_size);
- visible_end = visible_start + visible_size;
- cell_size = cell_end - cell_start;
-
- if (cell_size <= visible_size)
- {
- if (cell_start < visible_start)
- {
- *new_align = 0.0;
- *new_start = TRUE;
- }
- else if (cell_end > visible_end)
- {
- *new_align = 1.0;
- *new_start = FALSE;
- }
- else
- {
- /* XXX: start or end here? */
- *new_start = TRUE;
- *new_align = (double) (cell_start - visible_start) / visible_size;
- }
- }
- else
- {
- /* This is the unlikely case of the cell being higher than the visible area */
- if (cell_start > visible_start)
- {
- *new_align = 0.0;
- *new_start = TRUE;
- }
- else if (cell_end < visible_end)
- {
- *new_align = 1.0;
- *new_start = FALSE;
- }
- else
- {
- /* the cell already covers the whole screen */
- *new_align = current_align;
- *new_start = current_start;
- }
- }
-}
-
-static void
-gtk_list_view_scroll_to_item (GtkWidget *widget,
- const char *action_name,
- GVariant *parameter)
-{
- GtkListView *self = GTK_LIST_VIEW (widget);
- int start, end;
- double align;
- gboolean top;
- guint pos;
-
- if (!g_variant_check_format_string (parameter, "u", FALSE))
- return;
-
- g_variant_get (parameter, "u", &pos);
-
- /* figure out primary orientation and if position is valid */
- if (!gtk_list_base_get_allocation_along (GTK_LIST_BASE (self), pos, &start, &end))
- return;
-
- end += start;
- gtk_list_view_compute_scroll_align (self,
- gtk_list_base_get_orientation (GTK_LIST_BASE (self)),
- start, end,
- self->anchor_align, self->anchor_start,
- &align, &top);
-
- gtk_list_view_set_anchor (self, pos, align, top);
-
- /* HACK HACK HACK
- *
- * GTK has no way to track the focused child. But we now that when a listitem
- * gets focus, it calls this action. So we update our focus tracker from here
- * because it's the closest we can get to accurate tracking.
- */
- gtk_list_view_update_focus_tracker (self);
-}
-
static void
gtk_list_view_activate_item (GtkWidget *widget,
const char *action_name,
@@ -975,7 +684,7 @@ gtk_list_view_activate_item (GtkWidget *widget,
return;
g_variant_get (parameter, "u", &pos);
- if (self->model == NULL || pos >= g_list_model_get_n_items (self->model))
+ if (pos >= gtk_list_base_get_n_items (GTK_LIST_BASE (self)))
return;
g_signal_emit (widget, signals[ACTIVATE], 0, pos);
@@ -992,7 +701,6 @@ gtk_list_view_class_init (GtkListViewClass *klass)
list_base_class->list_item_size = sizeof (ListRow);
list_base_class->list_item_augment_size = sizeof (ListRowAugment);
list_base_class->list_item_augment_func = list_row_augment;
- list_base_class->adjustment_value_changed = gtk_list_view_adjustment_value_changed;
list_base_class->get_allocation_along = gtk_list_view_get_allocation_along;
list_base_class->get_allocation_across = gtk_list_view_get_allocation_across;
list_base_class->get_position_from_allocation = gtk_list_view_get_position_from_allocation;
@@ -1080,18 +788,6 @@ gtk_list_view_class_init (GtkListViewClass *klass)
"u",
gtk_list_view_activate_item);
- /**
- * GtkListView|list.scroll-to-item:
- * @position: position of item to scroll to
- *
- * Scrolls to the item given in @position with the minimum amount
- * of scrolling required. If the item is already visible, nothing happens.
- */
- gtk_widget_class_install_action (widget_class,
- "list.scroll-to-item",
- "u",
- gtk_list_view_scroll_to_item);
-
gtk_widget_class_set_css_name (widget_class, I_("list"));
}
@@ -1099,8 +795,10 @@ static void
gtk_list_view_init (GtkListView *self)
{
self->item_manager = gtk_list_base_get_manager (GTK_LIST_BASE (self));
- self->focus = gtk_list_item_tracker_new (self->item_manager);
- self->anchor = gtk_list_item_tracker_new (self->item_manager);
+
+ gtk_list_base_set_anchor_max_widgets (GTK_LIST_BASE (self),
+ GTK_LIST_VIEW_MAX_LIST_ITEMS,
+ GTK_LIST_VIEW_EXTRA_ITEMS);
}
/**
@@ -1168,7 +866,7 @@ gtk_list_view_get_model (GtkListView *self)
{
g_return_val_if_fail (GTK_IS_LIST_VIEW (self), NULL);
- return self->model;
+ return gtk_list_base_get_model (GTK_LIST_BASE (self));
}
/**
@@ -1188,32 +886,9 @@ gtk_list_view_set_model (GtkListView *self,
g_return_if_fail (GTK_IS_LIST_VIEW (self));
g_return_if_fail (model == NULL || G_IS_LIST_MODEL (model));
- if (self->model == model)
+ if (!gtk_list_base_set_model (GTK_LIST_BASE (self), model))
return;
- g_clear_object (&self->model);
-
- if (model)
- {
- GtkSelectionModel *selection_model;
-
- self->model = g_object_ref (model);
-
- if (GTK_IS_SELECTION_MODEL (model))
- selection_model = GTK_SELECTION_MODEL (g_object_ref (model));
- else
- selection_model = GTK_SELECTION_MODEL (gtk_single_selection_new (model));
-
- gtk_list_item_manager_set_model (self->item_manager, selection_model);
- gtk_list_view_set_anchor (self, 0, 0.0, TRUE);
-
- g_object_unref (selection_model);
- }
- else
- {
- gtk_list_item_manager_set_model (self->item_manager, NULL);
- }
-
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_MODEL]);
}
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]