From 2bc9b66830e8695e29f1f74297bc5f4fea696076 Mon Sep 17 00:00:00 2001 From: Milan Crha Date: Thu, 10 Nov 2022 15:46:40 +0100 Subject: [PATCH 1/3] gs-app-reviews-dialog: Avoid listbox rows removal when possible The GtkListBox tries very hard to keep cursor (focused) row in the view, and whenever any row is removed, it changes the position of the vertical scrollbar to reflect the change. Better to merge review changes into existing rows also to keep preserved the cursor row, because removing all rows and adding there back new rows with the same reviews does lost the cursor row, not only the vertical scrollbar position. Closes https://gitlab.gnome.org/GNOME/gnome-software/-/issues/1776 --- src/gs-app-reviews-dialog.c | 52 ++++++++++++++++++++++++++++++++----- 1 file changed, 45 insertions(+), 7 deletions(-) diff --git a/src/gs-app-reviews-dialog.c b/src/gs-app-reviews-dialog.c index e02ab0aa8..4bc41c406 100644 --- a/src/gs-app-reviews-dialog.c +++ b/src/gs-app-reviews-dialog.c @@ -100,10 +100,26 @@ review_button_clicked_cb (GsReviewRow *row, refresh_reviews (self); } +static GSList * /* (transfer container) */ +gather_listbox_rows (GtkWidget *listbox) +{ + GSList *rows = NULL; + GtkWidget *widget; + + widget = gtk_widget_get_first_child (listbox); + while (widget) { + rows = g_slist_prepend (rows, widget); + widget = gtk_widget_get_next_sibling (widget); + } + + return g_slist_reverse (rows); +} + static void populate_reviews (GsAppReviewsDialog *self) { GPtrArray *reviews; + GSList *rows, *link; gboolean show_reviews = FALSE; guint64 possible_actions = 0; guint i; @@ -158,18 +174,32 @@ populate_reviews (GsAppReviewsDialog *self) } /* add all the reviews */ - gs_widget_remove_all (self->listbox, (GsRemoveFunc) gtk_list_box_remove); + rows = gather_listbox_rows (self->listbox); g_ptr_array_sort (reviews, (GCompareFunc) sort_reviews); - for (i = 0; i < reviews->len; i++) { + for (i = 0, link = rows; i < reviews->len; i++, link = g_slist_next (link)) { AsReview *review = g_ptr_array_index (reviews, i); - GtkWidget *row = gs_review_row_new (review); + GtkWidget *row = NULL; guint64 actions; - gtk_list_box_row_set_activatable (GTK_LIST_BOX_ROW (row), FALSE); - gtk_list_box_append (GTK_LIST_BOX (self->listbox), row); + /* Try to merge with existing rows, to preserve cursor (focused) row + and window scroll position. */ + if (link != NULL) { + GtkWidget *existing_row = link->data; + if (gs_review_row_get_review (GS_REVIEW_ROW (existing_row)) == review) + row = existing_row; + else + gtk_list_box_remove (GTK_LIST_BOX (self->listbox), existing_row); + } + + if (row == NULL) { + row = gs_review_row_new (review); + gtk_list_box_row_set_activatable (GTK_LIST_BOX_ROW (row), FALSE); + gtk_list_box_append (GTK_LIST_BOX (self->listbox), row); + + g_signal_connect (row, "button-clicked", + G_CALLBACK (review_button_clicked_cb), self); + } - g_signal_connect (row, "button-clicked", - G_CALLBACK (review_button_clicked_cb), self); if (as_review_get_flags (review) & AS_REVIEW_FLAG_SELF) actions = possible_actions & (1 << GS_REVIEW_ACTION_REMOVE); else @@ -179,6 +209,14 @@ populate_reviews (GsAppReviewsDialog *self) GS_IS_PLUGIN_LOADER (self->plugin_loader) && gs_plugin_loader_get_network_available (self->plugin_loader)); } + while (link != NULL) { + GtkWidget *existing_row = link->data; + gtk_list_box_remove (GTK_LIST_BOX (self->listbox), existing_row); + link = g_slist_next (link); + } + + g_slist_free (rows); + gtk_stack_set_visible_child_name (GTK_STACK (self->stack), "reviews"); } -- GitLab From 1a77b60ea6aafa8e8a3e9899a0e28da83a7ae168 Mon Sep 17 00:00:00 2001 From: Milan Crha Date: Wed, 16 Nov 2022 17:31:20 +0100 Subject: [PATCH 2/3] gs-review-row: Expose refresh() function into the public API This will be used in the following commit. --- src/gs-review-row.c | 7 +++++-- src/gs-review-row.h | 1 + 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/gs-review-row.c b/src/gs-review-row.c index 9afb4849b..bc5aa89b0 100644 --- a/src/gs-review-row.c +++ b/src/gs-review-row.c @@ -39,8 +39,11 @@ enum { static guint signals [SIGNAL_LAST] = { 0 }; G_DEFINE_TYPE_WITH_PRIVATE (GsReviewRow, gs_review_row, GTK_TYPE_LIST_BOX_ROW) - -static void +/* FIXME: This is currently public to allow #GsAppReviewsDialog to refresh rows. + * That should no longer be needed once #AsReview emits #GObject::notify correctly, + * as then property bindings can be used internally. + * See https://github.com/ximion/appstream/pull/448 */ +void gs_review_row_refresh (GsReviewRow *row) { GsReviewRowPrivate *priv = gs_review_row_get_instance_private (row); diff --git a/src/gs-review-row.h b/src/gs-review-row.h index 48139ce96..4cb20188e 100644 --- a/src/gs-review-row.h +++ b/src/gs-review-row.h @@ -52,5 +52,6 @@ void gs_review_row_set_actions (GsReviewRow *review_row, guint64 actions); void gs_review_row_set_network_available (GsReviewRow *review_row, gboolean network_available); +void gs_review_row_refresh (GsReviewRow *row); G_END_DECLS -- GitLab From be4b111aa96739d64e41cce89f9c3022acb7fcf1 Mon Sep 17 00:00:00 2001 From: Milan Crha Date: Wed, 16 Nov 2022 17:37:23 +0100 Subject: [PATCH 3/3] gs-app-reviews-dialog: Refresh only the row after vote on the review No need to remove all rows and then re-add them just because a state of a single review changed, thus update only the row which changed instead. Related to https://gitlab.gnome.org/GNOME/gnome-software/-/issues/1776 --- src/gs-app-reviews-dialog.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gs-app-reviews-dialog.c b/src/gs-app-reviews-dialog.c index 4bc41c406..1578e8009 100644 --- a/src/gs-app-reviews-dialog.c +++ b/src/gs-app-reviews-dialog.c @@ -97,7 +97,7 @@ review_button_clicked_cb (GsReviewRow *row, return; } - refresh_reviews (self); + gs_review_row_refresh (row); } static GSList * /* (transfer container) */ -- GitLab