[gnome-software: 5/10] gs-app-translation-dialog: Add a new dialog to highlight app translation




commit 8484fd1f42d3810c5dc179c4d53e0bb000f946c2
Author: Philip Withnall <pwithnall endlessos org>
Date:   Tue Aug 10 15:23:46 2021 +0100

    gs-app-translation-dialog: Add a new dialog to highlight app translation
    
    This new dialogue is shown if an app isn’t translated to the user’s
    language, but could be, to guide the user towards potentially
    contributing to the translation themselves.
    
    Signed-off-by: Philip Withnall <pwithnall endlessos org>
    
    Helps: #1354

 po/POTFILES.in                   |   2 +
 src/gnome-software.gresource.xml |   1 +
 src/gs-app-translation-dialog.c  | 306 +++++++++++++++++++++++++++++++++++++++
 src/gs-app-translation-dialog.h  |  31 ++++
 src/gs-app-translation-dialog.ui | 156 ++++++++++++++++++++
 src/gtk-style-hc.css             |   2 +-
 src/gtk-style.css                |   2 +-
 src/meson.build                  |   1 +
 8 files changed, 499 insertions(+), 2 deletions(-)
---
diff --git a/po/POTFILES.in b/po/POTFILES.in
index eb8fc7c3c..b6a820d00 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -20,6 +20,8 @@ src/gs-app-row.c
 src/gs-app-row.ui
 src/gs-app-tile.c
 src/gs-app-tile.ui
+src/gs-app-translation-dialog.c
+src/gs-app-translation-dialog.ui
 src/gs-basic-auth-dialog.c
 src/gs-basic-auth-dialog.ui
 lib/gs-category.c
diff --git a/src/gnome-software.gresource.xml b/src/gnome-software.gresource.xml
index f129ed305..0e26ed067 100644
--- a/src/gnome-software.gresource.xml
+++ b/src/gnome-software.gresource.xml
@@ -8,6 +8,7 @@
   <file preprocess="xml-stripblanks">gs-app-version-history-dialog.ui</file>
   <file preprocess="xml-stripblanks">gs-app-version-history-row.ui</file>
   <file preprocess="xml-stripblanks">gs-app-row.ui</file>
+  <file preprocess="xml-stripblanks">gs-app-translation-dialog.ui</file>
   <file preprocess="xml-stripblanks">gs-basic-auth-dialog.ui</file>
   <file preprocess="xml-stripblanks">gs-category-page.ui</file>
   <file preprocess="xml-stripblanks">gs-category-tile.ui</file>
