[gtk/wip/otte/listview: 169/215] listbase: Add vfuncs to convert positions to/from coordinates



commit ea027cdf629187cf3b7b040b96ac6053bdc36e02
Author: Benjamin Otte <otte redhat com>
Date:   Fri Oct 25 07:39:57 2019 +0200

    listbase: Add vfuncs to convert positions to/from coordinates
    
    ... and use that to implement PageUp/PageDown.
    
    With that, all keyboard handling has been moved to GtkListBase.

 gtk/gtkgridview.c        | 280 ++++++++++++++++----------------------------
 gtk/gtklistbase.c        | 296 ++++++++++++++++++++++++++++++++++++++++++++++-
 gtk/gtklistbaseprivate.h |  17 +++
 gtk/gtklistview.c        | 240 ++++++++++++++------------------------
 4 files changed, 500 insertions(+), 333 deletions(-)
---
diff --git a/gtk/gtkgridview.c b/gtk/gtkgridview.c
index 4633a4972c..43ced73280 100644
--- a/gtk/gtkgridview.c
+++ b/gtk/gtkgridview.c
@@ -21,7 +21,6 @@
 
 #include "gtkgridview.h"
 
-#include "gtkbindings.h"
 #include "gtkintl.h"
 #include "gtklistbaseprivate.h"
 #include "gtklistitemfactory.h"
@@ -276,35 +275,46 @@ gtk_grid_view_get_cell_at_y (GtkGridView *self,
   return cell;
 }
 
