diff options
author | Benjamin Otte <otte@redhat.com> | 2019-10-26 08:49:27 +0200 |
---|---|---|
committer | Benjamin Otte <otte@redhat.com> | 2019-10-28 10:49:02 +0100 |
commit | cabbe06c5baad51c1b10b0cad0a70cdfda2d36eb (patch) | |
tree | bea552ab2619e4ca0ce84eda88c97d2de7f2e665 | |
parent | f2044539ee7cb44bd58474aa546e9ce15994167c (diff) | |
download | gtk+-cabbe06c5baad51c1b10b0cad0a70cdfda2d36eb.tar.gz |
listbase: Take over anchor handling
With that, pretty much all code but allocating the widgets is gone from
the gridview and listview.
-rw-r--r-- | gtk/gtkgridview.c | 452 | ||||
-rw-r--r-- | gtk/gtklistbase.c | 651 | ||||
-rw-r--r-- | gtk/gtklistbaseprivate.h | 34 | ||||
-rw-r--r-- | gtk/gtklistview.c | 359 |
4 files changed, 546 insertions, 950 deletions
diff --git a/gtk/gtkgridview.c b/gtk/gtkgridview.c index 43ced73280..85253c432b 100644 --- a/gtk/gtkgridview.c +++ b/gtk/gtkgridview.c @@ -57,7 +57,6 @@ struct _GtkGridView { GtkListBase parent_instance; - GListModel *model; GtkListItemManager *item_manager; guint min_columns; guint max_columns; @@ -65,14 +64,6 @@ struct _GtkGridView guint n_columns; double column_width; int unknown_row_height; - - GtkListItemTracker *anchor; - double anchor_xalign; - double anchor_yalign; - guint anchor_xstart : 1; - guint anchor_ystart : 1; - /* the item that has input focus */ - GtkListItemTracker *focus; }; struct _GtkGridViewClass @@ -275,33 +266,6 @@ gtk_grid_view_get_cell_at_y (GtkGridView *self, return cell; } -static void -gtk_grid_view_set_anchor (GtkGridView *self, - guint position, - double xalign, - gboolean xstart, - double yalign, - gboolean ystart) -{ - gtk_list_item_tracker_set_position (self->item_manager, - self->anchor, - position, - (ceil (GTK_GRID_VIEW_MAX_VISIBLE_ROWS * yalign) + 1) * self->max_columns, - (ceil (GTK_GRID_VIEW_MAX_VISIBLE_ROWS * (1 - yalign)) + 1) * self->max_columns); - - if (self->anchor_xalign != xalign || - self->anchor_xstart != xstart || - self->anchor_yalign != yalign || - self->anchor_ystart != ystart) - { - self->anchor_xalign = xalign; - self->anchor_xstart = xstart; - self->anchor_yalign = yalign; - self->anchor_ystart = ystart; - gtk_widget_queue_allocate (GTK_WIDGET (self)); - } -} - static gboolean gtk_grid_view_get_allocation_along (GtkListBase *base, guint pos, @@ -417,7 +381,7 @@ gtk_grid_view_get_position_from_allocation (GtkListBase *base, if (across >= self->column_width * self->n_columns) return FALSE; - n_items = self->model ? g_list_model_get_n_items (self->model) : 0; + n_items = gtk_list_base_get_n_items (base); if (!gtk_grid_view_get_cell_at_y (self, along, &pos, @@ -463,7 +427,7 @@ gtk_grid_view_move_focus_along (GtkListBase *base, } else { - guint n_items = self->model ? g_list_model_get_n_items (self->model) : 0; + guint n_items = gtk_list_base_get_n_items (base); if (n_items / self->n_columns > pos / self->n_columns) pos += MIN (n_items - pos - 1, steps); } @@ -476,202 +440,17 @@ gtk_grid_view_move_focus_across (GtkListBase *base, guint pos, int steps) { - GtkGridView *self = GTK_GRID_VIEW (base); - if (steps < 0) return pos - MIN (pos, -steps); else { - guint n_items = self->model ? g_list_model_get_n_items (self->model) : 0; + guint n_items = gtk_list_base_get_n_items (base); pos += MIN (n_items - pos - 1, steps); } return pos; } -static void -gtk_grid_view_adjustment_value_changed (GtkListBase *base, - GtkOrientation orientation) -{ - GtkGridView *self = GTK_GRID_VIEW (base); - int page_size, total_size, value, from_start; - guint pos, anchor_pos, n_items; - int offset, height, top, bottom; - double xalign, yalign; - gboolean xstart, ystart; - - gtk_list_base_get_adjustment_values (base, orientation, &value, &total_size, &page_size); - anchor_pos = gtk_list_item_tracker_get_position (self->item_manager, self->anchor); - n_items = g_list_model_get_n_items (self->model); - - if (orientation == gtk_list_base_get_orientation (GTK_LIST_BASE (self))) - { - /* Compute how far down we've scrolled. That's the height - * we want to align to. */ - yalign = (double) value / (total_size - page_size); - from_start = round (yalign * page_size); - - /* We want the cell that far down the page */ - if (gtk_grid_view_get_cell_at_y (self, - value + from_start, - &pos, - &offset, - &height)) - { - /* offset from value - which is where we wanna scroll to */ - top = from_start - offset; - bottom = top + height; - - /* find an anchor that is in the visible area */ - if (top > 0 && bottom < page_size) - ystart = from_start - top <= bottom - from_start; - else if (top > 0) - ystart = TRUE; - else if (bottom < page_size) - ystart = FALSE; - else - { - /* This is the case where the cell occupies the whole visible area. - * It's also the only case where align will not end up in [0..1] */ - ystart = from_start - top <= bottom - from_start; - } - - /* Now compute the align so that when anchoring to the looked - * up cell, the position is pixel-exact. - */ - yalign = (double) (ystart ? top : bottom) / page_size; - } - else - { - /* Happens if we scroll down to the end - we will query - * exactly the pixel behind the last one we can get a cell for. - * So take the last row. */ - pos = n_items - 1; - pos = pos - pos % self->n_columns; - yalign = 1.0; - ystart = FALSE; - } - - /* And finally, keep the column anchor intact. */ - anchor_pos %= self->n_columns; - pos += anchor_pos; - xstart = self->anchor_xstart; - xalign = self->anchor_xalign; - } - else - { - xalign = (double) value / (total_size - page_size); - from_start = round (xalign * page_size); - pos = floor ((value + from_start) / self->column_width); - if (pos >= self->n_columns) - { - /* scrolling to the end sets pos to exactly self->n_columns */ - pos = self->n_columns - 1; - xstart = FALSE; - xalign = 1.0; - } - else - { - top = ceil (self->column_width * pos) - value; - bottom = ceil (self->column_width * (pos + 1)) - value; - - /* offset from value - which is where we wanna scroll to */ - - /* find an anchor that is in the visible area */ - if (top > 0 && bottom < page_size) - xstart = from_start - top <= bottom - from_start; - else if (top > 0) - xstart = TRUE; - else if (bottom < page_size) - xstart = FALSE; - else - xstart = from_start - top <= bottom - from_start; - - xalign = (double) (xstart ? top : bottom) / page_size; - } - - /* And finally, keep the row anchor intact. */ - pos += (anchor_pos - anchor_pos % self->n_columns); - yalign = self->anchor_yalign; - ystart = self->anchor_ystart; - } - - if (pos >= n_items) - { - /* Ugh, we're in the last row and don't have enough items - * to fill the row. - * Do it the hard way then... */ - gtk_list_base_get_adjustment_values (base, - gtk_list_base_get_opposite_orientation (base), - &value, &total_size, &page_size); - - pos = n_items - 1; - xstart = FALSE; - xalign = (ceil (self->column_width * (pos % self->n_columns + 1)) - value) / page_size; - } - - gtk_grid_view_set_anchor (self, pos, xalign, xstart, yalign, ystart); - - gtk_widget_queue_allocate (GTK_WIDGET (self)); -} - -static int -gtk_grid_view_update_adjustment (GtkGridView *self, - GtkOrientation orientation) -{ - int value, page_size, cell_size, total_size; - guint anchor_pos; - - anchor_pos = gtk_list_item_tracker_get_position (self->item_manager, self->anchor); - if (anchor_pos == GTK_INVALID_LIST_POSITION) - return gtk_list_base_set_adjustment_values (GTK_LIST_BASE (self), orientation, 0, 0, 0); - - page_size = gtk_widget_get_size (GTK_WIDGET (self), orientation); - - if (gtk_list_base_get_orientation (GTK_LIST_BASE (self)) == orientation) - { - Cell *cell; - CellAugment *aug; - - cell = gtk_list_item_manager_get_root (self->item_manager); - g_assert (cell); - aug = gtk_list_item_manager_get_item_augment (self->item_manager, cell); - if (!gtk_list_base_get_allocation_along (GTK_LIST_BASE (self), - anchor_pos, - &value, - &cell_size)) - { - g_assert_not_reached (); - } - if (!self->anchor_ystart) - value += cell_size; - - value = gtk_list_base_set_adjustment_values (GTK_LIST_BASE (self), - orientation, - value - self->anchor_yalign * page_size, - aug->size, - page_size); - } - else - { - guint i = anchor_pos % self->n_columns; - - if (self->anchor_xstart) - value = ceil (self->column_width * i); - else - value = ceil (self->column_width * (i + 1)); - total_size = round (self->n_columns * self->column_width); - - value = gtk_list_base_set_adjustment_values (GTK_LIST_BASE (self), - orientation, - value - self->anchor_xalign * page_size, - total_size, - page_size); - } - - return value; -} - static int compare_ints (gconstpointer first, gconstpointer second) @@ -904,6 +683,19 @@ gtk_grid_view_size_allocate_child (GtkGridView *self, gtk_widget_size_allocate (child, &child_allocation, -1); } +static int +gtk_grid_view_compute_total_height (GtkGridView *self) +{ + Cell *cell; + CellAugment *aug; + + cell = gtk_list_item_manager_get_root (self->item_manager); + if (cell == NULL) + return 0; + aug = gtk_list_item_manager_get_item_augment (self->item_manager, cell); + return aug->size; +} + static void gtk_grid_view_size_allocate (GtkWidget *widget, int width, @@ -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: @@ -1141,134 +927,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, GVariant *parameter) @@ -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)); diff --git a/gtk/gtklistbase.c b/gtk/gtklistbase.c index 8e07b6c8b8..6867a7263d 100644 --- a/gtk/gtklistbase.c +++ b/gtk/gtklistbase.c @@ -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; + + gtk_list_base_get_adjustment_values (self, OPPOSITE_ORIENTATION (priv->orientation), &area.x, &total_size, &area.width); + if (total_size == area.width) + align_across = 0.5; + else if (adjustment != priv->adjustment[priv->orientation]) + align_across = CLAMP (priv->anchor_align_across, 0, 1); + else + align_across = (double) area.x / (total_size - area.width); + across = area.x + round (align_across * area.width); + across = CLAMP (across, 0, total_size - 1); + + gtk_list_base_get_adjustment_values (self, priv->orientation, &area.y, &total_size, &area.height); + if (total_size == area.height) + align_along = 0.5; + else if (adjustment != priv->adjustment[OPPOSITE_ORIENTATION(priv->orientation)]) + align_along = CLAMP (priv->anchor_align_along, 0, 1); + else + align_along = (double) area.y / (total_size - area.height); + along = area.y + round (align_along * area.height); + along = CLAMP (along, 0, total_size - 1); + + if (!gtk_list_base_get_position_from_allocation (self, + across, along, + &pos, + &cell_area)) + { + g_warning ("%s failed to scroll to given position. Ignoring...", G_OBJECT_TYPE_NAME (self)); + return; + } + + /* find an anchor that is in the visible area */ + if (cell_area.x < area.x && cell_area.x + cell_area.width <= area.x + area.width) + side_across = GTK_PACK_END; + else if (cell_area.x >= area.x && cell_area.x + cell_area.width > area.x + area.width) + side_across = GTK_PACK_START; + else if (cell_area.x + cell_area.width / 2 > across) + side_across = GTK_PACK_END; + else + side_across = GTK_PACK_START; + + if (cell_area.y < area.y && cell_area.y + cell_area.height <= area.y + area.height) + side_along = GTK_PACK_END; + else if (cell_area.y >= area.y && cell_area.y + cell_area.height > area.y + area.height) + side_along = GTK_PACK_START; + else if (cell_area.y + cell_area.height / 2 > along) + side_along = GTK_PACK_END; + else + side_along = GTK_PACK_START; - orientation = adjustment == priv->adjustment[GTK_ORIENTATION_HORIZONTAL] - ? GTK_ORIENTATION_HORIZONTAL - : GTK_ORIENTATION_VERTICAL; + /* 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_GET_CLASS (self)->adjustment_value_changed (self, orientation); + 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, @@ -203,35 +340,6 @@ gtk_list_base_get_allocation_across (GtkListBase *self, } /* - * 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 * @pos: item to select @@ -326,33 +434,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 @@ -437,13 +535,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); } @@ -576,77 +686,14 @@ gtk_list_base_set_property (GObject *object, } static void -gtk_list_base_select_item_action (GtkWidget *widget, - const char *action_name, - GVariant *parameter) -{ - GtkListBase *self = GTK_LIST_BASE (widget); - guint pos; - gboolean modify, extend; - - g_variant_get (parameter, "(ubb)", &pos, &modify, &extend); - - gtk_list_base_select_item (self, pos, modify, extend); -} - -static void -gtk_list_base_select_all (GtkWidget *widget, - const char *action_name, - GVariant *parameter) -{ - GtkListBase *self = GTK_LIST_BASE (widget); - GtkListBasePrivate *priv = gtk_list_base_get_instance_private (self); - GtkSelectionModel *selection_model; - - selection_model = gtk_list_item_manager_get_model (priv->item_manager); - if (selection_model == NULL) - return; - - gtk_selection_model_select_all (selection_model); -} - -static void -gtk_list_base_unselect_all (GtkWidget *widget, - const char *action_name, - GVariant *parameter) -{ - GtkListBase *self = GTK_LIST_BASE (widget); - GtkListBasePrivate *priv = gtk_list_base_get_instance_private (self); - GtkSelectionModel *selection_model; - - selection_model = gtk_list_item_manager_get_model (priv->item_manager); - if (selection_model == NULL) - return; - - gtk_selection_model_unselect_all (selection_model); -} - -static void -gtk_list_base_move_cursor_to_start (GtkWidget *widget, - GVariant *args, - gpointer unused) -{ - GtkListBase *self = GTK_LIST_BASE (widget); - gboolean select, modify, extend; - - if (gtk_list_base_get_n_items (self) == 0) - return; - - g_variant_get (args, "(bbb)", &select, &modify, &extend); - - 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, +gtk_list_base_compute_scroll_align (GtkListBase *self, GtkOrientation orientation, int cell_start, int cell_end, double current_align, - gboolean current_start, + GtkPackType current_side, double *new_align, - gboolean *new_start) + GtkPackType *new_side) { int visible_start, visible_size, visible_end; int cell_size; @@ -662,17 +709,17 @@ gtk_grid_view_compute_scroll_align (GtkGridView *self, if (cell_start < visible_start) { *new_align = 0.0; - *new_start = TRUE; + *new_side = GTK_PACK_START; } else if (cell_end > visible_end) { *new_align = 1.0; - *new_start = FALSE; + *new_side = GTK_PACK_END; } else { /* XXX: start or end here? */ - *new_start = TRUE; + *new_side = GTK_PACK_START; *new_align = (double) (cell_start - visible_start) / visible_size; } } @@ -682,25 +729,26 @@ gtk_grid_view_compute_scroll_align (GtkGridView *self, if (cell_start > visible_start) { *new_align = 0.0; - *new_start = TRUE; + *new_side = GTK_PACK_START; } else if (cell_end < visible_end) { *new_align = 1.0; - *new_start = FALSE; + *new_side = GTK_PACK_END; } else { /* the cell already covers the whole screen */ *new_align = current_align; - *new_start = current_start; + *new_side = current_side; } } } static void -gtk_grid_view_update_focus_tracker (GtkGridView *self) +gtk_list_base_update_focus_tracker (GtkListBase *self) { + GtkListBasePrivate *priv = gtk_list_base_get_instance_private (self); GtkWidget *focus_child; guint pos; @@ -709,25 +757,26 @@ gtk_grid_view_update_focus_tracker (GtkGridView *self) 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)) + if (pos != gtk_list_item_tracker_get_position (priv->item_manager, priv->focus)) { - gtk_list_item_tracker_set_position (self->item_manager, - self->focus, + gtk_list_item_tracker_set_position (priv->item_manager, + priv->focus, pos, - self->max_columns, - self->max_columns); + 0, + 0); } } static void -gtk_grid_view_scroll_to_item (GtkWidget *widget, +gtk_list_base_scroll_to_item (GtkWidget *widget, const char *action_name, GVariant *parameter) { - GtkGridView *self = GTK_GRID_VIEW (widget); + GtkListBase *self = GTK_LIST_BASE (widget); + GtkListBasePrivate *priv = gtk_list_base_get_instance_private (self); int start, end; - double xalign, yalign; - gboolean xstart, ystart; + double align_along, align_across; + GtkPackType side_along, side_across; guint pos; if (!g_variant_check_format_string (parameter, "u", FALSE)) @@ -736,26 +785,31 @@ gtk_grid_view_scroll_to_item (GtkWidget *widget, g_variant_get (parameter, "u", &pos); /* figure out primary orientation and if position is valid */ - if (!gtk_grid_view_get_size_at_position (self, pos, &start, &end)) + if (!gtk_list_base_get_allocation_along (GTK_LIST_BASE (self), pos, &start, &end)) return; end += start; - gtk_grid_view_compute_scroll_align (self, + gtk_list_base_compute_scroll_align (self, gtk_list_base_get_orientation (GTK_LIST_BASE (self)), start, end, - self->anchor_yalign, self->anchor_ystart, - &yalign, &ystart); + priv->anchor_align_along, priv->anchor_side_along, + &align_along, &side_along); /* 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, + 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, - self->anchor_xalign, self->anchor_xstart, - &xalign, &xstart); + priv->anchor_align_across, priv->anchor_side_across, + &align_across, &side_across); - gtk_grid_view_set_anchor (self, pos, xalign, xstart, yalign, ystart); + gtk_list_base_set_anchor (self, + pos, + align_across, side_across, + align_along, side_along); /* HACK HACK HACK * @@ -763,9 +817,70 @@ gtk_grid_view_scroll_to_item (GtkWidget *widget, * 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); + gtk_list_base_update_focus_tracker (self); +} + +static void +gtk_list_base_select_item_action (GtkWidget *widget, + const char *action_name, + GVariant *parameter) +{ + GtkListBase *self = GTK_LIST_BASE (widget); + guint pos; + gboolean modify, extend; + + g_variant_get (parameter, "(ubb)", &pos, &modify, &extend); + + gtk_list_base_select_item (self, pos, modify, extend); +} + +static void +gtk_list_base_select_all (GtkWidget *widget, + const char *action_name, + GVariant *parameter) +{ + GtkListBase *self = GTK_LIST_BASE (widget); + GtkListBasePrivate *priv = gtk_list_base_get_instance_private (self); + GtkSelectionModel *selection_model; + + selection_model = gtk_list_item_manager_get_model (priv->item_manager); + if (selection_model == NULL) + return; + + gtk_selection_model_select_all (selection_model); +} + +static void +gtk_list_base_unselect_all (GtkWidget *widget, + const char *action_name, + GVariant *parameter) +{ + GtkListBase *self = GTK_LIST_BASE (widget); + GtkListBasePrivate *priv = gtk_list_base_get_instance_private (self); + GtkSelectionModel *selection_model; + + selection_model = gtk_list_item_manager_get_model (priv->item_manager); + if (selection_model == NULL) + return; + + gtk_selection_model_unselect_all (selection_model); +} + +static void +gtk_list_base_move_cursor_to_start (GtkWidget *widget, + GVariant *args, + gpointer unused) +{ + GtkListBase *self = GTK_LIST_BASE (widget); + gboolean select, modify, extend; + + if (gtk_list_base_get_n_items (self) == 0) + return; + + g_variant_get (args, "(bbb)", &select, &modify, &extend); + + gtk_list_base_grab_focus_on_item (GTK_LIST_BASE (self), 0, select, modify, extend); } -#endif static void gtk_list_base_move_cursor_page_up (GtkWidget *widget, @@ -992,6 +1107,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 * @modify: %TRUE to toggle the existing selection, %FALSE to select @@ -1073,7 +1200,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); @@ -1083,41 +1214,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, @@ -1149,6 +1246,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) @@ -1175,6 +1327,108 @@ gtk_list_base_get_manager (GtkListBase *self) } /* + * 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 * @pos: position of the item to focus @@ -1234,3 +1488,46 @@ gtk_list_base_grab_focus_on_item (GtkListBase *self, return TRUE; } +GListModel * +gtk_list_base_get_model (GtkListBase *self) +{ + GtkListBasePrivate *priv = gtk_list_base_get_instance_private (self); + + return priv->model; +} + +gboolean +gtk_list_base_set_model (GtkListBase *self, + GListModel *model) +{ + GtkListBasePrivate *priv = gtk_list_base_get_instance_private (self); + + if (priv->model == model) + return FALSE; + + g_clear_object (&priv->model); + + if (model) + { + GtkSelectionModel *selection_model; + + priv->model = g_object_ref (model); + + if (GTK_IS_SELECTION_MODEL (model)) + selection_model = GTK_SELECTION_MODEL (g_object_ref (model)); + else + selection_model = GTK_SELECTION_MODEL (gtk_single_selection_new (model)); + + gtk_list_item_manager_set_model (priv->item_manager, selection_model); + gtk_list_base_set_anchor (self, 0, 0.0, GTK_PACK_START, 0.0, GTK_PACK_START); + + g_object_unref (selection_model); + } + else + { + gtk_list_item_manager_set_model (priv->item_manager, NULL); + } + + return TRUE; +} + diff --git a/gtk/gtklistbaseprivate.h b/gtk/gtklistbaseprivate.h index 8f3054acaa..fd4e8a2ea7 100644 --- a/gtk/gtklistbaseprivate.h +++ b/gtk/gtklistbaseprivate.h @@ -68,21 +68,27 @@ guint gtk_list_base_get_focus_position (GtkListBase GtkListItemManager * gtk_list_base_get_manager (GtkListBase *self); GtkScrollablePolicy gtk_list_base_get_scroll_policy (GtkListBase *self, GtkOrientation orientation); -gboolean gtk_list_base_get_allocation_along (GtkListBase *base, - guint pos, - int *offset, - int *size); -void gtk_list_base_get_adjustment_values (GtkListBase *self, - GtkOrientation orientation, - int *value, - int *size, - int *page_size); -int gtk_list_base_set_adjustment_values (GtkListBase *self, - GtkOrientation orientation, - int value, - int size, - int page_size); +guint gtk_list_base_get_n_items (GtkListBase *self); +GListModel * gtk_list_base_get_model (GtkListBase *self); +gboolean gtk_list_base_set_model (GtkListBase *self, + GListModel *model); +void gtk_list_base_update_adjustments (GtkListBase *self, + int total_across, + int total_along, + int page_across, + int page_along, + int *across, + int *along); +void gtk_list_base_set_anchor (GtkListBase *self, + guint anchor_pos, + double anchor_align_across, + GtkPackType anchor_side_across, + double anchor_align_along, + GtkPackType anchor_side_along); +void gtk_list_base_set_anchor_max_widgets (GtkListBase *self, + guint n_center, + guint n_above_below); void gtk_list_base_select_item (GtkListBase *self, guint pos, gboolean modify, diff --git a/gtk/gtklistview.c b/gtk/gtklistview.c index 69e81afbe7..fa104d9961 100644 --- a/gtk/gtklistview.c +++ b/gtk/gtklistview.c @@ -27,8 +27,6 @@ #include "gtkmain.h" #include "gtkprivate.h" #include "gtkrbtreeprivate.h" -#include "gtkselectionmodel.h" -#include "gtksingleselection.h" #include "gtkstylecontext.h" #include "gtkwidgetprivate.h" @@ -57,17 +55,10 @@ struct _GtkListView { GtkListBase parent_instance; - GListModel *model; GtkListItemManager *item_manager; gboolean show_separators; int list_width; - - GtkListItemTracker *anchor; - double anchor_align; - gboolean anchor_start; - /* the item that has input focus */ - GtkListItemTracker *focus; }; struct _GtkListViewClass @@ -195,37 +186,6 @@ gtk_list_view_get_row_at_y (GtkListView *self, } 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: @@ -843,127 +673,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, GVariant *parameter) @@ -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]); } |