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:
@@ -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));
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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]);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user