[gnome-software/11-collapse-long-descriptions] gs-details-page: Collapse long descriptions
- From: Milan Crha <mcrha src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-software/11-collapse-long-descriptions] gs-details-page: Collapse long descriptions
- Date: Wed, 6 Jan 2021 08:37:47 +0000 (UTC)
commit 75c63a9c8552e8498a0a2d97d81bd376c3788ccc
Author: Milan Crha <mcrha redhat com>
Date: Wed Jan 6 09:37:13 2021 +0100
gs-details-page: Collapse long descriptions
Add a button to Read More/Read Less of the application description, starting
with the collapsed state.
Closes https://gitlab.gnome.org/GNOME/gnome-software/-/issues/11
src/gs-description-box.c | 269 +++++++++++++++++++++++++++++++++++++++++++++++
src/gs-description-box.h | 32 ++++++
src/gs-details-page.c | 49 +--------
src/gs-details-page.ui | 27 +++--
src/gtk-style-hc.css | 5 +
src/gtk-style.css | 5 +
src/meson.build | 1 +
7 files changed, 337 insertions(+), 51 deletions(-)
---
diff --git a/src/gs-description-box.c b/src/gs-description-box.c
new file mode 100644
index 00000000..f6569c1d
--- /dev/null
+++ b/src/gs-description-box.c
@@ -0,0 +1,269 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ * vi:set noexpandtab tabstop=8 shiftwidth=8:
+ *
+ * Copyright (C) 2020 Red Hat <www.redhat.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ */
+
+#include "config.h"
+
+#include <glib/gi18n.h>
+
+#include "gs-description-box.h"
+
+#define MAX_COLLAPSED_LINES 4
+#define THREE_DOTS_TEXT _("…")
+
+struct _GsDescriptionBox {
+ GtkBox parent;
+ GtkLabel *label;
+ GtkButton *button;
+ gchar *text;
+ gboolean is_collapsed;
+ gboolean needs_recalc;
+ gint last_width;
+ gint last_height;
+};
+
+G_DEFINE_TYPE (GsDescriptionBox, gs_description_box, GTK_TYPE_BOX)
+
+static void
+gs_description_box_update_content (GsDescriptionBox *box)
+{
+ GtkAllocation allocation;
+ PangoLayout *layout;
+ gint n_lines;
+
+ if (!box->text || !*(box->text)) {
+ gtk_widget_hide (GTK_WIDGET (box));
+ box->needs_recalc = TRUE;
+ return;
+ }
+
+ gtk_widget_get_allocation (GTK_WIDGET (box), &allocation);
+
+ if (!box->needs_recalc && box->last_width == allocation.width && box->last_height ==
allocation.height)
+ return;
+
+ box->needs_recalc = allocation.width <= 1 || allocation.height <= 1;
+ box->last_width = allocation.width;
+ box->last_height = allocation.height;
+
+ gtk_button_set_label (box->button, box->is_collapsed ? _("_Read More") : _("_Read Less"));
+
+ gtk_label_set_text (box->label, box->text);
+
+ layout = gtk_label_get_layout (box->label);
+ n_lines = pango_layout_get_line_count (layout);
+
+ gtk_widget_set_visible (GTK_WIDGET (box->button), n_lines > MAX_COLLAPSED_LINES);
+
+ if (box->is_collapsed && n_lines > MAX_COLLAPSED_LINES) {
+ PangoLayoutLine *line;
+
+ line = pango_layout_get_line_readonly (layout, MAX_COLLAPSED_LINES);
+
+ if (line) {
+ GString *str;
+
+ str = g_string_sized_new (line->start_index + strlen (THREE_DOTS_TEXT) + 1);
+ g_string_append_len (str, box->text, line->start_index);
+
+ /* Cut characters from the end of the string, thus it doesn't look bad with the added
dots. */
+ while (str->len > 0 && strchr ("\r\n\t .", str->str[str->len - 1])) {
+ str->len--;
+ }
+
+ g_string_append (str, THREE_DOTS_TEXT);
+
+ gtk_label_set_text (box->label, str->str);
+
+ g_string_free (str, TRUE);
+ }
+ }
+
+ gtk_widget_show (GTK_WIDGET (box));
+}
+
+static void
+gs_description_box_read_button_clicked_cb (GtkButton *button,
+ gpointer user_data)
+{
+ GsDescriptionBox *box = user_data;
+
+ g_return_if_fail (GS_IS_DESCRIPTION_BOX (box));
+
+ box->is_collapsed = !box->is_collapsed;
+ box->needs_recalc = TRUE;
+
+ gs_description_box_update_content (box);
+}
+
+static void
+gs_description_box_size_allocate (GtkWidget *widget,
+ GtkAllocation *allocation)
+{
+ GsDescriptionBox *box = GS_DESCRIPTION_BOX (widget);
+
+ GTK_WIDGET_CLASS (gs_description_box_parent_class)->size_allocate (widget, allocation);
+
+ gs_description_box_update_content (box);
+}
+
+static void
+gs_description_box_finalize (GObject *object)
+{
+ GsDescriptionBox *box = GS_DESCRIPTION_BOX (object);
+
+ g_clear_pointer (&box->text, g_free);
+
+ G_OBJECT_CLASS (gs_description_box_parent_class)->finalize (object);
+}
+
+static void
+gs_description_box_init (GsDescriptionBox *box)
+{
+ GtkStyleContext *style_context;
+ GtkWidget *widget;
+
+ box->is_collapsed = TRUE;
+
+ style_context = gtk_widget_get_style_context (GTK_WIDGET (box));
+ gtk_style_context_add_class (style_context, "application-details-description");
+
+ widget = gtk_label_new ("");
+ g_object_set (G_OBJECT (widget),
+ "hexpand", TRUE,
+ "halign", GTK_ALIGN_FILL,
+ "vexpand", FALSE,
+ "valign", GTK_ALIGN_START,
+ "visible", TRUE,
+ "can-focus", FALSE,
+ "max-width-chars", 40,
+ "width-chars", 40,
+ "selectable", TRUE,
+ "wrap", TRUE,
+ "xalign", 0.0,
+ NULL);
+
+ gtk_box_pack_start (GTK_BOX (box), widget, TRUE, TRUE, 0);
+
+ style_context = gtk_widget_get_style_context (widget);
+ gtk_style_context_add_class (style_context, "label");
+
+ box->label = GTK_LABEL (widget);
+
+ widget = gtk_button_new_with_mnemonic (_("_Read More"));
+
+ g_object_set (G_OBJECT (widget),
+ "hexpand", FALSE,
+ "halign", GTK_ALIGN_CENTER,
+ "vexpand", FALSE,
+ "valign", GTK_ALIGN_CENTER,
+ "visible", TRUE,
+ NULL);
+
+ gtk_box_pack_start (GTK_BOX (box), widget, FALSE, FALSE, 0);
+
+ style_context = gtk_widget_get_style_context (widget);
+ gtk_style_context_add_class (style_context, "button");
+ gtk_style_context_add_class (style_context, "circular");
+
+ box->button = GTK_BUTTON (widget);
+
+ g_signal_connect (box->button, "clicked",
+ G_CALLBACK (gs_description_box_read_button_clicked_cb), box);
+}
+
+static void
+gs_description_box_class_init (GsDescriptionBoxClass *klass)
+{
+ GObjectClass *object_class;
+ GtkWidgetClass *widget_class;
+
+ object_class = G_OBJECT_CLASS (klass);
+ object_class->finalize = gs_description_box_finalize;
+
+ widget_class = GTK_WIDGET_CLASS (klass);
+ widget_class->size_allocate = gs_description_box_size_allocate;
+}
+
+GtkWidget *
+gs_description_box_new (void)
+{
+ return g_object_new (GS_TYPE_DESCRIPTION_BOX,
+ "orientation", GTK_ORIENTATION_VERTICAL,
+ "spacing", 24,
+ NULL);
+}
+
+const gchar *
+gs_description_box_get_text (GsDescriptionBox *box)
+{
+ g_return_val_if_fail (GS_IS_DESCRIPTION_BOX (box), NULL);
+
+ return box->text;
+}
+
+void
+gs_description_box_set_text (GsDescriptionBox *box,
+ const gchar *text)
+{
+ g_return_if_fail (GS_IS_DESCRIPTION_BOX (box));
+
+ if (g_strcmp0 (text, box->text) != 0) {
+ g_free (box->text);
+ box->text = g_strdup (text);
+ box->needs_recalc = TRUE;
+
+ gs_description_box_update_content (box);
+ }
+}
+
+gboolean
+gs_description_box_get_collapsed (GsDescriptionBox *box)
+{
+ g_return_val_if_fail (GS_IS_DESCRIPTION_BOX (box), FALSE);
+
+ return box->is_collapsed;
+}
+
+void
+gs_description_box_set_collapsed (GsDescriptionBox *box,
+ gboolean collapsed)
+{
+ g_return_if_fail (GS_IS_DESCRIPTION_BOX (box));
+
+ if ((collapsed ? 1 : 0) != (box->is_collapsed ? 1 : 0)) {
+ box->is_collapsed = collapsed;
+ box->needs_recalc = TRUE;
+
+ gs_description_box_update_content (box);
+ }
+}
+
+void
+gs_description_box_reset (GsDescriptionBox *box,
+ const gchar *text,
+ gboolean collapsed)
+{
+ gboolean changed;
+
+ g_return_if_fail (GS_IS_DESCRIPTION_BOX (box));
+
+ changed = g_strcmp0 (text, box->text) != 0;
+
+ if (changed || (collapsed ? 1 : 0) != (box->is_collapsed ? 1 : 0)) {
+ box->is_collapsed = collapsed;
+
+ if (changed) {
+ g_free (box->text);
+ box->text = g_strdup (text);
+ }
+
+ box->needs_recalc = TRUE;
+
+ gs_description_box_update_content (box);
+ }
+}
diff --git a/src/gs-description-box.h b/src/gs-description-box.h
new file mode 100644
index 00000000..02e35455
--- /dev/null
+++ b/src/gs-description-box.h
@@ -0,0 +1,32 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ * vi:set noexpandtab tabstop=8 shiftwidth=8:
+ *
+ * Copyright (C) 2020 Red Hat <www.redhat.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ */
+
+#pragma once
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define GS_TYPE_DESCRIPTION_BOX (gs_description_box_get_type ())
+
+G_DECLARE_FINAL_TYPE (GsDescriptionBox, gs_description_box, GS, DESCRIPTION_BOX, GtkBox)
+
+GtkWidget *gs_description_box_new (void);
+const gchar *gs_description_box_get_text (GsDescriptionBox *box);
+void gs_description_box_set_text (GsDescriptionBox *box,
+ const gchar *text);
+gboolean gs_description_box_get_collapsed
+ (GsDescriptionBox *box);
+void gs_description_box_set_collapsed
+ (GsDescriptionBox *box,
+ gboolean collapsed);
+void gs_description_box_reset (GsDescriptionBox *box,
+ const gchar *text,
+ gboolean collapsed);
+
+G_END_DECLS
diff --git a/src/gs-details-page.c b/src/gs-details-page.c
index 7d5a1171..a75dcb2a 100644
--- a/src/gs-details-page.c
+++ b/src/gs-details-page.c
@@ -20,6 +20,7 @@
#include "gs-details-page.h"
#include "gs-app-addon-row.h"
+#include "gs-description-box.h"
#include "gs-history-dialog.h"
#include "gs-origin-popover-row.h"
#include "gs-screenshot-image.h"
@@ -62,6 +63,7 @@ struct _GsDetailsPage
GtkWidget *box_addons;
GtkWidget *box_details;
GtkWidget *box_details_description;
+ GtkWidget *label_webapp_warning;
GtkWidget *box_details_support;
GtkWidget *box_progress;
GtkWidget *box_progress2;
@@ -716,50 +718,8 @@ gs_details_page_donate_cb (GtkWidget *widget, GsDetailsPage *self)
static void
gs_details_page_set_description (GsDetailsPage *self, const gchar *tmp)
{
- GtkStyleContext *style_context;
- GtkWidget *para;
- guint i;
- g_auto(GStrv) split = NULL;
-
- /* does the description exist? */
- gtk_widget_set_visible (self->box_details_description, tmp != NULL);
- if (tmp == NULL)
- return;
-
- /* add each paragraph as a new GtkLabel which lets us get the 24px
- * paragraph spacing */
- gs_container_remove_all (GTK_CONTAINER (self->box_details_description));
- split = g_strsplit (tmp, "\n\n", -1);
- for (i = 0; split[i] != NULL; i++) {
- para = gtk_label_new (split[i]);
- gtk_label_set_line_wrap (GTK_LABEL (para), TRUE);
- gtk_label_set_max_width_chars (GTK_LABEL (para), 40);
- gtk_label_set_selectable (GTK_LABEL (para), TRUE);
- gtk_widget_set_visible (para, TRUE);
- gtk_widget_set_can_focus (para, FALSE);
- g_object_set (para,
- "xalign", 0.0,
- NULL);
-
- /* add style class for theming */
- style_context = gtk_widget_get_style_context (para);
- gtk_style_context_add_class (style_context,
- "application-details-description");
-
- gtk_container_add (GTK_CONTAINER (self->box_details_description), para);
- }
-
- /* show the webapp warning */
- if (gs_app_get_kind (self->app) == AS_APP_KIND_WEB_APP) {
- GtkWidget *label;
- /* TRANSLATORS: this is the warning box */
- label = gtk_label_new (_("This application can only be used when there is an active internet
connection."));
- gtk_widget_set_visible (label, TRUE);
- gtk_label_set_xalign (GTK_LABEL (label), 0.f);
- gtk_style_context_add_class (gtk_widget_get_style_context (label),
- "application-details-webapp-warning");
- gtk_container_add (GTK_CONTAINER (self->box_details_description), label);
- }
+ gs_description_box_reset (GS_DESCRIPTION_BOX (self->box_details_description), tmp, TRUE);
+ gtk_widget_set_visible (self->label_webapp_warning, gs_app_get_kind (self->app) ==
AS_APP_KIND_WEB_APP);
}
static void
@@ -2851,6 +2811,7 @@ gs_details_page_class_init (GsDetailsPageClass *klass)
gtk_widget_class_bind_template_child (widget_class, GsDetailsPage, box_addons);
gtk_widget_class_bind_template_child (widget_class, GsDetailsPage, box_details);
gtk_widget_class_bind_template_child (widget_class, GsDetailsPage, box_details_description);
+ gtk_widget_class_bind_template_child (widget_class, GsDetailsPage, label_webapp_warning);
gtk_widget_class_bind_template_child (widget_class, GsDetailsPage, box_details_support);
gtk_widget_class_bind_template_child (widget_class, GsDetailsPage, box_progress);
gtk_widget_class_bind_template_child (widget_class, GsDetailsPage, box_progress2);
diff --git a/src/gs-details-page.ui b/src/gs-details-page.ui
index 51c10a2d..f2d25d26 100644
--- a/src/gs-details-page.ui
+++ b/src/gs-details-page.ui
@@ -378,14 +378,27 @@
</object>
</child>
<child>
- <object class="GtkBox" id="box_details_description">
- <property name="visible">True</property>
- <property name="margin_bottom">14</property>
+ <object class="GsDescriptionBox" id="box_details_description">
<property name="orientation">vertical</property>
- <property name="spacing">18</property>
- <child>
- <placeholder/>
- </child>
+ <property name="spacing">12</property>
+ <property name="halign">fill</property>
+ <property name="hexpand">True</property>
+ <property name="valign">start</property>
+ <property name="visible">FALSE</property>
+ <property name="margin_bottom">14</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label_webapp_warning">
+ <property name="visible">False</property>
+ <property name="halign">center</property>
+ <property name="hexpand">True</property>
+ <property name="valign">start</property>
+ <property name="xalign">0.0</property>
+ <property name="label" translatable="yes">This application can only be used when
there is an active internet connection.</property>
+ <style>
+ <class name="application-details-webapp-warning"/>
+ </style>
</object>
</child>
<child>
diff --git a/src/gtk-style-hc.css b/src/gtk-style-hc.css
index c5696b80..81e399a5 100644
--- a/src/gtk-style-hc.css
+++ b/src/gtk-style-hc.css
@@ -118,6 +118,11 @@
100% { background-position: 0%; }
}
+.application-details-description .button {
+ padding-left:24px;
+ padding-right:24px;
+}
+
.install-progress {
background-image: linear-gradient(to top, @theme_selected_bg_color 2px,
alpha(@theme_selected_bg_color, 0) 2px);
background-repeat: no-repeat;
diff --git a/src/gtk-style.css b/src/gtk-style.css
index bf1f8ccc..74fdeadc 100644
--- a/src/gtk-style.css
+++ b/src/gtk-style.css
@@ -328,6 +328,11 @@
100% { background-position: 0%; }
}
+.application-details-description .button {
+ padding-left:24px;
+ padding-right:24px;
+}
+
.install-progress {
background-image: linear-gradient(to top, @theme_selected_bg_color 2px,
alpha(@theme_selected_bg_color, 0) 2px);
background-repeat: no-repeat;
diff --git a/src/meson.build b/src/meson.build
index 2f9ff234..2e6a2bd8 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -26,6 +26,7 @@ gnome_software_sources = [
'gs-common.c',
'gs-css.c',
'gs-content-rating.c',
+ 'gs-description-box.c',
'gs-details-page.c',
'gs-extras-page.c',
'gs-feature-tile.c',
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]