app: display the release notes as a GtkListBox instead of text.

What it means is that we will be a bit strict over our <release>
formatting which will have to always be a <p> introduction followed by a
list of items. This is what gimp_appstream_to_pango_markups() expects.

Since so far, this is how all our <release> tags were formatted anyway,
this is not too much of a problem.

Note that I keep the less strict gimp_appstream_to_pango_markup() and
use it for extension's appstream description as we will have no control
over these.

The main reason for this new rule and new display of our release notes
is that I am going to add the ability to click independent release items
so that people can get "blinking" indications of what changed when
relevant.
This commit is contained in:
Jehan 2022-03-04 21:26:05 +01:00
parent 83a3130688
commit 3011795407
3 changed files with 146 additions and 62 deletions

View File

@ -75,9 +75,15 @@ typedef struct
const gchar *lang;
GString *original;
gint foreign_level;
gchar **introduction;
GList **release_items;
} ParseState;
static gchar * gimp_appstream_parse (const gchar *as_text,
gchar **introduction,
GList **release_items);
static void appstream_text_start_element (GMarkupParseContext *context,
const gchar *element_name,
const gchar **attribute_names,
@ -955,49 +961,19 @@ gimp_ascii_strtod (const gchar *nptr,
gchar *
gimp_appstream_to_pango_markup (const gchar *as_text)
{
static const GMarkupParser appstream_text_parser =
{
appstream_text_start_element,
appstream_text_end_element,
appstream_text_characters,
NULL, /* passthrough */
NULL /* error */
};
GimpXmlParser *xml_parser;
gchar *markup = NULL;
GError *error = NULL;
ParseState state;
state.level = 0;
state.foreign_level = -1;
state.text = g_string_new (NULL);
state.numbered_list = FALSE;
state.unnumbered_list = FALSE;
state.lang = g_getenv ("LANGUAGE");
state.original = NULL;
xml_parser = gimp_xml_parser_new (&appstream_text_parser, &state);
if (as_text &&
! gimp_xml_parser_parse_buffer (xml_parser, as_text, -1, &error))
{
g_printerr ("%s: %s\n", G_STRFUNC, error->message);
g_error_free (error);
}
/* Append possibly last original text without proper localization. */
if (state.original)
{
g_string_append (state.text, state.original->str);
g_string_free (state.original, TRUE);
}
markup = g_string_free (state.text, FALSE);
gimp_xml_parser_free (xml_parser);
return markup;
return gimp_appstream_parse (as_text, NULL, NULL);
}
void
gimp_appstream_to_pango_markups (const gchar *as_text,
gchar **introduction,
GList **release_items)
{
gchar * markup;
markup = gimp_appstream_parse (as_text, introduction, release_items);
g_free (markup);
}
gint
gimp_g_list_compare (GList *list1,
@ -1308,6 +1284,61 @@ gimp_create_image_from_buffer (Gimp *gimp,
/* Private functions */
static gchar *
gimp_appstream_parse (const gchar *as_text,
gchar **introduction,
GList **release_items)
{
static const GMarkupParser appstream_text_parser =
{
appstream_text_start_element,
appstream_text_end_element,
appstream_text_characters,
NULL, /* passthrough */
NULL /* error */
};
GimpXmlParser *xml_parser;
gchar *markup = NULL;
GError *error = NULL;
ParseState state;
state.level = 0;
state.foreign_level = -1;
state.text = g_string_new (NULL);
state.list_num = 0;
state.numbered_list = FALSE;
state.unnumbered_list = FALSE;
state.lang = g_getenv ("LANGUAGE");
state.original = NULL;
state.introduction = introduction;
state.release_items = release_items;
xml_parser = gimp_xml_parser_new (&appstream_text_parser, &state);
if (as_text &&
! gimp_xml_parser_parse_buffer (xml_parser, as_text, -1, &error))
{
g_printerr ("%s: %s\n", G_STRFUNC, error->message);
g_error_free (error);
}
/* Append possibly last original text without proper localization. */
if (state.original)
{
g_string_append (state.text, state.original->str);
g_string_free (state.original, TRUE);
}
if (release_items)
*release_items = g_list_reverse (*release_items);
markup = g_string_free (state.text, FALSE);
gimp_xml_parser_free (xml_parser);
return markup;
}
static void
appstream_text_start_element (GMarkupParseContext *context,
const gchar *element_name,
@ -1367,6 +1398,7 @@ appstream_text_start_element (GMarkupParseContext *context,
else if (g_strcmp0 (element_name, "ul") == 0)
{
state->unnumbered_list = TRUE;
state->list_num = 0;
}
else if (g_strcmp0 (element_name, "ol") == 0)
{
@ -1407,6 +1439,10 @@ appstream_text_end_element (GMarkupParseContext *context,
{
if (state->foreign_level < 0)
{
if (state->introduction &&
*state->introduction == NULL)
*state->introduction = g_strdup (state->original ? state->original->str : state->text->str);
if (state->original)
g_string_append (state->original, "\n\n");
else
@ -1440,8 +1476,24 @@ appstream_text_characters (GMarkupParseContext *context,
{
ParseState *state = user_data;
if (state->foreign_level < 0)
if (state->foreign_level < 0 && text_len > 0)
{
if (state->list_num > 0 && state->release_items)
{
GList **items = state->release_items;
if (state->list_num == g_list_length (*(state->release_items)))
{
gchar *tmp = (*items)->data;
(*items)->data = g_strconcat (tmp, text, NULL);
g_free (tmp);
}
else
{
*items = g_list_prepend (*items, g_strdup (text));
}
}
if (state->original)
g_string_append (state->original, text);
else

View File

@ -102,6 +102,9 @@ gboolean gimp_ascii_strtod (const gchar *nptr,
gdouble *result);
gchar * gimp_appstream_to_pango_markup (const gchar *as_text);
void gimp_appstream_to_pango_markups (const gchar *as_text,
gchar **introduction,
GList **release_items);
gint gimp_g_list_compare (GList *list1,
GList *list2);

View File

@ -74,15 +74,15 @@ welcome_dialog_create (Gimp *gimp)
GtkWidget *vbox;
GtkWidget *hbox;
GtkWidget *image;
GtkWidget *listbox;
GtkWidget *widget;
GtkTextBuffer *buffer;
GtkTextIter iter;
gchar *release_link;
gchar *appdata_path;
gchar *title;
gchar *markup;
gchar *release_introduction = NULL;
GList *release_items = NULL;
gchar *tmp;
gint row;
@ -344,26 +344,55 @@ welcome_dialog_create (Gimp *gimp)
/* Release note contents. */
scrolled_window = gtk_scrolled_window_new (NULL, NULL);
gtk_box_pack_start (GTK_BOX (vbox), scrolled_window, TRUE, TRUE, 0);
gtk_widget_show (scrolled_window);
gimp_appstream_to_pango_markups (release_notes,
&release_introduction,
&release_items);
if (release_introduction)
{
widget = gtk_label_new (NULL);
gtk_label_set_markup (GTK_LABEL (widget), release_introduction);
gtk_label_set_max_width_chars (GTK_LABEL (widget), 70);
gtk_label_set_selectable (GTK_LABEL (widget), FALSE);
gtk_label_set_justify (GTK_LABEL (widget), GTK_JUSTIFY_LEFT);
gtk_label_set_line_wrap (GTK_LABEL (widget), TRUE);
gtk_box_pack_start (GTK_BOX (vbox), widget, FALSE, FALSE, 0);
gtk_widget_show (widget);
widget = gtk_text_view_new ();
gtk_widget_set_vexpand (widget, TRUE);
gtk_widget_set_hexpand (widget, TRUE);
gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (widget), GTK_WRAP_WORD_CHAR);
gtk_text_view_set_editable (GTK_TEXT_VIEW (widget), FALSE);
gtk_text_view_set_cursor_visible (GTK_TEXT_VIEW (widget), FALSE);
gtk_text_view_set_justification (GTK_TEXT_VIEW (widget), GTK_JUSTIFY_LEFT);
buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (widget));
gtk_text_buffer_get_start_iter (buffer, &iter);
g_free (release_introduction);
}
markup = gimp_appstream_to_pango_markup (release_notes);
gtk_text_buffer_insert_markup (buffer, &iter, markup, -1);
g_free (markup);
if (release_items)
{
GList *item;
gtk_container_add (GTK_CONTAINER (scrolled_window), widget);
gtk_widget_show (widget);
scrolled_window = gtk_scrolled_window_new (NULL, NULL);
gtk_box_pack_start (GTK_BOX (vbox), scrolled_window, TRUE, TRUE, 0);
gtk_widget_show (scrolled_window);
listbox = gtk_list_box_new ();
for (item = release_items; item; item = item->next)
{
GtkWidget *row;
row = gtk_list_box_row_new ();
widget = gtk_label_new (NULL);
gtk_label_set_markup (GTK_LABEL (widget), item->data);
gtk_label_set_line_wrap (GTK_LABEL (widget), TRUE);
gtk_label_set_line_wrap_mode (GTK_LABEL (widget), PANGO_WRAP_WORD);
gtk_label_set_justify (GTK_LABEL (widget), GTK_JUSTIFY_LEFT);
gtk_widget_set_halign (widget, GTK_ALIGN_START);
gtk_label_set_xalign (GTK_LABEL (widget), 0.0);
gtk_container_add (GTK_CONTAINER (row), widget);
gtk_list_box_insert (GTK_LIST_BOX (listbox), row, -1);
gtk_widget_show_all (row);
}
gtk_container_add (GTK_CONTAINER (scrolled_window), listbox);
gtk_widget_show (listbox);
g_list_free_full (release_items, g_free);
}
hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0);