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.
This commit is contained in:
Benjamin Otte
2019-10-25 07:39:57 +02:00
committed by Matthias Clasen
parent 045cea31e5
commit f4150c6d64
4 changed files with 422 additions and 373 deletions

View File

@@ -275,103 +275,6 @@ 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 gboolean
gtk_grid_view_get_size_at_position (GtkGridView *self,
guint position,
int *offset,
int *size)
{
Cell *cell, *tmp;
int y;
cell = gtk_list_item_manager_get_root (self->item_manager);
y = 0;
position -= position % self->n_columns;
while (cell)
{
tmp = gtk_rb_tree_node_get_left (cell);
if (tmp)
{
CellAugment *aug = gtk_list_item_manager_get_item_augment (self->item_manager, tmp);
if (position < aug->parent.n_items)
{
cell = tmp;
continue;
}
position -= aug->parent.n_items;
y += aug->size;
}
if (position < cell->parent.n_items)
break;
y += cell->size;
position -= cell->parent.n_items;
cell = gtk_rb_tree_node_get_right (cell);
}
if (cell == NULL)
{
if (offset)
*offset = 0;
if (size)
*size = 0;
return FALSE;
}
/* We know have the (range of) cell(s) that contains this offset.
* Now for the hard part of computing which index this actually is.
*/
if (offset || size)
{
guint n_items = cell->parent.n_items;
guint skip;
/* skip remaining items at end of row */
if (position % self->n_columns)
{
skip = position % self->n_columns;
n_items -= skip;
position -= skip;
}
/* Skip all the rows this index doesn't go into */
skip = position / self->n_columns;
n_items -= skip * self->n_columns;
y += skip * self->unknown_row_height;
if (offset)
*offset = y;
if (size)
{
if (n_items > self->n_columns)
*size = self->unknown_row_height;
else
*size = cell->size - skip * self->unknown_row_height;
}
}
return TRUE;
}
static void
gtk_grid_view_set_anchor (GtkGridView *self,
guint position,
@@ -399,6 +302,151 @@ gtk_grid_view_set_anchor (GtkGridView *self,
}
}
static gboolean
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;
pos -= pos % self->n_columns;
while (cell)
{
tmp = gtk_rb_tree_node_get_left (cell);
if (tmp)
{
CellAugment *aug = gtk_list_item_manager_get_item_augment (self->item_manager, tmp);
if (pos < aug->parent.n_items)
{
cell = tmp;
continue;
}
pos -= aug->parent.n_items;
y += aug->size;
}
if (pos < cell->parent.n_items)
break;
y += cell->size;
pos -= cell->parent.n_items;
cell = gtk_rb_tree_node_get_right (cell);
}
if (cell == NULL)
{
if (offset)
*offset = 0;
if (size)
*size = 0;
return FALSE;
}
/* We know have the (range of) cell(s) that contains this offset.
* Now for the hard part of computing which index this actually is.
*/
if (offset || size)
{
guint n_items = cell->parent.n_items;
guint skip;
/* skip remaining items at end of row */
if (pos % self->n_columns)
{
skip = pos % self->n_columns;
n_items -= skip;
pos -= skip;
}
/* Skip all the rows this index doesn't go into */
skip = pos / self->n_columns;
n_items -= skip * self->n_columns;
y += skip * self->unknown_row_height;
if (offset)
*offset = y;
if (size)
{
if (n_items > self->n_columns)
*size = self->unknown_row_height;
else
*size = cell->size - skip * self->unknown_row_height;
}
}
return TRUE;
}
static gboolean
gtk_grid_view_get_allocation_across (GtkListBase *base,
guint pos,
int *offset,
int *size)
{
GtkGridView *self = GTK_GRID_VIEW (base);
guint start;
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)
{
/* 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
gtk_grid_view_move_focus_along (GtkListBase *base,
guint pos,
@@ -588,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))
@@ -1213,7 +1261,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;
@@ -1261,124 +1309,6 @@ gtk_grid_view_activate_item (GtkWidget *widget,
g_signal_emit (widget, signals[ACTIVATE], 0, pos);
}
static gboolean
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 TRUE;
if (!gtk_grid_view_get_size_at_position (self, pos, &start, &size))
return TRUE;
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 TRUE;
/* 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);
return TRUE;
}
static gboolean
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 TRUE;
if (!gtk_grid_view_get_size_at_position (self, pos, &start, NULL))
return TRUE;
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);
return TRUE;
}
static void
gtk_grid_view_add_custom_move_binding (GtkWidgetClass *widget_class,
guint keyval,
GtkShortcutFunc callback)
{
gtk_widget_class_add_binding (widget_class,
keyval,
0,
callback,
"(bbb)", TRUE, FALSE, FALSE);
gtk_widget_class_add_binding (widget_class,
keyval,
GDK_CONTROL_MASK,
callback,
"(bbb)", FALSE, FALSE, FALSE);
gtk_widget_class_add_binding (widget_class,
keyval,
GDK_SHIFT_MASK,
callback,
"(bbb)", TRUE, FALSE, TRUE);
gtk_widget_class_add_binding (widget_class,
keyval,
GDK_CONTROL_MASK | GDK_SHIFT_MASK,
callback,
"(bbb)", TRUE, TRUE, TRUE);
}
static void
gtk_grid_view_class_init (GtkGridViewClass *klass)
{
@@ -1391,6 +1321,9 @@ gtk_grid_view_class_init (GtkGridViewClass *klass)
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;
@@ -1503,11 +1436,6 @@ gtk_grid_view_class_init (GtkGridViewClass *klass)
"u",
gtk_grid_view_scroll_to_item);
gtk_grid_view_add_custom_move_binding (widget_class, GDK_KEY_Page_Up, gtk_grid_view_move_cursor_page_up);
gtk_grid_view_add_custom_move_binding (widget_class, GDK_KEY_KP_Page_Up, gtk_grid_view_move_cursor_page_up);
gtk_grid_view_add_custom_move_binding (widget_class, GDK_KEY_Page_Down, gtk_grid_view_move_cursor_page_down);
gtk_grid_view_add_custom_move_binding (widget_class, GDK_KEY_KP_Page_Down, gtk_grid_view_move_cursor_page_down);
gtk_widget_class_set_css_name (widget_class, I_("flowbox"));
}

View File

@@ -155,9 +155,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
@@ -560,6 +635,95 @@ gtk_list_base_move_cursor_to_start (GtkWidget *widget,
return TRUE;
}
static gboolean
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 TRUE;
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 TRUE;
/* 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 TRUE;
}
g_variant_get (args, "(bbb)", &select, &modify, &extend);
gtk_list_base_grab_focus_on_item (GTK_LIST_BASE (self), new_pos, select, modify, extend);
return TRUE;
}
static gboolean
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 TRUE;
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 TRUE;
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 TRUE;
/* 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 TRUE;
}
g_variant_get (args, "(bbb)", &select, &modify, &extend);
gtk_list_base_grab_focus_on_item (GTK_LIST_BASE (self), new_pos, select, modify, extend);
return TRUE;
}
static gboolean
gtk_list_base_move_cursor_to_end (GtkWidget *widget,
GVariant *args,
@@ -753,6 +917,10 @@ gtk_list_base_class_init (GtkListBaseClass *klass)
gtk_list_base_add_custom_move_binding (widget_class, GDK_KEY_KP_Home, gtk_list_base_move_cursor_to_start);
gtk_list_base_add_custom_move_binding (widget_class, GDK_KEY_End, gtk_list_base_move_cursor_to_end);
gtk_list_base_add_custom_move_binding (widget_class, GDK_KEY_KP_End, gtk_list_base_move_cursor_to_end);
gtk_list_base_add_custom_move_binding (widget_class, GDK_KEY_Page_Up, gtk_list_base_move_cursor_page_up);
gtk_list_base_add_custom_move_binding (widget_class, GDK_KEY_KP_Page_Up, gtk_list_base_move_cursor_page_up);
gtk_list_base_add_custom_move_binding (widget_class, GDK_KEY_Page_Down, gtk_list_base_move_cursor_page_down);
gtk_list_base_add_custom_move_binding (widget_class, GDK_KEY_KP_Page_Down, gtk_list_base_move_cursor_page_down);
gtk_widget_class_add_binding_action (widget_class, GDK_KEY_a, GDK_CONTROL_MASK, "list.select-all", NULL);
gtk_widget_class_add_binding_action (widget_class, GDK_KEY_slash, GDK_CONTROL_MASK, "list.select-all", NULL);

View File

@@ -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,

View File

@@ -263,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)
{
@@ -392,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,
@@ -410,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,
@@ -434,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))
@@ -892,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;
@@ -931,118 +981,6 @@ gtk_list_view_activate_item (GtkWidget *widget,
g_signal_emit (widget, signals[ACTIVATE], 0, pos);
}
static gboolean
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 TRUE;
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 TRUE;
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);
return TRUE;
}
static gboolean
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 TRUE;
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 TRUE;
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);
return TRUE;
}
static void
gtk_list_view_add_custom_move_binding (GtkWidgetClass *widget_class,
guint keyval,
GtkShortcutFunc callback)
{
gtk_widget_class_add_binding (widget_class,
keyval,
0,
callback,
"(bbb)", TRUE, FALSE, FALSE);
gtk_widget_class_add_binding (widget_class,
keyval,
GDK_CONTROL_MASK,
callback,
"(bbb)", FALSE, FALSE, FALSE);
gtk_widget_class_add_binding (widget_class,
keyval,
GDK_SHIFT_MASK,
callback,
"(bbb)", TRUE, FALSE, TRUE);
gtk_widget_class_add_binding (widget_class,
keyval,
GDK_CONTROL_MASK | GDK_SHIFT_MASK,
callback,
"(bbb)", TRUE, TRUE, TRUE);
}
static void
gtk_list_view_class_init (GtkListViewClass *klass)
{
@@ -1055,6 +993,9 @@ gtk_list_view_class_init (GtkListViewClass *klass)
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;
@@ -1151,11 +1092,6 @@ gtk_list_view_class_init (GtkListViewClass *klass)
"u",
gtk_list_view_scroll_to_item);
gtk_list_view_add_custom_move_binding (widget_class, GDK_KEY_Page_Up, gtk_list_view_move_cursor_page_up);
gtk_list_view_add_custom_move_binding (widget_class, GDK_KEY_KP_Page_Up, gtk_list_view_move_cursor_page_up);
gtk_list_view_add_custom_move_binding (widget_class, GDK_KEY_Page_Down, gtk_list_view_move_cursor_page_down);
gtk_list_view_add_custom_move_binding (widget_class, GDK_KEY_KP_Page_Down, gtk_list_view_move_cursor_page_down);
gtk_widget_class_set_css_name (widget_class, I_("list"));
}