diff --git a/gtk/gtklistitemmanager.c b/gtk/gtklistitemmanager.c index d365137d18..cce8eda34d 100644 --- a/gtk/gtklistitemmanager.c +++ b/gtk/gtklistitemmanager.c @@ -204,6 +204,30 @@ gtk_list_item_manager_end_change (GtkListItemManager *self, g_slice_free (GtkListItemManagerChange, change); } +/* + * gtk_list_item_manager_change_contains: + * @change: a #GtkListItemManagerChange + * @list_item: The item that may have been released into this change set + * + * Checks if @list_item has been released as part of @change but not been + * reacquired yet. + * + * This is useful to test before calling gtk_list_item_manager_end_change() + * if special actions need to be performed when important list items - like + * the focused item - are about to be deleted. + * + * Returns: %TRUE if the item is part of this change + **/ +gboolean +gtk_list_item_manager_change_contains (GtkListItemManagerChange *change, + GtkWidget *list_item) +{ + g_return_val_if_fail (change != NULL, FALSE); + g_return_val_if_fail (GTK_IS_LIST_ITEM (list_item), FALSE); + + return g_hash_table_lookup (change->items, gtk_list_item_get_item (GTK_LIST_ITEM (list_item))) == list_item; +} + /* * gtk_list_item_manager_acquire_list_item: * @self: a #GtkListItemManager diff --git a/gtk/gtklistitemmanagerprivate.h b/gtk/gtklistitemmanagerprivate.h index d1cdfaf819..cf7721bc6c 100644 --- a/gtk/gtklistitemmanagerprivate.h +++ b/gtk/gtklistitemmanagerprivate.h @@ -55,6 +55,9 @@ GtkListItemManagerChange * gtk_list_item_manager_begin_change (GtkListItemManager *self); void gtk_list_item_manager_end_change (GtkListItemManager *self, GtkListItemManagerChange *change); +gboolean gtk_list_item_manager_change_contains (GtkListItemManagerChange *change, + GtkWidget *list_item); + GtkWidget * gtk_list_item_manager_acquire_list_item (GtkListItemManager *self, guint position, GtkWidget *next_sibling); diff --git a/gtk/gtklistview.c b/gtk/gtklistview.c index 36e710ab18..35866d47b7 100644 --- a/gtk/gtklistview.c +++ b/gtk/gtklistview.c @@ -54,10 +54,8 @@ struct _GtkListView int list_width; /* managing the visible region */ - guint anchor_pos; - int anchor_dy; - guint first_visible_pos; - guint lasst_visible_pos; + GtkWidget *anchor; + int anchor_align; }; struct _ListRow @@ -284,6 +282,54 @@ gtk_list_view_get_list_height (GtkListView *self) return aug->height; } +static void +gtk_list_view_set_anchor (GtkListView *self, + guint position, + double align) +{ + ListRow *row; + + g_assert (align >= 0.0 && align <= 1.0); + + row = gtk_list_view_get_row (self, position, NULL); + if (row == NULL) + { + /* like, if the list is empty */ + self->anchor = NULL; + self->anchor_align = 0.0; + return; + } + + self->anchor = row->widget; + self->anchor_align = align; + + gtk_widget_queue_allocate (GTK_WIDGET (self)); +} + +static void +gtk_list_view_adjustment_value_changed_cb (GtkAdjustment *adjustment, + GtkListView *self) +{ + if (adjustment == self->adjustment[GTK_ORIENTATION_VERTICAL]) + { + ListRow *row; + guint pos; + int dy; + + row = gtk_list_view_get_row_at_y (self, gtk_adjustment_get_value (adjustment), &dy); + if (row) + pos = list_row_get_position (self, row); + else + pos = 0; + + gtk_list_view_set_anchor (self, pos, 0); + } + else + { + gtk_widget_queue_allocate (GTK_WIDGET (self)); + } +} + static void gtk_list_view_update_adjustments (GtkListView *self, GtkOrientation orientation) @@ -306,15 +352,21 @@ gtk_list_view_update_adjustments (GtkListView *self, page_size = gtk_widget_get_height (GTK_WIDGET (self)); upper = gtk_list_view_get_list_height (self); - row = gtk_list_view_get_row (self, self->anchor_pos, NULL); + if (self->anchor) + row = gtk_list_view_get_row (self, gtk_list_item_get_position (GTK_LIST_ITEM (self->anchor)), NULL); + else + row = NULL; if (row) value = list_row_get_y (self, row); else value = 0; - value += self->anchor_dy; + value -= self->anchor_align * (page_size - (row ? row->height : 0)); } upper = MAX (upper, page_size); + g_signal_handlers_block_by_func (self->adjustment[orientation], + gtk_list_view_adjustment_value_changed_cb, + self); gtk_adjustment_configure (self->adjustment[orientation], value, 0, @@ -322,6 +374,9 @@ gtk_list_view_update_adjustments (GtkListView *self, page_size * 0.1, page_size * 0.9, page_size); + g_signal_handlers_unblock_by_func (self->adjustment[orientation], + gtk_list_view_adjustment_value_changed_cb, + self); } static void @@ -413,7 +468,7 @@ gtk_list_view_size_allocate (GtkWidget *widget, /* step 4: actually allocate the widgets */ child_allocation.x = - gtk_adjustment_get_value (self->adjustment[GTK_ORIENTATION_HORIZONTAL]); - child_allocation.y = - gtk_adjustment_get_value (self->adjustment[GTK_ORIENTATION_VERTICAL]); + child_allocation.y = - round (gtk_adjustment_get_value (self->adjustment[GTK_ORIENTATION_VERTICAL])); child_allocation.width = self->list_width; for (row = gtk_rb_tree_get_first (self->rows); row != NULL; @@ -425,6 +480,23 @@ gtk_list_view_size_allocate (GtkWidget *widget, } } +static guint +gtk_list_view_get_anchor (GtkListView *self, + double *align) +{ + guint anchor_pos; + + if (self->anchor) + anchor_pos = gtk_list_item_get_position (GTK_LIST_ITEM (self->anchor)); + else + anchor_pos = 0; + + if (align) + *align = self->anchor_align; + + return anchor_pos; +} + static void gtk_list_view_remove_rows (GtkListView *self, GtkListItemManagerChange *change, @@ -432,24 +504,11 @@ gtk_list_view_remove_rows (GtkListView *self, guint n_rows) { ListRow *row; - guint i, n_remaining; + guint i; if (n_rows == 0) return; - n_remaining = self->model ? g_list_model_get_n_items (self->model) : 0; - if (self->anchor_pos >= position + n_rows) - { - self->anchor_pos -= n_rows; - } - else if (self->anchor_pos >= position) - { - self->anchor_pos = position; - if (self->anchor_pos > 0 && self->anchor_pos >= n_remaining) - self->anchor_pos = n_remaining - 1; - self->anchor_dy = 0; - } - row = gtk_list_view_get_row (self, position, NULL); for (i = 0; i < n_rows; i++) @@ -471,18 +530,11 @@ gtk_list_view_add_rows (GtkListView *self, guint n_rows) { ListRow *row; - guint i, n_total; + guint i; if (n_rows == 0) return; - n_total = self->model ? g_list_model_get_n_items (self->model) : 0; - if (self->anchor_pos >= position) - { - if (n_total != n_rows) /* the model was not empty before */ - self->anchor_pos += n_rows; - } - row = gtk_list_view_get_row (self, position, NULL); for (i = 0; i < n_rows; i++) @@ -541,6 +593,15 @@ gtk_list_view_model_items_changed_cb (GListModel *model, if (removed != added) gtk_list_view_update_rows (self, position + added); + if (gtk_list_item_manager_change_contains (change, self->anchor)) + { + guint anchor_pos = gtk_list_item_get_position (GTK_LIST_ITEM (self->anchor)); + + /* removed cannot be NULL or the anchor wouldn't have been removed */ + anchor_pos = position + (anchor_pos - position) * added / removed; + gtk_list_view_set_anchor (self, anchor_pos, self->anchor_align); + } + gtk_list_item_manager_end_change (self->item_manager, change); } @@ -558,35 +619,6 @@ gtk_list_view_clear_model (GtkListView *self) g_clear_object (&self->model); } -static void -gtk_list_view_adjustment_value_changed_cb (GtkAdjustment *adjustment, - GtkListView *self) -{ - if (adjustment == self->adjustment[GTK_ORIENTATION_VERTICAL]) - { - ListRow *row; - guint pos; - int dy; - - row = gtk_list_view_get_row_at_y (self, gtk_adjustment_get_value (adjustment), &dy); - if (row) - pos = list_row_get_position (self, row); - else - pos = 0; - - if (pos != self->anchor_pos || dy != self->anchor_dy) - { - self->anchor_pos = pos; - self->anchor_dy = dy; - gtk_widget_queue_allocate (GTK_WIDGET (self)); - } - } - else - { - gtk_widget_queue_allocate (GTK_WIDGET (self)); - } -} - static void gtk_list_view_clear_adjustment (GtkListView *self, GtkOrientation orientation) @@ -866,6 +898,7 @@ gtk_list_view_set_model (GtkListView *self, self); gtk_list_view_add_rows (self, NULL, 0, g_list_model_get_n_items (model)); + gtk_list_view_set_anchor (self, 0, 0); } g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_MODEL]); @@ -879,13 +912,15 @@ gtk_list_view_set_functions (GtkListView *self, GDestroyNotify user_destroy) { GtkListItemFactory *factory; - guint n_items; + guint n_items, anchor; + double anchor_align; g_return_if_fail (GTK_IS_LIST_VIEW (self)); g_return_if_fail (setup_func || bind_func); g_return_if_fail (user_data != NULL || user_destroy == NULL); n_items = self->model ? g_list_model_get_n_items (self->model) : 0; + anchor = gtk_list_view_get_anchor (self, &anchor_align); gtk_list_view_remove_rows (self, NULL, 0, n_items); factory = gtk_list_item_factory_new (setup_func, bind_func, user_data, user_destroy); @@ -893,5 +928,6 @@ gtk_list_view_set_functions (GtkListView *self, g_object_unref (factory); gtk_list_view_add_rows (self, NULL, 0, n_items); + gtk_list_view_set_anchor (self, anchor, anchor_align); }