diff --git a/src/gs-app-translation-dialog.c b/src/gs-app-translation-dialog.c
new file mode 100644
index 000000000..d57a63c29
--- /dev/null
+++ b/src/gs-app-translation-dialog.c
@@ -0,0 +1,306 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ * vi:set noexpandtab tabstop=8 shiftwidth=8:
+ *
+ * Copyright (C) 2021 Endless OS Foundation LLC
+ *
+ * Author: Philip Withnall <pwithnall endlessos org>
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ */
+
+/**
+ * SECTION:gs-app-translation-dialog
+ * @short_description: A dialog showing translation information about an app
+ *
+ * #GsAppTranslationDialog is a dialog which shows a message about the
+ * translation status of an app, and provides information and a link for how
+ * to contribute more translations to the app.
+ *
+ * It is intended to be shown if the app is not sufficiently translated to the
+ * current locale.
+ *
+ * The widget has no special appearance if the app is unset, so callers will
+ * typically want to hide the dialog in that case.
+ *
+ * Since: 41
+ */
+
+#include "config.h"
+
+#include <glib.h>
+#include <glib-object.h>
+#include <glib/gi18n.h>
+#include <gtk/gtk.h>
+#include <handy.h>
+#include <locale.h>
+
+#include "gs-app.h"
+#include "gs-app-translation-dialog.h"
+#include "gs-common.h"
+
+struct _GsAppTranslationDialog
+{
+       HdyWindow                parent_instance;
+
+       GsApp                   *app;  /* (not nullable) (owned) */
+       gulong                   app_notify_name_handler;
+
+       GtkLabel                *title;
+       GtkLabel                *description;
+};
+
+G_DEFINE_TYPE (GsAppTranslationDialog, gs_app_translation_dialog, HDY_TYPE_WINDOW)
+
+typedef enum {
+       PROP_APP = 1,
+} GsAppTranslationDialogProperty;
+
+static GParamSpec *obj_props[PROP_APP + 1] = { NULL, };
+
+static void
+update_labels (GsAppTranslationDialog *self)
+{
+       g_autofree gchar *title = NULL;
+       g_autofree gchar *description = NULL;
+
+       /* Translators: The placeholder is an application name */
+       title = g_strdup_printf (_("Help Translate %s"), gs_app_get_name (self->app));
+
+       /* Translators: The placeholder is an application name */
+       description = g_strdup_printf (_("%s is designed, developed, and translated by an "
+                                        "international community of volunteers."
+                                        "\n\n"
+                                        "This means that while it’s not yet available in "
+                                        "your language, you can get involved and help "
+                                        "translate it yourself."), gs_app_get_name (self->app));
+
+       gtk_label_set_text (self->title, title);
+       gtk_label_set_text (self->description, description);
+}
+
+static void
+app_notify_cb (GObject    *obj,
+               GParamSpec *pspec,
+               gpointer    user_data)
+{
+       GsAppTranslationDialog *self = GS_APP_TRANSLATION_DIALOG (user_data);
+
+       update_labels (self);
+}
+
+static gboolean
+key_press_event_cb (GtkWidget            *sender,
+                    GdkEvent             *event,
+                    HdyPreferencesWindow *self)
+{
+       guint keyval;
+       GdkModifierType state;
+       GdkKeymap *keymap;
+       GdkEventKey *key_event = (GdkEventKey *) event;
+
+       gdk_event_get_state (event, &state);
+
+       keymap = gdk_keymap_get_for_display (gtk_widget_get_display (sender));
+
+       gdk_keymap_translate_keyboard_state (keymap,
+                                            key_event->hardware_keycode,
+                                            state,
+                                            key_event->group,
+                                            &keyval, NULL, NULL, NULL);
+
+       if (keyval == GDK_KEY_Escape) {
+               gtk_window_close (GTK_WINDOW (self));
+
+               return GDK_EVENT_STOP;
+       }
+
+       return GDK_EVENT_PROPAGATE;
+}
+
+static const gchar *
+get_url_for_app (GsApp *app)
+{
+       const gchar *url;
+
+       /* Try the translate URL, or a fallback */
+       url = gs_app_get_url (app, AS_URL_KIND_TRANSLATE);
+       if (url == NULL)
+               url = gs_app_get_url (app, AS_URL_KIND_BUGTRACKER);
+
+       return url;
+}
+
+static void
+button_clicked_cb (GtkButton *button,
+                   gpointer   user_data)
+{
+       GsAppTranslationDialog *self = GS_APP_TRANSLATION_DIALOG (user_data);
+       g_autoptr(GError) error = NULL;
+       const gchar *url;
+
+       url = get_url_for_app (self->app);
+
+       if (!gtk_show_uri_on_window (GTK_WINDOW (self),
+                                    url,
+                                    GDK_CURRENT_TIME,
+                                    &error)) {
+               g_warning ("failed to show URI %s: %s",
+                          url, error->message);
+       }
+}
+
+static void
+gs_app_translation_dialog_init (GsAppTranslationDialog *self)
+{
+       gtk_widget_init_template (GTK_WIDGET (self));
+}
+
+static void
+gs_app_translation_dialog_get_property (GObject    *object,
+                                        guint       prop_id,
+                                        GValue     *value,
+                                        GParamSpec *pspec)
+{
+       GsAppTranslationDialog *self = GS_APP_TRANSLATION_DIALOG (object);
+
+       switch ((GsAppTranslationDialogProperty) prop_id) {
+       case PROP_APP:
+               g_value_set_object (value, gs_app_translation_dialog_get_app (self));
+               break;
+       default:
+               G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+               break;
+       }
+}
+
+static void
+gs_app_translation_dialog_set_property (GObject      *object,
+                                        guint         prop_id,
+                                        const GValue *value,
+                                        GParamSpec   *pspec)
+{
+       GsAppTranslationDialog *self = GS_APP_TRANSLATION_DIALOG (object);
+
+       switch ((GsAppTranslationDialogProperty) prop_id) {
+       case PROP_APP:
+               /* Construct only */
+               g_assert (self->app == NULL);
+               g_assert (self->app_notify_name_handler == 0);
+
+               self->app = g_value_dup_object (value);
+               self->app_notify_name_handler = g_signal_connect (self->app, "notify::name", G_CALLBACK 
(app_notify_cb), self);
+
+               /* Update the UI. */
+               update_labels (self);
+
+               g_object_notify_by_pspec (G_OBJECT (self), obj_props[PROP_APP]);
+               break;
+       default:
+               G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+               break;
+       }
+}
+
+static void
+gs_app_translation_dialog_dispose (GObject *object)
+{
+       GsAppTranslationDialog *self = GS_APP_TRANSLATION_DIALOG (object);
+
+       g_clear_signal_handler (&self->app_notify_name_handler, self->app);
+       g_clear_object (&self->app);
+
+       G_OBJECT_CLASS (gs_app_translation_dialog_parent_class)->dispose (object);
+}
+
+static void
+gs_app_translation_dialog_class_init (GsAppTranslationDialogClass *klass)
+{
+       GObjectClass *object_class = G_OBJECT_CLASS (klass);
+       GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+       object_class->get_property = gs_app_translation_dialog_get_property;
+       object_class->set_property = gs_app_translation_dialog_set_property;
+       object_class->dispose = gs_app_translation_dialog_dispose;
+
+       /**
+        * GsAppTranslationDialog:app: (not nullable)
+        *
+        * The app to display the translation details for.
+        *
+        * This must not be %NULL.
+        *
+        * Since: 41
+        */
+       obj_props[PROP_APP] =
+               g_param_spec_object ("app", NULL, NULL,
+                                    GS_TYPE_APP,
+                                    G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_EXPLICIT_NOTIFY | 
G_PARAM_STATIC_STRINGS);
+
+       g_object_class_install_properties (object_class, G_N_ELEMENTS (obj_props), obj_props);
+
+       gtk_widget_class_set_template_from_resource (widget_class, 
"/org/gnome/Software/gs-app-translation-dialog.ui");
+
+       gtk_widget_class_bind_template_child (widget_class, GsAppTranslationDialog, title);
+       gtk_widget_class_bind_template_child (widget_class, GsAppTranslationDialog, description);
+
+       gtk_widget_class_bind_template_callback (widget_class, key_press_event_cb);
+       gtk_widget_class_bind_template_callback (widget_class, button_clicked_cb);
+}
+
+/**
+ * gs_app_translation_dialog_new:
+ * @app: (not nullable): the app to display translation information for
+ *
+ * Create a new #GsAppTranslationDialog and set its initial app to @app.
+ *
+ * Returns: (transfer full): a new #GsAppTranslationDialog
+ * Since: 41
+ */
+GsAppTranslationDialog *
+gs_app_translation_dialog_new (GsApp *app)
+{
+       g_return_val_if_fail (GS_IS_APP (app), NULL);
+
+       return g_object_new (GS_TYPE_APP_TRANSLATION_DIALOG,
+                            "app", app,
+                            NULL);
+}
+
+/**
+ * gs_app_translation_dialog_get_app:
+ * @self: a #GsAppTranslationDialog
+ *
+ * Gets the value of #GsAppTranslationDialog:app.
+ *
+ * Returns: (not nullable) (transfer none): app whose translation information is
+ *     being displayed
+ * Since: 41
+ */
+GsApp *
+gs_app_translation_dialog_get_app (GsAppTranslationDialog *self)
+{
+       g_return_val_if_fail (GS_IS_APP_TRANSLATION_DIALOG (self), NULL);
+
+       return self->app;
+}
+
+/**
+ * gs_app_translation_dialog_app_has_url:
+ * @app: a #GsApp
+ *
+ * Check @app to see if it has appropriate URLs set on it to allow the user
+ * to be linked to a page relevant to translating the app.
+ *
+ * Generally this should be used to work out whether to show a
+ * #GsAppTranslationDialog dialog for a given @app.
+ *
+ * Returns: %TRUE if an URL exists, %FALSE otherwise
+ * Since: 41
+ */
+gboolean
+gs_app_translation_dialog_app_has_url (GsApp *app)
+{
+       g_return_val_if_fail (GS_IS_APP (app), FALSE);
+
+       return (get_url_for_app (app) != NULL);
+}
diff --git a/src/gs-app-translation-dialog.h b/src/gs-app-translation-dialog.h
new file mode 100644
index 000000000..2f1974b60
--- /dev/null
+++ b/src/gs-app-translation-dialog.h
@@ -0,0 +1,31 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ * vi:set noexpandtab tabstop=8 shiftwidth=8:
+ *
+ * Copyright (C) 2021 Endless OS Foundation LLC
+ *
+ * Author: Philip Withnall <pwithnall endlessos org>
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ */
+
+#pragma once
+
+#include <glib.h>
+#include <glib-object.h>
+#include <gtk/gtk.h>
+
+#include "gs-app.h"
+
+G_BEGIN_DECLS
+
+#define GS_TYPE_APP_TRANSLATION_DIALOG (gs_app_translation_dialog_get_type ())
+
+G_DECLARE_FINAL_TYPE (GsAppTranslationDialog, gs_app_translation_dialog, GS, APP_TRANSLATION_DIALOG, 
HdyWindow)
+
+GsAppTranslationDialog *gs_app_translation_dialog_new          (GsApp                  *app);
+
+GsApp                  *gs_app_translation_dialog_get_app      (GsAppTranslationDialog *self);
+
+gboolean                gs_app_translation_dialog_app_has_url  (GsApp                  *app);
+
+G_END_DECLS
diff --git a/src/gs-app-translation-dialog.ui b/src/gs-app-translation-dialog.ui
new file mode 100644
index 000000000..edf077a66
--- /dev/null
+++ b/src/gs-app-translation-dialog.ui
@@ -0,0 +1,156 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <requires lib="gtk+" version="3.10"/>
+  <template class="GsAppTranslationDialog" parent="HdyWindow">
+    <property name="modal">True</property>
+    <property name="window_position">center</property>
+    <property name="destroy_with_parent">True</property>
+    <property name="icon_name">dialog-information</property>
+    <property name="title" translatable="yes">Translations</property>
+    <property name="type_hint">dialog</property>
+    <property name="default-width">380</property>
+    <signal name="key-press-event" handler="key_press_event_cb" after="yes" swapped="no"/>
+    <style>
+      <class name="toolbox"/>
+    </style>
+
+    <child>
+      <object class="GtkOverlay">
+        <property name="visible">True</property>
+        <child type="overlay">
+          <object class="HdyHeaderBar">
+            <property name="show_close_button">True</property>
+            <property name="visible">True</property>
+            <property name="valign">start</property>
+          </object>
+        </child>
+        <child>
+          <object class="HdyPreferencesPage">
+            <property name="visible">True</property>
+            <child>
+              <object class="HdyPreferencesGroup">
+                <property name="visible">True</property>
+
+                <child>
+                  <object class="GtkBox">
+                    <property name="margin-left">3</property>
+                    <property name="margin-right">3</property>
+                    <property name="orientation">vertical</property>
+                    <property name="spacing">8</property>
+                    <property name="visible">True</property>
+
+                    <child>
+                      <object class="GtkBox">
+                        <property name="margin">20</property>
+                        <property name="orientation">vertical</property>
+                        <property name="spacing">12</property>
+                        <property name="visible">True</property>
+
+                        <child>
+                          <object class="GtkBox" id="lozenge">
+                            <property name="halign">center</property>
+                            <property name="visible">True</property>
+                            <style>
+                              <class name="context-tile-lozenge"/>
+                              <class name="large"/>
+                              <class name="blue"/>
+                            </style>
+                            <child>
+                              <object class="GtkImage" id="icon">
+                                <property name="halign">center</property>
+                                <property name="icon-name">flag-outline-thin-symbolic</property>
+                                <property name="icon-size">3</property><!-- GTK_ICON_SIZE_LARGE_TOOLBAR -->
+                                <property name="visible">True</property>
+                                <accessibility>
+                                  <relation target="title" type="labelled-by"/>
+                                </accessibility>
+                              </object>
+                              <packing>
+                                <property name="expand">True</property>
+                              </packing>
+                            </child>
+                          </object>
+                          <packing>
+                            <property name="fill">False</property>
+                          </packing>
+                        </child>
+
+                        <child>
+                          <object class="GtkLabel" id="title">
+                            <property name="justify">center</property>
+                            <!-- this is a placeholder: the text is actually set in code -->
+                            <property name="label">Help Translate Shortwave</property>
+                            <property name="visible">True</property>
+                            <property name="wrap">True</property>
+                            <property name="xalign">0.5</property>
+                            <style>
+                              <class name="title-1"/>
+                            </style>
+                            <accessibility>
+                              <relation target="lozenge" type="label-for"/>
+                            </accessibility>
+                            <style>
+                              <class name="context-tile-title"/>
+                            </style>
+                          </object>
+                        </child>
+                      </object>
+                    </child>
+
+                    <child>
+                      <object class="GtkLabel" id="description">
+                        <property name="justify">center</property>
+                        <!-- This is a placeholder: the actual label is set in code -->
+                        <property name="label">Shortwave is designed, developed, and translated by an 
international community of volunteers.\n\nThis means that while it’s not yet available in your language, you 
can get involved and help translate it yourself.</property>
+                        <property name="visible">True</property>
+                        <property name="wrap">True</property>
+                        <property name="xalign">0.5</property>
+                      </object>
+                    </child>
+
+                    <child>
+                      <object class="GtkButton">
+                        <property name="halign">center</property>
+                        <property name="margin">14</property>
+                        <property name="visible">True</property>
+                        <signal name="clicked" handler="button_clicked_cb"/>
+                        <style>
+                          <class name="suggested-action"/>
+                        </style>
+                        <child>
+                          <object class="GtkBox">
+                            <property name="visible">True</property>
+                            <property name="orientation">horizontal</property>
+                            <property name="spacing">6</property>
+                            <property name="halign">center</property>
+                            <property name="margin-left">12</property>
+                            <property name="margin-right">12</property>
+                            <child>
+                              <object class="GtkLabel">
+                                <property name="visible">True</property>
+                                <property name="xalign">0</property>
+                                <property name="yalign">0.5</property>
+                                <property name="label" translatable="yes">_Translation Website</property>
+                                <property name="use-underline">True</property>
+                              </object>
+                            </child>
+                            <child>
+                              <object class="GtkImage">
+                                <property name="visible">True</property>
+                                <property name="icon-name">external-link-symbolic</property>
+                              </object>
+                            </child>
+                          </object>
+                        </child>
+                      </object>
+                    </child>
+                  </object>
+                </child>
+              </object>
+            </child>
+          </object>
+        </child>
+      </object>
+    </child>
+  </template>
+</interface>
diff --git a/src/gtk-style-hc.css b/src/gtk-style-hc.css
index a41262902..42d1ae7cc 100644
--- a/src/gtk-style-hc.css
+++ b/src/gtk-style-hc.css
@@ -52,7 +52,7 @@ app-context-bar .context-tile {
 .context-tile-lozenge.red, .context-tile-lozenge.details-rating-18 { background-color: #fbd3cf; color: 
@theme_fg_color }
 .context-tile-lozenge.details-rating-15 { background-color: #ffd7a5; color: @theme_fg_color }
 .context-tile-lozenge.yellow, .context-tile-lozenge.details-rating-12 { background-color: #f7eb9f; color: 
@theme_fg_color }
-.context-tile-lozenge.details-rating-5 { background-color: #d1e4fb; color: @theme_fg_color }
+.context-tile-lozenge.blue, .context-tile-lozenge.details-rating-5 { background-color: #d1e4fb; color: 
@theme_fg_color }
 
 .community-built { color: @theme_fg_color }
 
diff --git a/src/gtk-style.css b/src/gtk-style.css
index 86deea6b4..32e4bcfa1 100644
--- a/src/gtk-style.css
+++ b/src/gtk-style.css
@@ -772,7 +772,7 @@ app-context-bar .context-tile-description { font-size: smaller }
 .context-tile-lozenge.red, .context-tile-lozenge.details-rating-18 { background-color: #fbd3cf; color: 
#ab3342 }
 .context-tile-lozenge.details-rating-15 { background-color: #ffd7a5; color: #c75400 }
 .context-tile-lozenge.yellow, .context-tile-lozenge.details-rating-12 { background-color: #f7eb9f; color: 
#9c7107 }
-.context-tile-lozenge.details-rating-5 { background-color: #d1e4fb; color: #294d7a }
+.context-tile-lozenge.blue, .context-tile-lozenge.details-rating-5 { background-color: #d1e4fb; color: 
#294d7a }
 
 window.toolbox button.titlebutton:not(:hover) {
   background: @theme_bg_color;
diff --git a/src/meson.build b/src/meson.build
index 6bf66ab5f..8b3b00501 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -32,6 +32,7 @@ gnome_software_sources = [
   'gs-app-details-page.c',
   'gs-app-row.c',
   'gs-app-tile.c',
+  'gs-app-translation-dialog.c',
   'gs-basic-auth-dialog.c',
   'gs-category-page.c',
   'gs-category-tile.c',


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]