-/*<private>
- * gtk_grid_view_get_size_at_position:
- * @self: a #GtkGridView
- * @position: position of the item
- * @offset: (out caller-allocates) (optional): stores the y coordinate 
- *     of the cell (x coordinate for horizontal grids)
- * @size: (out caller-allocates) (optional): stores the height
- *     of the cell (width for horizontal grids)
- *
- * Computes where the cell at @position is allocated.
- *
- * If position is larger than the number of items, %FALSE will be returned.
- * In particular that means that for an emtpy grid, %FALSE is returned
- * for any value.
- *
- * Returns: (nullable): %TRUE if the cell existed, %FALSE otherwise
- **/
+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_size_at_position (GtkGridView *self,
-                                    guint        position,
+gtk_grid_view_get_allocation_along (GtkListBase *base,
+                                    guint        pos,
                                     int         *offset,
                                     int         *size)
 {
+  GtkGridView *self = GTK_GRID_VIEW (base);
   Cell *cell, *tmp;
   int y;
 
   cell = gtk_list_item_manager_get_root (self->item_manager);
   y = 0;
-  position -= position % self->n_columns;
+  pos -= pos % self->n_columns;
 
   while (cell)
     {
@@ -312,19 +322,19 @@ gtk_grid_view_get_size_at_position (GtkGridView *self,
       if (tmp)
         {
           CellAugment *aug = gtk_list_item_manager_get_item_augment (self->item_manager, tmp);
-          if (position < aug->parent.n_items)
+          if (pos < aug->parent.n_items)
             {
               cell = tmp;
               continue;
             }
-          position -= aug->parent.n_items;
+          pos -= aug->parent.n_items;
           y += aug->size;
         }
 
-      if (position < cell->parent.n_items)
+      if (pos < cell->parent.n_items)
         break;
       y += cell->size;
-      position -= cell->parent.n_items;
+      pos -= cell->parent.n_items;
 
       cell = gtk_rb_tree_node_get_right (cell);
     }
@@ -347,15 +357,15 @@ gtk_grid_view_get_size_at_position (GtkGridView *self,
       guint skip;
 
       /* skip remaining items at end of row */
-      if (position % self->n_columns)
+      if (pos % self->n_columns)
         {
-          skip = position % self->n_columns;
+          skip = pos % self->n_columns;
           n_items -= skip;
-          position -= skip;
+          pos -= skip;
         }
 
       /* Skip all the rows this index doesn't go into */
-      skip = position / self->n_columns;
+      skip = pos / self->n_columns;
       n_items -= skip * self->n_columns;
       y += skip * self->unknown_row_height;
 
@@ -373,31 +383,68 @@ gtk_grid_view_get_size_at_position (GtkGridView *self,
   return TRUE;
 }
 
-static void
-gtk_grid_view_set_anchor (GtkGridView *self,
-                          guint        position,
-                          double       xalign,
-                          gboolean     xstart,
-                          double       yalign,
-                          gboolean     ystart)
+static gboolean
+gtk_grid_view_get_allocation_across (GtkListBase *base,
+                                     guint        pos,
+                                     int         *offset,
+                                     int         *size)
 {
-  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);
+  GtkGridView *self = GTK_GRID_VIEW (base);
+  guint start;
 
-  if (self->anchor_xalign != xalign ||
-      self->anchor_xstart != xstart ||
-      self->anchor_yalign != yalign ||
-      self->anchor_ystart != ystart)
+  pos %= self->n_columns;
+  start = ceil (self->column_width * pos);
+
+  if (offset)
+    *offset = start;
+  if (size)
+    *size = ceil (self->column_width * (pos + 1)) - start;
+
+  return TRUE;
+}
+
+static gboolean
+gtk_grid_view_get_position_from_allocation (GtkListBase           *base,
+                                            int                    across,
+                                            int                    along,
+                                            guint                 *position,
+                                            cairo_rectangle_int_t *area)
+{
+  GtkGridView *self = GTK_GRID_VIEW (base);
+  int offset, size;
+  guint pos, n_items;
+
+  if (across >= self->column_width * self->n_columns)
+    return FALSE;
+
+  n_items = self->model ? g_list_model_get_n_items (self->model) : 0;
+  if (!gtk_grid_view_get_cell_at_y (self,
+                                    along,
+                                    &pos,
+                                    &offset,
+                                    &size))
+    return FALSE;
+
+  pos += floor (across / self->column_width);
+
+  if (pos >= n_items)
     {
-      self->anchor_xalign = xalign;
-      self->anchor_xstart = xstart;
-      self->anchor_yalign = yalign;
-      self->anchor_ystart = ystart;
-      gtk_widget_queue_allocate (GTK_WIDGET (self));
+      /* Ugh, we're in the last row and don't have enough items
+       * to fill the row.
+       * Do it the hard way then... */
+      pos = n_items - 1;
+    }
+
+  *position = pos;
+  if (area)
+    {
+      area->x = ceil (self->column_width * (pos % self->n_columns));
+      area->width = ceil (self->column_width * (1 + pos % self->n_columns)) - area->x;
+      area->y = along - offset;
+      area->height = size;
     }
+
+  return TRUE;
 }
 
 static guint
@@ -589,7 +636,7 @@ gtk_grid_view_update_adjustment (GtkGridView    *self,
       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_grid_view_get_size_at_position (self,
+      if (!gtk_list_base_get_allocation_along (GTK_LIST_BASE (self),
                                                anchor_pos,
                                                &value,
                                                &cell_size))
@@ -1191,7 +1238,7 @@ 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;
@@ -1239,137 +1286,21 @@ gtk_grid_view_activate_item (GtkWidget  *widget,
   g_signal_emit (widget, signals[ACTIVATE], 0, pos);
 }
 
-static void
-gtk_grid_view_move_cursor_page_up (GtkWidget *widget,
-                                   GVariant  *args,
-                                   gpointer   unused)
-{
-  GtkGridView *self = GTK_GRID_VIEW (widget);
-  gboolean select, modify, extend;
-  int offset, start, size, page_size;
-  guint pos, new_pos;
-
-  pos = gtk_list_item_tracker_get_position (self->item_manager, self->anchor);
-  if (pos < self->n_columns) /* already on first row */
-    return;
-  if (!gtk_grid_view_get_size_at_position (self, pos, &start, &size))
-    return;
-  gtk_list_base_get_adjustment_values (GTK_LIST_BASE (self),
-                                       gtk_list_base_get_orientation (GTK_LIST_BASE (self)),
-                                       NULL, NULL, &page_size);
-  if (!gtk_grid_view_get_cell_at_y (self,
-                                    MAX (0, start + size - page_size),
-                                    &new_pos,
-                                    &offset,
-                                    NULL))
-    return;
-  /* gtk_grid_view_get_cell_at_y() returns first column positions, we want to keep columns */
-  new_pos += pos % self->n_columns;
-  if (offset > 0)
-    new_pos += self->n_columns;
-  if (new_pos >= pos)
-    new_pos = pos - self->n_columns;
-
-  g_variant_get (args, "(bbb)", &select, &modify, &extend);
-
-  gtk_list_base_grab_focus_on_item (GTK_LIST_BASE (self), new_pos, select, modify, extend);
-}
-
-static void
-gtk_grid_view_move_cursor_page_down (GtkWidget *widget,
-                                     GVariant  *args,
-                                     gpointer   unused)
-{
-  GtkGridView *self = GTK_GRID_VIEW (widget);
-  gboolean select, modify, extend;
-  int start, page_size;
-  guint pos, new_pos, n_items;
-
-  pos = gtk_list_item_tracker_get_position (self->item_manager, self->anchor);
-  n_items = g_list_model_get_n_items (self->model);
-  if (n_items == 0 || pos / self->n_columns >= (n_items - 1) / self->n_columns)
-    return;
-  if (!gtk_grid_view_get_size_at_position (self, pos, &start, NULL))
-    return;
-  gtk_list_base_get_adjustment_values (GTK_LIST_BASE (self),
-                                       gtk_list_base_get_orientation (GTK_LIST_BASE (self)),
-                                       NULL, NULL, &page_size);
-  if (gtk_grid_view_get_cell_at_y (self,
-                                   start + page_size,
-                                   &new_pos,
-                                   NULL, NULL))
-    {
-      /* We want a fully visible row and we just checked for the row that covers the
-       * pixels more than a page down */
-      if (new_pos >= self->n_columns)
-        new_pos -= self->n_columns;
-    }
-  else 
-    {
-      /* scroll to last row if there's nothing in the place we checked */
-      new_pos = (n_items - 1);
-      new_pos -= new_pos % self->n_columns;
-    }
-
-  /* gtk_grid_view_get_cell_at_y() returns first column positions, we want to keep columns */
-  new_pos += pos % self->n_columns;
-  /* We want to scroll at least one row */
-  if (new_pos <= pos)
-    new_pos = pos + self->n_columns;
-  /* And finally, we need to check we've not scrolled to a cell in the last row that isn't
-   * covered because n_items is not a multiple of n_columns */
-  if (new_pos >= n_items)
-    new_pos = n_items - 1;
-
-  g_variant_get (args, "(bbb)", &select, &modify, &extend);
-
-  gtk_list_base_grab_focus_on_item (GTK_LIST_BASE (self), new_pos, select, modify, extend);
-}
-
-static void
-gtk_grid_view_add_custom_move_binding (GtkBindingSet      *binding_set,
-                                       guint               keyval,
-                                       GtkBindingCallback  callback)
-{
-  gtk_binding_entry_add_callback (binding_set,
-                                  keyval,
-                                  0,
-                                  callback,
-                                  g_variant_new ("(bbb)", TRUE, FALSE, FALSE),
-                                  NULL, NULL);
-  gtk_binding_entry_add_callback (binding_set,
-                                  keyval,
-                                  GDK_CONTROL_MASK,
-                                  callback,
-                                  g_variant_new ("(bbb)", FALSE, FALSE, FALSE),
-                                  NULL, NULL);
-  gtk_binding_entry_add_callback (binding_set,
-                                  keyval,
-                                  GDK_SHIFT_MASK,
-                                  callback,
-                                  g_variant_new ("(bbb)", TRUE, FALSE, TRUE),
-                                  NULL, NULL);
-  gtk_binding_entry_add_callback (binding_set,
-                                  keyval,
-                                  GDK_CONTROL_MASK | GDK_SHIFT_MASK,
-                                  callback,
-                                  g_variant_new ("(bbb)", TRUE, TRUE, TRUE),
-                                  NULL, NULL);
-}
-
 static void
 gtk_grid_view_class_init (GtkGridViewClass *klass)
 {
   GtkListBaseClass *list_base_class = GTK_LIST_BASE_CLASS (klass);
   GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
   GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
-  GtkBindingSet *binding_set;
 
   list_base_class->list_item_name = "flowboxchild";
   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;
   list_base_class->move_focus_along = gtk_grid_view_move_focus_along;
   list_base_class->move_focus_across = gtk_grid_view_move_focus_across;
 
@@ -1482,13 +1413,6 @@ gtk_grid_view_class_init (GtkGridViewClass *klass)
                                    "u",
                                    gtk_grid_view_scroll_to_item);
 
-  binding_set = gtk_binding_set_by_class (klass);
-
-  gtk_grid_view_add_custom_move_binding (binding_set, GDK_KEY_Page_Up, gtk_grid_view_move_cursor_page_up);
-  gtk_grid_view_add_custom_move_binding (binding_set, GDK_KEY_KP_Page_Up, gtk_grid_view_move_cursor_page_up);
-  gtk_grid_view_add_custom_move_binding (binding_set, GDK_KEY_Page_Down, 
gtk_grid_view_move_cursor_page_down);
-  gtk_grid_view_add_custom_move_binding (binding_set, GDK_KEY_KP_Page_Down, 
gtk_grid_view_move_cursor_page_down);
-
   gtk_widget_class_set_css_name (widget_class, I_("flowbox"));
 }
 
diff --git a/gtk/gtklistbase.c b/gtk/gtklistbase.c
index 593e816e1f..8e07b6c8b8 100644
--- a/gtk/gtklistbase.c
+++ b/gtk/gtklistbase.c
@@ -156,9 +156,84 @@ gtk_list_base_move_focus (GtkListBase    *self,
     return gtk_list_base_move_focus_across (self, pos, steps);
 }
 
+/*
+ * gtk_list_base_get_allocation_along:
+ * @self: a #GtkListBase
+ * @pos: item to get the size of
+ * @offset: (out caller-allocates) (allow-none) set to the offset
+ *     of the top/left of the item
+ * @size: (out caller-allocates) (allow-none) set to the size of
+ *     the item in the direction
+ *
+ * Computes the allocation of the item in the direction along the sizing
+ * axis.
+ *
+ * Returns: %TRUE if the item exists and has an allocation, %FALSE otherwise
+ **/
+gboolean
+gtk_list_base_get_allocation_along (GtkListBase *self,
+                                    guint        pos,
+                                    int         *offset,
+                                    int         *size)
+{
+  return GTK_LIST_BASE_GET_CLASS (self)->get_allocation_along (self, pos, offset, size);
+}
+
+/*
+ * gtk_list_base_get_allocation_across:
+ * @self: a #GtkListBase
+ * @pos: item to get the size of
+ * @offset: (out caller-allocates) (allow-none) set to the offset
+ *     of the top/left of the item
+ * @size: (out caller-allocates) (allow-none) set to the size of
+ *     the item in the direction
+ *
+ * Computes the allocation of the item in the direction across to the sizing
+ * axis.
+ *
+ * Returns: %TRUE if the item exists and has an allocation, %FALSE otherwise
+ **/
+static gboolean
+gtk_list_base_get_allocation_across (GtkListBase *self,
+                                     guint        pos,
+                                     int         *offset,
+                                     int         *size)
+{
+  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
+ * @self: a #GtkListBase
  * @pos: item to select
  * @modify: %TRUE if the selection should be modified, %FALSE
  *     if a new selection should be done. This is usually set
@@ -562,6 +637,221 @@ gtk_list_base_move_cursor_to_start (GtkWidget *widget,
   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,
+                                    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_grid_view_get_size_at_position (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);
+}
+#endif
+
+static void
+gtk_list_base_move_cursor_page_up (GtkWidget *widget,
+                                   GVariant  *args,
+                                   gpointer   unused)
+{
+  GtkListBase *self = GTK_LIST_BASE (widget);
+  GtkListBasePrivate *priv = gtk_list_base_get_instance_private (self);
+  gboolean select, modify, extend;
+  cairo_rectangle_int_t area, new_area;
+  int page_size;
+  guint pos, new_pos;
+
+  pos = gtk_list_base_get_focus_position (self);
+  page_size = gtk_adjustment_get_page_size (priv->adjustment[priv->orientation]);
+  if (!gtk_list_base_get_allocation_along (self, pos, &area.y, &area.height) ||
+      !gtk_list_base_get_allocation_across (self, pos, &area.x, &area.width))
+    return;
+  if (!gtk_list_base_get_position_from_allocation (self,
+                                                   area.x + area.width / 2,
+                                                   MAX (0, area.y + area.height - page_size),
+                                                   &new_pos,
+                                                   &new_area))
+    return;
+
+  /* We want the whole row to be visible */
+  if (new_area.y < MAX (0, area.y + area.height - page_size))
+    new_pos = gtk_list_base_move_focus_along (self, new_pos, 1);
+  /* But we definitely want to move if we can */
+  if (new_pos >= pos)
+    {
+      new_pos = gtk_list_base_move_focus_along (self, new_pos, -1);
+      if (new_pos == pos)
+        return;
+    }
+
+  g_variant_get (args, "(bbb)", &select, &modify, &extend);
+
+  gtk_list_base_grab_focus_on_item (GTK_LIST_BASE (self), new_pos, select, modify, extend);
+}
+
+static void
+gtk_list_base_move_cursor_page_down (GtkWidget *widget,
+                                     GVariant  *args,
+                                     gpointer   unused)
+{
+  GtkListBase *self = GTK_LIST_BASE (widget);
+  GtkListBasePrivate *priv = gtk_list_base_get_instance_private (self);
+  gboolean select, modify, extend;
+  cairo_rectangle_int_t area, new_area;
+  int page_size, end;
+  guint pos, new_pos;
+
+  pos = gtk_list_base_get_focus_position (self);
+  page_size = gtk_adjustment_get_page_size (priv->adjustment[priv->orientation]);
+  end = gtk_adjustment_get_upper (priv->adjustment[priv->orientation]);
+  if (end == 0)
+    return;
+
+  if (!gtk_list_base_get_allocation_along (self, pos, &area.y, &area.height) ||
+      !gtk_list_base_get_allocation_across (self, pos, &area.x, &area.width))
+    return;
+
+  if (!gtk_list_base_get_position_from_allocation (self,
+                                                   area.x + area.width / 2,
+                                                   MIN (end, area.y + page_size) - 1,
+                                                   &new_pos,
+                                                   &new_area))
+    return;
+
+  /* We want the whole row to be visible */
+  if (new_area.y + new_area.height > MIN (end, area.y + page_size))
+    new_pos = gtk_list_base_move_focus_along (self, new_pos, -1);
+  /* But we definitely want to move if we can */
+  if (new_pos <= pos)
+    {
+      new_pos = gtk_list_base_move_focus_along (self, new_pos, 1);
+      if (new_pos == pos)
+        return;
+    }
+
+  g_variant_get (args, "(bbb)", &select, &modify, &extend);
+
+  gtk_list_base_grab_focus_on_item (GTK_LIST_BASE (self), new_pos, select, modify, extend);
+}
+
 static void
 gtk_list_base_move_cursor_to_end (GtkWidget *widget,
                                   GVariant  *args,
@@ -761,6 +1051,10 @@ gtk_list_base_class_init (GtkListBaseClass *klass)
   gtk_list_base_add_custom_move_binding (binding_set, GDK_KEY_KP_Home, gtk_list_base_move_cursor_to_start);
   gtk_list_base_add_custom_move_binding (binding_set, GDK_KEY_End, gtk_list_base_move_cursor_to_end);
   gtk_list_base_add_custom_move_binding (binding_set, GDK_KEY_KP_End, gtk_list_base_move_cursor_to_end);
+  gtk_list_base_add_custom_move_binding (binding_set, GDK_KEY_Page_Up, gtk_list_base_move_cursor_page_up);
+  gtk_list_base_add_custom_move_binding (binding_set, GDK_KEY_KP_Page_Up, gtk_list_base_move_cursor_page_up);
+  gtk_list_base_add_custom_move_binding (binding_set, GDK_KEY_Page_Down, 
gtk_list_base_move_cursor_page_down);
+  gtk_list_base_add_custom_move_binding (binding_set, GDK_KEY_KP_Page_Down, 
gtk_list_base_move_cursor_page_down);
 
   gtk_binding_entry_add_action (binding_set, GDK_KEY_a, GDK_CONTROL_MASK, "list.select-all", NULL);
   gtk_binding_entry_add_action (binding_set, GDK_KEY_slash, GDK_CONTROL_MASK, "list.select-all", NULL);
diff --git a/gtk/gtklistbaseprivate.h b/gtk/gtklistbaseprivate.h
index 189538a700..8f3054acaa 100644
--- a/gtk/gtklistbaseprivate.h
+++ b/gtk/gtklistbaseprivate.h
@@ -41,6 +41,19 @@ struct _GtkListBaseClass
 
   void                 (* adjustment_value_changed)             (GtkListBase            *self,
                                                                  GtkOrientation          orientation);
+  gboolean             (* get_allocation_along)                 (GtkListBase            *self,
+                                                                 guint                   pos,
+                                                                 int                    *offset,
+                                                                 int                    *size);
+  gboolean             (* get_allocation_across)                (GtkListBase            *self,
+                                                                 guint                   pos,
+                                                                 int                    *offset,
+                                                                 int                    *size);
+  gboolean             (* get_position_from_allocation)         (GtkListBase            *self,
+                                                                 int                     across,
+                                                                 int                     along,
+                                                                 guint                  *pos,
+                                                                 cairo_rectangle_int_t  *area);
   guint                (* move_focus_along)                     (GtkListBase            *self,
                                                                  guint                   pos,
                                                                  int                     steps);
@@ -55,6 +68,10 @@ 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,
diff --git a/gtk/gtklistview.c b/gtk/gtklistview.c
index 033f4eab8f..69e81afbe7 100644
--- a/gtk/gtklistview.c
+++ b/gtk/gtklistview.c
@@ -21,7 +21,6 @@
 
 #include "gtklistview.h"
 
-#include "gtkbindings.h"
 #include "gtkintl.h"
 #include "gtklistbaseprivate.h"
 #include "gtklistitemmanagerprivate.h"
@@ -264,37 +263,6 @@ list_row_get_y (GtkListView *self,
   return y ;
 }
 
-static gboolean
-gtk_list_view_get_size_at_position (GtkListView *self,
-                                    guint        pos,
-                                    int         *offset,
-                                    int         *height)
-{
-  ListRow *row;
-  guint skip;
-  int y;
-
-  row = gtk_list_item_manager_get_nth (self->item_manager, pos, &skip);
-  if (row == NULL)
-    {
-      if (offset)
-        *offset = 0;
-      if (height)
-        *height = 0;
-      return FALSE;
-    }
-
-  y = list_row_get_y (self, row);
-  y += skip * row->height;
-
-  if (offset)
-    *offset = y;
-  if (height)
-    *height = row->height;
-
-  return TRUE;
-}
-
 static int
 gtk_list_view_get_list_height (GtkListView *self)
 {
@@ -393,6 +361,54 @@ gtk_list_view_adjustment_value_changed (GtkListBase    *base,
   gtk_widget_queue_allocate (GTK_WIDGET (self));
 }
 
+static gboolean
+gtk_list_view_get_allocation_along (GtkListBase *base,
+                                    guint        pos,
+                                    int         *offset,
+                                    int         *size)
+{
+  GtkListView *self = GTK_LIST_VIEW (base);
+  ListRow *row;
+  guint skip;
+  int y;
+
+  row = gtk_list_item_manager_get_nth (self->item_manager, pos, &skip);
+  if (row == NULL)
+    {
+      if (offset)
+        *offset = 0;
+      if (size)
+        *size = 0;
+      return FALSE;
+    }
+
+  y = list_row_get_y (self, row);
+  y += skip * row->height;
+
+  if (offset)
+    *offset = y;
+  if (size)
+    *size = row->height;
+
+  return TRUE;
+}
+
+static gboolean
+gtk_list_view_get_allocation_across (GtkListBase *base,
+                                     guint        pos,
+                                     int         *offset,
+                                     int         *size)
+{
+  GtkListView *self = GTK_LIST_VIEW (base);
+
+  if (offset)
+    *offset = 0;
+  if (size)
+    *size = self->list_width;
+
+  return TRUE;
+}
+
 static guint
 gtk_list_view_move_focus_along (GtkListBase *base,
                                 guint        pos,
@@ -411,6 +427,39 @@ gtk_list_view_move_focus_along (GtkListBase *base,
   return pos;
 }
 
+static gboolean
+gtk_list_view_get_position_from_allocation (GtkListBase           *base,
+                                            int                    across,
+                                            int                    along,
+                                            guint                 *pos,
+                                            cairo_rectangle_int_t *area)
+{
+  GtkListView *self = GTK_LIST_VIEW (base);
+  ListRow *row;
+  int remaining;
+
+  if (across >= self->list_width)
+    return FALSE;
+
+  row = gtk_list_view_get_row_at_y (self, along, &remaining);
+  if (row == NULL)
+    return FALSE;
+
+  *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 (area)
+    {
+      area->x = 0;
+      area->width = self->list_width;
+      area->y = along - remaining % row->height;
+      area->height = row->height;
+    }
+
+  return TRUE;
+}
+
 static guint
 gtk_list_view_move_focus_across (GtkListBase *base,
                                  guint        pos,
@@ -435,7 +484,7 @@ gtk_list_view_update_adjustments (GtkListView    *self,
       upper = gtk_list_view_get_list_height (self);
       anchor_pos = gtk_list_item_tracker_get_position (self->item_manager, self->anchor);
 
-      if (!gtk_list_view_get_size_at_position (self,
+      if (!gtk_list_base_get_allocation_along (GTK_LIST_BASE (self),
                                                anchor_pos,
                                                &offset,
                                                &size))
@@ -893,7 +942,7 @@ gtk_list_view_scroll_to_item (GtkWidget  *widget,
   g_variant_get (parameter, "u", &pos);
 
   /* figure out primary orientation and if position is valid */
-  if (!gtk_list_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;
@@ -932,131 +981,21 @@ gtk_list_view_activate_item (GtkWidget  *widget,
   g_signal_emit (widget, signals[ACTIVATE], 0, pos);
 }
 
-static void
-gtk_list_view_move_cursor_page_up (GtkWidget *widget,
-                                   GVariant  *args,
-                                   gpointer   unused)
-{
-  GtkListView *self = GTK_LIST_VIEW (widget);
-  gboolean select, modify, extend;
-  guint start, pos, n_items;
-  ListRow *row;
-  int pixels, offset;
-
-  start = gtk_list_item_tracker_get_position (self->item_manager, self->focus);
-  row = gtk_list_item_manager_get_nth (self->item_manager, start, NULL);
-  if (row == NULL)
-    return;
-  n_items = self->model ? g_list_model_get_n_items (self->model) : 0;
-  /* check that we can go at least one row up */
-  if (n_items == 0 || start == 0)
-    return;
-
-  pixels = gtk_widget_get_size (widget, gtk_list_base_get_orientation (GTK_LIST_BASE (self)));
-  pixels -= row->height;
-
-  pos = gtk_list_view_get_position_at_y (self, 
-                                         MAX (0, list_row_get_y (self, row) - pixels),
-                                         &offset,
-                                         NULL);
-  /* there'll always be rows between 0 and this row */
-  g_assert (pos < n_items);
-  /* if row is too high, go one row less */
-  if (offset > 0)
-    pos++;
-  /* but go at least 1 row */
-  if (pos >= start)
-    pos = start - 1;
-
-  g_variant_get (args, "(bbb)", &select, &modify, &extend);
-
-  gtk_list_base_grab_focus_on_item (GTK_LIST_BASE (self), pos, select, modify, extend);
-}
-
-static void
-gtk_list_view_move_cursor_page_down (GtkWidget *widget,
-                                     GVariant  *args,
-                                     gpointer   unused)
-{
-  GtkListView *self = GTK_LIST_VIEW (widget);
-  gboolean select, modify, extend;
-  guint start, pos, n_items;
-  ListRow *row;
-  int pixels, offset;
-
-  start = gtk_list_item_tracker_get_position (self->item_manager, self->focus);
-  row = gtk_list_item_manager_get_nth (self->item_manager, start, NULL);
-  if (row == NULL)
-    return;
-  n_items = self->model ? g_list_model_get_n_items (self->model) : 0;
-  /* check that we can go at least one row down */
-  if (n_items == 0 || start >= n_items - 1)
-    return;
-
-  pixels = gtk_widget_get_size (widget, gtk_list_base_get_orientation (GTK_LIST_BASE (self)));
-
-  pos = gtk_list_view_get_position_at_y (self, 
-                                         list_row_get_y (self, row) + pixels,
-                                         &offset,
-                                         NULL);
-  if (pos >= n_items)
-    pos = n_items - 1;
-  /* if row is too high, go one row less */
-  else if (pos > 0 && offset > 0)
-    pos--;
-  /* but go at least 1 row */
-  if (pos <= start)
-    pos = start + 1;
-
-  g_variant_get (args, "(bbb)", &select, &modify, &extend);
-
-  gtk_list_base_grab_focus_on_item (GTK_LIST_BASE (self), pos, select, modify, extend);
-}
-
-static void
-gtk_list_view_add_custom_move_binding (GtkBindingSet      *binding_set,
-                                       guint               keyval,
-                                       GtkBindingCallback  callback)
-{
-  gtk_binding_entry_add_callback (binding_set,
-                                  keyval,
-                                  0,
-                                  callback,
-                                  g_variant_new ("(bbb)", TRUE, FALSE, FALSE),
-                                  NULL, NULL);
-  gtk_binding_entry_add_callback (binding_set,
-                                  keyval,
-                                  GDK_CONTROL_MASK,
-                                  callback,
-                                  g_variant_new ("(bbb)", FALSE, FALSE, FALSE),
-                                  NULL, NULL);
-  gtk_binding_entry_add_callback (binding_set,
-                                  keyval,
-                                  GDK_SHIFT_MASK,
-                                  callback,
-                                  g_variant_new ("(bbb)", TRUE, FALSE, TRUE),
-                                  NULL, NULL);
-  gtk_binding_entry_add_callback (binding_set,
-                                  keyval,
-                                  GDK_CONTROL_MASK | GDK_SHIFT_MASK,
-                                  callback,
-                                  g_variant_new ("(bbb)", TRUE, TRUE, TRUE),
-                                  NULL, NULL);
-}
-
 static void
 gtk_list_view_class_init (GtkListViewClass *klass)
 {
   GtkListBaseClass *list_base_class = GTK_LIST_BASE_CLASS (klass);
   GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
   GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
-  GtkBindingSet *binding_set;
 
   list_base_class->list_item_name = "row";
   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;
   list_base_class->move_focus_along = gtk_list_view_move_focus_along;
   list_base_class->move_focus_across = gtk_list_view_move_focus_across;
 
@@ -1153,13 +1092,6 @@ gtk_list_view_class_init (GtkListViewClass *klass)
                                    "u",
                                    gtk_list_view_scroll_to_item);
 
-  binding_set = gtk_binding_set_by_class (klass);
-
-  gtk_list_view_add_custom_move_binding (binding_set, GDK_KEY_Page_Up, gtk_list_view_move_cursor_page_up);
-  gtk_list_view_add_custom_move_binding (binding_set, GDK_KEY_KP_Page_Up, gtk_list_view_move_cursor_page_up);
-  gtk_list_view_add_custom_move_binding (binding_set, GDK_KEY_Page_Down, 
gtk_list_view_move_cursor_page_down);
-  gtk_list_view_add_custom_move_binding (binding_set, GDK_KEY_KP_Page_Down, 
gtk_list_view_move_cursor_page_down);
-
   gtk_widget_class_set_css_name (widget_class, I_("list"));
 }
 



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]