[gtk/wip/otte/listview: 100/141] listbase: Take over anchor handling



commit 33d17b1c0d60fcce7d99f13b062cd7fbd3b83021
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        | 593 ++++++++++++++++++++++++++++++++++++++++-------
 gtk/gtklistbaseprivate.h |  34 +--
 gtk/gtklistview.c        | 361 ++---------------------------
 4 files changed, 581 insertions(+), 857 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 7da7f11a48..aed0c9463d 100644
--- a/gtk/gtklistbase.c
+++ b/gtk/gtklistbase.c
@@ -25,6 +25,7 @@
 #include "gtkintl.h"
 #include "gtkorientableprivate.h"
 #include "gtkscrollable.h"
+#include "gtksingleselection.h"
 #include "gtktypebuiltins.h"
 
 typedef struct _GtkListBasePrivate GtkListBasePrivate;
@@ -32,12 +33,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
@@ -64,18 +75,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;
 
-  orientation = adjustment == priv->adjustment[GTK_ORIENTATION_HORIZONTAL] 
-                ? GTK_ORIENTATION_HORIZONTAL
-                : GTK_ORIENTATION_VERTICAL;
+  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;
 
-  GTK_LIST_BASE_GET_CLASS (self)->adjustment_value_changed (self, orientation);
+  /* 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_set_anchor (self,
+                            pos,
+                            align_across, side_across,
+                            align_along, side_along);
+  
+  gtk_widget_queue_allocate (GTK_WIDGET (self));
 }
 
 static void
@@ -169,7 +306,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,
@@ -201,35 +338,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
@@ -322,33 +430,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
@@ -433,13 +531,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);
 }
 
@@ -571,6 +681,141 @@ gtk_list_base_set_property (GObject      *object,
     }
 }
 
+static void
+gtk_list_base_compute_scroll_align (GtkListBase   *self,
+                                    GtkOrientation orientation,
+                                    int            cell_start,
+                                    int            cell_end,
+                                    double         current_align,
+                                    GtkPackType    current_side,
+                                    double        *new_align,
+                                    GtkPackType   *new_side)
+{
+  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_side = GTK_PACK_START;
+        }
+      else if (cell_end > visible_end)
+        {
+          *new_align = 1.0;
+          *new_side = GTK_PACK_END;
+        }
+      else
+        {
+          /* XXX: start or end here? */
+          *new_side = GTK_PACK_START;
+          *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_side = GTK_PACK_START;
+        }
+      else if (cell_end < visible_end)
+        {
+          *new_align = 1.0;
+          *new_side = GTK_PACK_END;
+        }
+      else
+        {
+          /* the cell already covers the whole screen */
+          *new_align = current_align;
+          *new_side = current_side;
+        }
+    }
+}
+
+static void
+gtk_list_base_update_focus_tracker (GtkListBase *self)
+{
+  GtkListBasePrivate *priv = gtk_list_base_get_instance_private (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 (priv->item_manager, priv->focus))
+    {
+      gtk_list_item_tracker_set_position (priv->item_manager,
+                                          priv->focus,
+                                          pos,
+                                          0,
+                                          0);
+    }
+}
+
+static void
+gtk_list_base_scroll_to_item (GtkWidget  *widget,
+                              const char *action_name,
+                              GVariant   *parameter)
+{
+  GtkListBase *self = GTK_LIST_BASE (widget);
+  GtkListBasePrivate *priv = gtk_list_base_get_instance_private (self);
+  int start, end;
+  double align_along, align_across;
+  GtkPackType side_along, side_across;
+  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_base_compute_scroll_align (self,
+                                      gtk_list_base_get_orientation (GTK_LIST_BASE (self)),
+                                      start, end,
+                                      priv->anchor_align_along, priv->anchor_side_along,
+                                      &align_along, &side_along);
+
+  /* now do the same thing with the other orientation */
+  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,
+                                      priv->anchor_align_across, priv->anchor_side_across,
+                                      &align_across, &side_across);
+
+  gtk_list_base_set_anchor (self,
+                            pos,
+                            align_across, side_across,
+                            align_along, side_along);
+
+  /* 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_base_update_focus_tracker (self);
+}
+
 static void
 gtk_list_base_select_item_action (GtkWidget  *widget,
                                   const char *action_name,
@@ -859,6 +1104,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
@@ -939,7 +1196,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);
@@ -949,41 +1210,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,
@@ -1015,6 +1242,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)
@@ -1040,6 +1322,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
@@ -1100,3 +1484,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..a590abd882 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)
@@ -268,7 +228,7 @@ gtk_list_view_get_list_height (GtkListView *self)
 {
   ListRow *row;
   ListRowAugment *aug;
-  
+
   row = gtk_list_item_manager_get_root (self->item_manager);
   if (row == NULL)
     return 0;
@@ -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]