listbase: Take over anchor handling

With that, pretty much all code but allocating the widgets is gone from
the gridview and listview.
This commit is contained in:
Benjamin Otte
2019-10-26 08:49:27 +02:00
parent ad6a6cd2f3
commit 71ace6763d
4 changed files with 582 additions and 986 deletions

View File

@@ -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,
@@ -1014,8 +806,14 @@ 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);
x = -x;
y = -y;
i = self->n_columns; /* so we run the sizing step at the beginning of the for loop */
row_height = 0;
@@ -1059,18 +857,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);
@@ -1099,7 +885,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:
@@ -1140,134 +926,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,
@@ -1280,7 +938,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);
@@ -1297,7 +955,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;
@@ -1401,18 +1058,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"));
}
@@ -1420,13 +1065,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);
}
/**
@@ -1494,7 +1139,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));
}
/**
@@ -1511,32 +1156,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]);
}
@@ -1616,13 +1238,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));

View File

@@ -26,6 +26,7 @@
#include "gtkintl.h"
#include "gtkorientableprivate.h"
#include "gtkscrollable.h"
#include "gtksingleselection.h"
#include "gtktypebuiltins.h"
typedef struct _GtkListBasePrivate GtkListBasePrivate;
@@ -33,12 +34,22 @@ typedef struct _GtkListBasePrivate GtkListBasePrivate;
struct _GtkListBasePrivate
{
GtkListItemManager *item_manager;
GListModel *model;
GtkOrientation orientation;
GtkAdjustment *adjustment[2];
GtkScrollablePolicy scroll_policy[2];
GtkListItemTracker *anchor;
double anchor_align_along;
double anchor_align_across;
GtkPackType anchor_side_along;
GtkPackType anchor_side_across;
guint center_widgets;
guint above_below_widgets;
/* the last item that was selected - basically the location to extend selections from */
GtkListItemTracker *selected;
/* the item that has input focus */
GtkListItemTracker *focus;
};
enum
@@ -65,18 +76,144 @@ G_GNUC_UNUSED static void gtk_list_base_init (GtkListBase *self) { }
static GParamSpec *properties[N_PROPS] = { NULL, };
/*
* gtk_list_base_get_position_from_allocation:
* @self: a #GtkListBase
* @across: position in pixels in the direction cross to the list
* @along: position in pixels in the direction of the list
* @pos: (out caller-allocates): set to the looked up position
* @area: (out caller-allocates) (allow-none): set to the area occupied
* by the returned position.
*
* Given a coordinate in list coordinates, determine the position of the
* item that occupies that position.
*
* It is possible for @area to not include the point given by (across, along).
* This will happen for example in the last row of a gridview, where the
* last item will be returned for the whole width, even if there are empty
* cells.
*
* Returns: %TRUE on success or %FALSE if no position occupies the given offset.
**/
static guint
gtk_list_base_get_position_from_allocation (GtkListBase *self,
int across,
int along,
guint *pos,
cairo_rectangle_int_t *area)
{
return GTK_LIST_BASE_GET_CLASS (self)->get_position_from_allocation (self, across, along, pos, area);
}
static gboolean
gtk_list_base_adjustment_is_flipped (GtkListBase *self,
GtkOrientation orientation)
{
if (orientation == GTK_ORIENTATION_VERTICAL)
return FALSE;
return gtk_widget_get_direction (GTK_WIDGET (self)) == GTK_TEXT_DIR_RTL;
}
static void
gtk_list_base_get_adjustment_values (GtkListBase *self,
GtkOrientation orientation,
int *value,
int *size,
int *page_size)
{
GtkListBasePrivate *priv = gtk_list_base_get_instance_private (self);
int val, upper, ps;
val = gtk_adjustment_get_value (priv->adjustment[orientation]);
upper = gtk_adjustment_get_upper (priv->adjustment[orientation]);
ps = gtk_adjustment_get_page_size (priv->adjustment[orientation]);
if (gtk_list_base_adjustment_is_flipped (self, orientation))
val = upper - ps - val;
if (value)
*value = val;
if (size)
*size = upper;
if (page_size)
*page_size = ps;
}
static void
gtk_list_base_adjustment_value_changed_cb (GtkAdjustment *adjustment,
GtkListBase *self)
{
GtkListBasePrivate *priv = gtk_list_base_get_instance_private (self);
GtkOrientation orientation;
cairo_rectangle_int_t area, cell_area;
int along, across, total_size;
double align_across, align_along;
GtkPackType side_across, side_along;
guint pos;
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_CLASS (self)->adjustment_value_changed (self, orientation);
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;
/* 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
@@ -170,7 +307,7 @@ gtk_list_base_move_focus (GtkListBase *self,
*
* Returns: %TRUE if the item exists and has an allocation, %FALSE otherwise
**/
gboolean
static gboolean
gtk_list_base_get_allocation_along (GtkListBase *self,
guint pos,
int *offset,
@@ -202,35 +339,6 @@ gtk_list_base_get_allocation_across (GtkListBase *self,
return GTK_LIST_BASE_GET_CLASS (self)->get_allocation_across (self, pos, offset, size);
}
/*
* gtk_list_base_get_position_from_allocation:
* @self: a #GtkListBase
* @across: position in pixels in the direction cross to the list
* @along: position in pixels in the direction of the list
* @pos: (out caller-allocates): set to the looked up position
* @area: (out caller-allocates) (allow-none): set to the area occupied
* by the returned position.
*
* Given a coordinate in list coordinates, determine the position of the
* item that occupies that position.
*
* It is possible for @area to not include the point given by (across, along).
* This will happen for example in the last row of a gridview, where the
* last item will be returned for the whole width, even if there are empty
* cells.
*
* Returns: %TRUE on success or %FALSE if no position occupies the given offset.
**/
static guint
gtk_list_base_get_position_from_allocation (GtkListBase *self,
int across,
int along,
guint *pos,
cairo_rectangle_int_t *area)
{
return GTK_LIST_BASE_GET_CLASS (self)->get_position_from_allocation (self, across, along, pos, area);
}
/*
* gtk_list_base_select_item:
* @self: a #GtkListBase
@@ -323,33 +431,23 @@ gtk_list_base_select_item (GtkListBase *self,
0, 0);
}
static guint
guint
gtk_list_base_get_n_items (GtkListBase *self)
{
GtkListBasePrivate *priv = gtk_list_base_get_instance_private (self);
GtkSelectionModel *model;
model = gtk_list_item_manager_get_model (priv->item_manager);
if (model == NULL)
if (priv->model == NULL)
return 0;
return g_list_model_get_n_items (G_LIST_MODEL (model));
return g_list_model_get_n_items (priv->model);
}
guint
gtk_list_base_get_focus_position (GtkListBase *self)
{
#if 0
GtkListBasePrivate *priv = gtk_list_base_get_instance_private (self);
return gtk_list_item_tracker_get_position (priv->item_manager, priv->focus);
#else
GtkWidget *focus_child = gtk_widget_get_focus_child (GTK_WIDGET (self));
if (focus_child)
return gtk_list_item_get_position (GTK_LIST_ITEM (focus_child));
else
return GTK_INVALID_LIST_POSITION;
#endif
}
static gboolean
@@ -434,13 +532,25 @@ gtk_list_base_dispose (GObject *object)
gtk_list_base_clear_adjustment (self, GTK_ORIENTATION_HORIZONTAL);
gtk_list_base_clear_adjustment (self, GTK_ORIENTATION_VERTICAL);
if (priv->anchor)
{
gtk_list_item_tracker_free (priv->item_manager, priv->anchor);
priv->anchor = NULL;
}
if (priv->selected)
{
gtk_list_item_tracker_free (priv->item_manager, priv->selected);
priv->selected = NULL;
}
if (priv->focus)
{
gtk_list_item_tracker_free (priv->item_manager, priv->focus);
priv->focus = NULL;
}
g_clear_object (&priv->item_manager);
g_clear_object (&priv->model);
G_OBJECT_CLASS (gtk_list_base_parent_class)->dispose (object);
}
@@ -572,6 +682,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,
@@ -634,136 +879,6 @@ 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,
@@ -988,6 +1103,18 @@ gtk_list_base_class_init (GtkListBaseClass *klass)
g_object_class_install_properties (gobject_class, N_PROPS, properties);
/**
* GtkListBase|list.scroll-to-item:
* @position: position of item to scroll to
*
* Moves the visible area to the item given in @position with the minimum amount
* of scrolling required. If the item is already visible, nothing happens.
*/
gtk_widget_class_install_action (widget_class,
"list.scroll-to-item",
"u",
gtk_list_base_scroll_to_item);
/**
* GtkListBase|list.select-item:
* @position: position of item to select
@@ -1070,7 +1197,11 @@ gtk_list_base_init_real (GtkListBase *self,
g_class->list_item_size,
g_class->list_item_augment_size,
g_class->list_item_augment_func);
priv->anchor = gtk_list_item_tracker_new (priv->item_manager);
priv->anchor_side_along = GTK_PACK_START;
priv->anchor_side_across = GTK_PACK_START;
priv->selected = gtk_list_item_tracker_new (priv->item_manager);
priv->focus = gtk_list_item_tracker_new (priv->item_manager);
priv->adjustment[GTK_ORIENTATION_HORIZONTAL] = gtk_adjustment_new (0.0, 0.0, 0.0, 0.0, 0.0, 0.0);
priv->adjustment[GTK_ORIENTATION_VERTICAL] = gtk_adjustment_new (0.0, 0.0, 0.0, 0.0, 0.0, 0.0);
@@ -1080,41 +1211,7 @@ gtk_list_base_init_real (GtkListBase *self,
gtk_widget_set_overflow (GTK_WIDGET (self), GTK_OVERFLOW_HIDDEN);
}
static gboolean
gtk_list_base_adjustment_is_flipped (GtkListBase *self,
GtkOrientation orientation)
{
if (orientation == GTK_ORIENTATION_VERTICAL)
return FALSE;
return gtk_widget_get_direction (GTK_WIDGET (self)) == GTK_TEXT_DIR_RTL;
}
void
gtk_list_base_get_adjustment_values (GtkListBase *self,
GtkOrientation orientation,
int *value,
int *size,
int *page_size)
{
GtkListBasePrivate *priv = gtk_list_base_get_instance_private (self);
int val, upper, ps;
val = gtk_adjustment_get_value (priv->adjustment[orientation]);
upper = gtk_adjustment_get_upper (priv->adjustment[orientation]);
ps = gtk_adjustment_get_page_size (priv->adjustment[orientation]);
if (gtk_list_base_adjustment_is_flipped (self, orientation))
val = upper - ps - val;
if (value)
*value = val;
if (size)
*size = upper;
if (page_size)
*page_size = ps;
}
int
static int
gtk_list_base_set_adjustment_values (GtkListBase *self,
GtkOrientation orientation,
int value,
@@ -1146,6 +1243,61 @@ gtk_list_base_set_adjustment_values (GtkListBase *self,
return value;
}
void
gtk_list_base_update_adjustments (GtkListBase *self,
int total_across,
int total_along,
int page_across,
int page_along,
int *across,
int *along)
{
GtkListBasePrivate *priv = gtk_list_base_get_instance_private (self);
int value_along, value_across, size;
guint pos;
pos = gtk_list_item_tracker_get_position (priv->item_manager, priv->anchor);
if (pos == GTK_INVALID_LIST_POSITION)
{
value_across = 0;
value_along = 0;
}
else
{
if (gtk_list_base_get_allocation_across (self, pos, &value_across, &size))
{
if (priv->anchor_side_across == GTK_PACK_END)
value_across += size;
value_across -= priv->anchor_align_across * page_across;
}
else
{
value_along = 0;
}
if (gtk_list_base_get_allocation_along (self, pos, &value_along, &size))
{
if (priv->anchor_side_along == GTK_PACK_END)
value_along += size;
value_along -= priv->anchor_align_along * page_along;
}
else
{
value_along = 0;
}
}
*across = gtk_list_base_set_adjustment_values (self,
OPPOSITE_ORIENTATION (priv->orientation),
value_across,
total_across,
page_across);
*along = gtk_list_base_set_adjustment_values (self,
priv->orientation,
value_along,
total_along,
page_along);
}
GtkScrollablePolicy
gtk_list_base_get_scroll_policy (GtkListBase *self,
GtkOrientation orientation)
@@ -1171,6 +1323,108 @@ gtk_list_base_get_manager (GtkListBase *self)
return priv->item_manager;
}
/*
* gtk_list_base_set_anchor:
* @self: a #GtkListBase
* @anchor_pos: position of the item to anchor
* @anchor_align_across: how far in the across direction to anchor
* @anchor_side_across: if the anchor should side to start or end
* of item
* @anchor_align_along: how far in the along direction to anchor
* @anchor_side_along: if the anchor should side to start or end
* of item
*
* Sets the anchor.
* The anchor is the item that is always kept on screen.
*
* In each dimension, anchoring uses 2 variables: The side of the
* item that gets anchored - either start or end - and where in
* the widget's allocation it should get anchored - here 0.0 means
* the start of the widget and 1.0 is the end of the widget.
* It is allowed to use values outside of this range. In particular,
* this is necessary when the items are larger than the list's
* allocation.
*
* Using this information, the adjustment's value and in turn widget
* offsets will then be computed. If the anchor is too far off, it
* will be clamped so that there are always visible items on screen.
*
* Making anchoring this complicated ensures that one item - one
* corner of one item to be exact - always stays at the same place
* (usually this item is the focused item). So when the list undergoes
* heavy changes (like sorting, filtering, removals, additions), this
* item will stay in place while everything around it will shuffle
* around.
*
* The anchor will also ensure that enough widgets are created according
* to gtk_list_base_set_anchor_max_widgets().
**/
void
gtk_list_base_set_anchor (GtkListBase *self,
guint anchor_pos,
double anchor_align_across,
GtkPackType anchor_side_across,
double anchor_align_along,
GtkPackType anchor_side_along)
{
GtkListBasePrivate *priv = gtk_list_base_get_instance_private (self);
guint items_before;
items_before = round (priv->center_widgets * CLAMP (anchor_align_along, 0, 1));
gtk_list_item_tracker_set_position (priv->item_manager,
priv->anchor,
anchor_pos,
items_before + priv->above_below_widgets,
priv->center_widgets - items_before + priv->above_below_widgets);
priv->anchor_align_across = anchor_align_across;
priv->anchor_side_across = anchor_side_across;
priv->anchor_align_along = anchor_align_along;
priv->anchor_side_along = anchor_side_along;
gtk_widget_queue_allocate (GTK_WIDGET (self));
}
/**
* gtk_list_base_set_anchor_max_widgets:
* @self: a #GtkListBase
* @center: the number of widgets in the middle
* @above_below: extra widgets above and below
*
* Sets how many widgets should be kept alive around the anchor.
* The number of these widgets determines how many items can be
* displayed and must be chosen to be large enough to cover the
* allocation but should be kept as small as possible for
* performance reasons.
*
* There will be @center widgets allocated around the anchor
* evenly distributed according to the anchor's alignment - if
* the anchor is at the start, all these widgets will be allocated
* behind it, if it's at the end, all the widgets will be allocated
* in front of it.
*
* Addditionally, there will be @above_below widgets allocated both
* before and after the sencter widgets, so the total number of
* widgets kept alive is 2 * above_below + center + 1.
**/
void
gtk_list_base_set_anchor_max_widgets (GtkListBase *self,
guint n_center,
guint n_above_below)
{
GtkListBasePrivate *priv = gtk_list_base_get_instance_private (self);
priv->center_widgets = n_center;
priv->above_below_widgets = n_above_below;
gtk_list_base_set_anchor (self,
gtk_list_item_tracker_get_position (priv->item_manager, priv->anchor),
priv->anchor_align_across,
priv->anchor_side_across,
priv->anchor_align_along,
priv->anchor_side_along);
}
/*
* gtk_list_base_grab_focus_on_item:
* @self: a #GtkListBase
@@ -1231,3 +1485,46 @@ gtk_list_base_grab_focus_on_item (GtkListBase *self,
return TRUE;
}
GListModel *
gtk_list_base_get_model (GtkListBase *self)
{
GtkListBasePrivate *priv = gtk_list_base_get_instance_private (self);
return priv->model;
}
gboolean
gtk_list_base_set_model (GtkListBase *self,
GListModel *model)
{
GtkListBasePrivate *priv = gtk_list_base_get_instance_private (self);
if (priv->model == model)
return FALSE;
g_clear_object (&priv->model);
if (model)
{
GtkSelectionModel *selection_model;
priv->model = g_object_ref (model);
if (GTK_IS_SELECTION_MODEL (model))
selection_model = GTK_SELECTION_MODEL (g_object_ref (model));
else
selection_model = GTK_SELECTION_MODEL (gtk_single_selection_new (model));
gtk_list_item_manager_set_model (priv->item_manager, selection_model);
gtk_list_base_set_anchor (self, 0, 0.0, GTK_PACK_START, 0.0, GTK_PACK_START);
g_object_unref (selection_model);
}
else
{
gtk_list_item_manager_set_model (priv->item_manager, NULL);
}
return TRUE;
}

View File

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

View File

@@ -27,8 +27,6 @@
#include "gtkmain.h"
#include "gtkprivate.h"
#include "gtkrbtreeprivate.h"
#include "gtkselectionmodel.h"
#include "gtksingleselection.h"
#include "gtkstylecontext.h"
#include "gtkwidgetprivate.h"
@@ -57,17 +55,10 @@ struct _GtkListView
{
GtkListBase parent_instance;
GListModel *model;
GtkListItemManager *item_manager;
gboolean show_separators;
int list_width;
GtkListItemTracker *anchor;
double anchor_align;
gboolean anchor_start;
/* the item that has input focus */
GtkListItemTracker *focus;
};
struct _GtkListViewClass
@@ -194,37 +185,6 @@ gtk_list_view_get_row_at_y (GtkListView *self,
return row;
}
static int
gtk_list_view_get_position_at_y (GtkListView *self,
int y,
int *offset,
int *height)
{
ListRow *row;
guint pos;
int remaining;
row = gtk_list_view_get_row_at_y (self, y, &remaining);
if (row == NULL)
{
pos = GTK_INVALID_LIST_POSITION;
if (offset)
*offset = 0;
if (height)
*height = 0;
return GTK_INVALID_LIST_POSITION;
}
pos = gtk_list_item_manager_get_item_position (self->item_manager, row);
g_assert (remaining < row->height * row->parent.n_items);
pos += remaining / row->height;
if (offset)
*offset = remaining % row->height;
if (height)
*height = row->height;
return pos;
}
static int
list_row_get_y (GtkListView *self,
ListRow *row)
@@ -277,90 +237,6 @@ gtk_list_view_get_list_height (GtkListView *self)
return aug->height;
}
static void
gtk_list_view_set_anchor (GtkListView *self,
guint position,
double align,
gboolean start)
{
gtk_list_item_tracker_set_position (self->item_manager,
self->anchor,
position,
GTK_LIST_VIEW_EXTRA_ITEMS + GTK_LIST_VIEW_MAX_LIST_ITEMS * align,
GTK_LIST_VIEW_EXTRA_ITEMS + GTK_LIST_VIEW_MAX_LIST_ITEMS - 1 - GTK_LIST_VIEW_MAX_LIST_ITEMS * align);
if (self->anchor_align != align || self->anchor_start != start)
{
self->anchor_align = align;
self->anchor_start = start;
gtk_widget_queue_allocate (GTK_WIDGET (self));
}
}
static void
gtk_list_view_adjustment_value_changed (GtkListBase *base,
GtkOrientation orientation)
{
GtkListView *self = GTK_LIST_VIEW (base);
if (orientation == gtk_list_base_get_orientation (GTK_LIST_BASE (self)))
{
int page_size, total_size, value, from_start;
int row_start, row_end;
double align;
gboolean top;
guint pos;
gtk_list_base_get_adjustment_values (base, orientation, &value, &total_size, &page_size);
/* Compute how far down we've scrolled. That's the height
* we want to align to. */
align = (double) value / (total_size - page_size);
from_start = round (align * page_size);
pos = gtk_list_view_get_position_at_y (self,
value + from_start,
&row_start, &row_end);
if (pos != GTK_INVALID_LIST_POSITION)
{
/* offset from value - which is where we wanna scroll to */
row_start = from_start - row_start;
row_end += row_start;
/* find an anchor that is in the visible area */
if (row_start > 0 && row_end < page_size)
top = from_start - row_start <= row_end - from_start;
else if (row_start > 0)
top = TRUE;
else if (row_end < page_size)
top = FALSE;
else
{
/* This is the case where the row occupies the whole visible area.
* It's also the only case where align will not end up in [0..1] */
top = from_start - row_start <= row_end - from_start;
}
/* Now compute the align so that when anchoring to the looked
* up row, the position is pixel-exact.
*/
align = (double) (top ? row_start : row_end) / page_size;
}
else
{
/* Happens if we scroll down to the end - we will query
* exactly the pixel behind the last one we can get a row for.
* So take the last row. */
pos = g_list_model_get_n_items (self->model) - 1;
align = 1.0;
top = FALSE;
}
gtk_list_view_set_anchor (self, pos, align, top);
}
gtk_widget_queue_allocate (GTK_WIDGET (self));
}
static gboolean
gtk_list_view_get_allocation_along (GtkListBase *base,
guint pos,
@@ -414,14 +290,11 @@ gtk_list_view_move_focus_along (GtkListBase *base,
guint pos,
int steps)
{
GtkListView *self = GTK_LIST_VIEW (base);
if (steps < 0)
return pos - MIN (pos, -steps);
else
{
guint n_items = self->model ? g_list_model_get_n_items (self->model) : 0;
pos += MIN (n_items - pos - 1, steps);
pos += MIN (gtk_list_base_get_n_items (base) - pos - 1, steps);
}
return pos;
@@ -468,43 +341,6 @@ gtk_list_view_move_focus_across (GtkListBase *base,
return pos;
}
static int
gtk_list_view_update_adjustments (GtkListView *self,
GtkOrientation orientation)
{
int upper, page_size, value;
page_size = gtk_widget_get_size (GTK_WIDGET (self), orientation);
if (orientation == gtk_list_base_get_orientation (GTK_LIST_BASE (self)))
{
int offset, size;
guint anchor_pos;
upper = gtk_list_view_get_list_height (self);
anchor_pos = gtk_list_item_tracker_get_position (self->item_manager, self->anchor);
if (!gtk_list_base_get_allocation_along (GTK_LIST_BASE (self),
anchor_pos,
&offset,
&size))
{
g_assert_not_reached ();
}
if (!self->anchor_start)
offset += size;
value = offset - self->anchor_align * page_size;
}
else
{
upper = self->list_width;
gtk_list_base_get_adjustment_values (GTK_LIST_BASE (self), orientation, &value, NULL, NULL);
}
return gtk_list_base_set_adjustment_values (GTK_LIST_BASE (self), orientation, value, upper, page_size);
}
static int
compare_ints (gconstpointer first,
gconstpointer second)
@@ -741,8 +577,14 @@ gtk_list_view_size_allocate (GtkWidget *widget,
}
/* step 3: update the adjustments */
x = - gtk_list_view_update_adjustments (self, opposite_orientation);
y = - gtk_list_view_update_adjustments (self, orientation);
gtk_list_base_update_adjustments (GTK_LIST_BASE (self),
self->list_width,
gtk_list_view_get_list_height (self),
gtk_widget_get_size (widget, opposite_orientation),
gtk_widget_get_size (widget, orientation),
&x, &y);
x = -x;
y = -y;
/* step 4: actually allocate the widgets */
@@ -769,18 +611,6 @@ gtk_list_view_dispose (GObject *object)
{
GtkListView *self = GTK_LIST_VIEW (object);
g_clear_object (&self->model);
if (self->anchor)
{
gtk_list_item_tracker_free (self->item_manager, self->anchor);
self->anchor = NULL;
}
if (self->focus)
{
gtk_list_item_tracker_free (self->item_manager, self->focus);
self->focus = NULL;
}
self->item_manager = NULL;
G_OBJECT_CLASS (gtk_list_view_parent_class)->dispose (object);
@@ -801,7 +631,7 @@ gtk_list_view_get_property (GObject *object,
break;
case PROP_MODEL:
g_value_set_object (value, self->model);
g_value_set_object (value, gtk_list_base_get_model (GTK_LIST_BASE (self)));
break;
case PROP_SHOW_SEPARATORS:
@@ -842,127 +672,6 @@ gtk_list_view_set_property (GObject *object,
}
}
static void
gtk_list_view_update_focus_tracker (GtkListView *self)
{
GtkWidget *focus_child;
guint pos;
focus_child = gtk_widget_get_focus_child (GTK_WIDGET (self));
if (!GTK_IS_LIST_ITEM (focus_child))
return;
pos = gtk_list_item_get_position (GTK_LIST_ITEM (focus_child));
if (pos != gtk_list_item_tracker_get_position (self->item_manager, self->focus))
{
gtk_list_item_tracker_set_position (self->item_manager,
self->focus,
pos,
GTK_LIST_VIEW_EXTRA_ITEMS,
GTK_LIST_VIEW_EXTRA_ITEMS);
}
}
static void
gtk_list_view_compute_scroll_align (GtkListView *self,
GtkOrientation orientation,
int cell_start,
int cell_end,
double current_align,
gboolean current_start,
double *new_align,
gboolean *new_start)
{
int visible_start, visible_size, visible_end;
int cell_size;
gtk_list_base_get_adjustment_values (GTK_LIST_BASE (self),
orientation,
&visible_start,
NULL,
&visible_size);
visible_end = visible_start + visible_size;
cell_size = cell_end - cell_start;
if (cell_size <= visible_size)
{
if (cell_start < visible_start)
{
*new_align = 0.0;
*new_start = TRUE;
}
else if (cell_end > visible_end)
{
*new_align = 1.0;
*new_start = FALSE;
}
else
{
/* XXX: start or end here? */
*new_start = TRUE;
*new_align = (double) (cell_start - visible_start) / visible_size;
}
}
else
{
/* This is the unlikely case of the cell being higher than the visible area */
if (cell_start > visible_start)
{
*new_align = 0.0;
*new_start = TRUE;
}
else if (cell_end < visible_end)
{
*new_align = 1.0;
*new_start = FALSE;
}
else
{
/* the cell already covers the whole screen */
*new_align = current_align;
*new_start = current_start;
}
}
}
static void
gtk_list_view_scroll_to_item (GtkWidget *widget,
const char *action_name,
GVariant *parameter)
{
GtkListView *self = GTK_LIST_VIEW (widget);
int start, end;
double align;
gboolean top;
guint pos;
if (!g_variant_check_format_string (parameter, "u", FALSE))
return;
g_variant_get (parameter, "u", &pos);
/* figure out primary orientation and if position is valid */
if (!gtk_list_base_get_allocation_along (GTK_LIST_BASE (self), pos, &start, &end))
return;
end += start;
gtk_list_view_compute_scroll_align (self,
gtk_list_base_get_orientation (GTK_LIST_BASE (self)),
start, end,
self->anchor_align, self->anchor_start,
&align, &top);
gtk_list_view_set_anchor (self, pos, align, top);
/* HACK HACK HACK
*
* GTK has no way to track the focused child. But we now that when a listitem
* gets focus, it calls this action. So we update our focus tracker from here
* because it's the closest we can get to accurate tracking.
*/
gtk_list_view_update_focus_tracker (self);
}
static void
gtk_list_view_activate_item (GtkWidget *widget,
const char *action_name,
@@ -975,7 +684,7 @@ gtk_list_view_activate_item (GtkWidget *widget,
return;
g_variant_get (parameter, "u", &pos);
if (self->model == NULL || pos >= g_list_model_get_n_items (self->model))
if (pos >= gtk_list_base_get_n_items (GTK_LIST_BASE (self)))
return;
g_signal_emit (widget, signals[ACTIVATE], 0, pos);
@@ -992,7 +701,6 @@ gtk_list_view_class_init (GtkListViewClass *klass)
list_base_class->list_item_size = sizeof (ListRow);
list_base_class->list_item_augment_size = sizeof (ListRowAugment);
list_base_class->list_item_augment_func = list_row_augment;
list_base_class->adjustment_value_changed = gtk_list_view_adjustment_value_changed;
list_base_class->get_allocation_along = gtk_list_view_get_allocation_along;
list_base_class->get_allocation_across = gtk_list_view_get_allocation_across;
list_base_class->get_position_from_allocation = gtk_list_view_get_position_from_allocation;
@@ -1080,18 +788,6 @@ gtk_list_view_class_init (GtkListViewClass *klass)
"u",
gtk_list_view_activate_item);
/**
* GtkListView|list.scroll-to-item:
* @position: position of item to scroll to
*
* Scrolls to the item given in @position with the minimum amount
* of scrolling required. If the item is already visible, nothing happens.
*/
gtk_widget_class_install_action (widget_class,
"list.scroll-to-item",
"u",
gtk_list_view_scroll_to_item);
gtk_widget_class_set_css_name (widget_class, I_("list"));
}
@@ -1099,8 +795,10 @@ static void
gtk_list_view_init (GtkListView *self)
{
self->item_manager = gtk_list_base_get_manager (GTK_LIST_BASE (self));
self->focus = gtk_list_item_tracker_new (self->item_manager);
self->anchor = gtk_list_item_tracker_new (self->item_manager);
gtk_list_base_set_anchor_max_widgets (GTK_LIST_BASE (self),
GTK_LIST_VIEW_MAX_LIST_ITEMS,
GTK_LIST_VIEW_EXTRA_ITEMS);
}
/**
@@ -1168,7 +866,7 @@ gtk_list_view_get_model (GtkListView *self)
{
g_return_val_if_fail (GTK_IS_LIST_VIEW (self), NULL);
return self->model;
return gtk_list_base_get_model (GTK_LIST_BASE (self));
}
/**
@@ -1188,32 +886,9 @@ gtk_list_view_set_model (GtkListView *self,
g_return_if_fail (GTK_IS_LIST_VIEW (self));
g_return_if_fail (model == NULL || G_IS_LIST_MODEL (model));
if (self->model == model)
if (!gtk_list_base_set_model (GTK_LIST_BASE (self), model))
return;
g_clear_object (&self->model);
if (model)
{
GtkSelectionModel *selection_model;
self->model = g_object_ref (model);
if (GTK_IS_SELECTION_MODEL (model))
selection_model = GTK_SELECTION_MODEL (g_object_ref (model));
else
selection_model = GTK_SELECTION_MODEL (gtk_single_selection_new (model));
gtk_list_item_manager_set_model (self->item_manager, selection_model);
gtk_list_view_set_anchor (self, 0, 0.0, TRUE);
g_object_unref (selection_model);
}
else
{
gtk_list_item_manager_set_model (self->item_manager, NULL);
}
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_MODEL]);
}