diff --git a/demo/adw-demo-debug-info.c b/demo/adw-demo-debug-info.c new file mode 100644 index 0000000000000000000000000000000000000000..d67db893e71849492dba1030a89742e94d70fde5 --- /dev/null +++ b/demo/adw-demo-debug-info.c @@ -0,0 +1,170 @@ +#include "config.h" + +#include "adw-demo-debug-info.h" + +/* Copied and adapted from gtk/inspector/general.c */ +static void +get_gtk_info (const char **backend, + const char **renderer) +{ + GdkDisplay *display = gdk_display_get_default (); + GdkSurface *surface; + GskRenderer *gsk_renderer; + + if (!g_strcmp0 (G_OBJECT_TYPE_NAME (display), "GdkX11Display")) + *backend = "X11"; + else if (!g_strcmp0 (G_OBJECT_TYPE_NAME (display), "GdkWaylandDisplay")) + *backend = "Wayland"; + else if (!g_strcmp0 (G_OBJECT_TYPE_NAME (display), "GdkBroadwayDisplay")) + *backend = "Broadway"; + else if (!g_strcmp0 (G_OBJECT_TYPE_NAME (display), "GdkWin32Display")) + *backend = "Windows"; + else if (!g_strcmp0 (G_OBJECT_TYPE_NAME (display), "GdkMacosDisplay")) + *backend = "macOS"; + else + *backend = G_OBJECT_TYPE_NAME (display); + + surface = gdk_surface_new_toplevel (display); + gsk_renderer = gsk_renderer_new_for_surface (surface); + if (!g_strcmp0 (G_OBJECT_TYPE_NAME (gsk_renderer), "GskVulkanRenderer")) + *renderer = "Vulkan"; + else if (!g_strcmp0 (G_OBJECT_TYPE_NAME (gsk_renderer), "GskGLRenderer")) + *renderer = "GL"; + else if (!g_strcmp0 (G_OBJECT_TYPE_NAME (gsk_renderer), "GskCairoRenderer")) + *renderer = "Cairo"; + else + *renderer = G_OBJECT_TYPE_NAME (gsk_renderer); + + gsk_renderer_unrealize (gsk_renderer); + g_object_unref (gsk_renderer); + gdk_surface_destroy (surface); +} + +#ifndef G_OS_WIN32 +static char * +get_flatpak_info (const char *group, + const char *key) +{ + GKeyFile *keyfile = g_key_file_new (); + char *ret = NULL; + + if (g_key_file_load_from_file (keyfile, "/.flatpak-info", 0, NULL)) + ret = g_key_file_get_string (keyfile, group, key, NULL); + + g_key_file_unref (keyfile); + + return ret; +} +#endif + +char * +adw_demo_generate_debug_info (void) +{ + GString *string = g_string_new (NULL); +#ifndef G_OS_WIN32 + gboolean flatpak = g_file_test ("/.flatpak-info", G_FILE_TEST_EXISTS); +#endif + + g_string_append_printf (string, "Libadwaita demo: %s (%s)\n", ADW_VERSION_S, + ADW_DEMO_VCS_TAG); + g_string_append (string, "\n"); + + g_string_append (string, "Compiled against:\n"); + g_string_append_printf (string, "- GLib: %d.%d.%d\n", GLIB_MAJOR_VERSION, + GLIB_MINOR_VERSION, + GLIB_MICRO_VERSION); + g_string_append_printf (string, "- GTK: %d.%d.%d\n", GTK_MAJOR_VERSION, + GTK_MINOR_VERSION, + GTK_MICRO_VERSION); + g_string_append (string, "\n"); + + g_string_append (string, "Running against:\n"); + g_string_append_printf (string, "- GLib: %d.%d.%d\n", glib_major_version, + glib_minor_version, + glib_micro_version); + g_string_append_printf (string, "- GTK: %d.%d.%d\n", gtk_get_major_version (), + gtk_get_minor_version (), + gtk_get_micro_version ()); + g_string_append (string, "\n"); + + { + char *os_name = g_get_os_info (G_OS_INFO_KEY_NAME); + char *os_version = g_get_os_info (G_OS_INFO_KEY_VERSION); + + g_string_append (string, "System:\n"); + g_string_append_printf (string, "- Name: %s\n", os_name); + g_string_append_printf (string, "- Version: %s\n", os_version); + g_string_append (string, "\n"); + + g_free (os_name); + g_free (os_version); + } + + { + const char *backend, *renderer; + + get_gtk_info (&backend, &renderer); + + g_string_append (string, "GTK:\n"); + g_string_append_printf (string, "- GDK backend: %s\n", backend); + g_string_append_printf (string, "- GSK renderer: %s\n", renderer); + g_string_append (string, "\n"); + } + +#ifndef G_OS_WIN32 + if (flatpak) { + char *runtime = get_flatpak_info ("Application", "runtime"); + char *runtime_commit = get_flatpak_info ("Instance", "runtime-commit"); + char *arch = get_flatpak_info ("Instance", "arch"); + char *flatpak_version = get_flatpak_info ("Instance", "flatpak-version"); + char *devel = get_flatpak_info ("Instance", "devel"); + + g_string_append (string, "Flatpak:\n"); + g_string_append_printf (string, "- Runtime: %s\n", runtime); + g_string_append_printf (string, "- Runtime commit: %s\n", runtime_commit); + g_string_append_printf (string, "- Arch: %s\n", arch); + g_string_append_printf (string, "- Flatpak version: %s\n", flatpak_version); + g_string_append_printf (string, "- Devel: %s\n", devel ? "yes" : "no"); + g_string_append (string, "\n"); + + g_free (runtime); + g_free (runtime_commit); + g_free (arch); + g_free (flatpak_version); + g_free (devel); + } +#endif + + { + const char *desktop = g_getenv ("XDG_CURRENT_DESKTOP"); + const char *session_desktop = g_getenv ("XDG_SESSION_DESKTOP"); + const char *session_type = g_getenv ("XDG_SESSION_TYPE"); + const char *lang = g_getenv ("LANG"); + const char *builder = g_getenv ("INSIDE_GNOME_BUILDER"); + const char *gtk_debug = g_getenv ("GTK_DEBUG"); + const char *gtk_theme = g_getenv ("GTK_THEME"); + const char *adw_debug_color_scheme = g_getenv ("ADW_DEBUG_COLOR_SCHEME"); + const char *adw_debug_high_contrast = g_getenv ("ADW_DEBUG_HIGH_CONTRAST"); + const char *adw_disable_portal = g_getenv ("ADW_DISABLE_PORTAL"); + + g_string_append (string, "Environment:\n"); + g_string_append_printf (string, "- Desktop: %s\n", desktop); + g_string_append_printf (string, "- Session: %s (%s)\n", session_desktop, + session_type); + g_string_append_printf (string, "- Language: %s\n", lang); + g_string_append_printf (string, "- Running inside Builder: %s\n", builder ? "yes" : "no"); + + if (gtk_debug) + g_string_append_printf (string, "- GTK_DEBUG: %s\n", gtk_debug); + if (gtk_theme) + g_string_append_printf (string, "- GTK_THEME: %s\n", gtk_theme); + if (adw_debug_color_scheme) + g_string_append_printf (string, "- ADW_DEBUG_COLOR_SCHEME: %s\n", adw_debug_color_scheme); + if (adw_debug_high_contrast) + g_string_append_printf (string, "- ADW_DEBUG_HIGH_CONTRAST: %s\n", adw_debug_high_contrast); + if (adw_disable_portal) + g_string_append_printf (string, "- ADW_DISABLE_PORTAL: %s\n", adw_disable_portal); + } + + return g_string_free (string, FALSE); +} diff --git a/demo/adw-demo-debug-info.h b/demo/adw-demo-debug-info.h new file mode 100644 index 0000000000000000000000000000000000000000..341eae9f49af5e78b0453163c64b10bc79f4d5ec --- /dev/null +++ b/demo/adw-demo-debug-info.h @@ -0,0 +1,9 @@ +#pragma once + +#include + +G_BEGIN_DECLS + +char *adw_demo_generate_debug_info (void); + +G_END_DECLS diff --git a/demo/adw-demo-window.c b/demo/adw-demo-window.c index b15faf9ce56192a7b2a2927a726ef60402aea6f9..74edef492edebffa35348ab2798bd1768fe66732 100644 --- a/demo/adw-demo-window.c +++ b/demo/adw-demo-window.c @@ -2,6 +2,7 @@ #include +#include "pages/about/adw-demo-page-about.h" #include "pages/animations/adw-demo-page-animations.h" #include "pages/avatar/adw-demo-page-avatar.h" #include "pages/buttons/adw-demo-page-buttons.h" @@ -116,6 +117,7 @@ adw_demo_window_init (AdwDemoWindow *self) { AdwStyleManager *manager = adw_style_manager_get_default (); + g_type_ensure (ADW_TYPE_DEMO_PAGE_ABOUT); g_type_ensure (ADW_TYPE_DEMO_PAGE_ANIMATIONS); g_type_ensure (ADW_TYPE_DEMO_PAGE_AVATAR); g_type_ensure (ADW_TYPE_DEMO_PAGE_BUTTONS); diff --git a/demo/adw-demo-window.ui b/demo/adw-demo-window.ui index 7f3799bf34156d35c6aa237f76414e20571a9737..76f90f9f5b8d242fc549211ce69f6a81c9947e25 100644 --- a/demo/adw-demo-window.ui +++ b/demo/adw-demo-window.ui @@ -228,6 +228,14 @@ + + + About Window + + + + + diff --git a/demo/adwaita-demo.c b/demo/adwaita-demo.c index 54bbb1defa14f6efd2f6015fab682907842a45b8..ff968883bf096e10e50628c50b63c9cc7cb1c720 100644 --- a/demo/adwaita-demo.c +++ b/demo/adwaita-demo.c @@ -2,6 +2,7 @@ #include #include +#include "adw-demo-debug-info.h" #include "adw-demo-preferences-window.h" #include "adw-demo-window.h" @@ -31,7 +32,7 @@ show_about (GSimpleAction *action, GVariant *state, gpointer user_data) { - const char *authors[] = { + const char *developers[] = { "Adrien Plazas", "Alexander Mikhaylenko", "Andrei Lișiță", @@ -42,38 +43,47 @@ show_about (GSimpleAction *action, NULL }; - const char *artists[] = { + const char *designers[] = { "GNOME Design Team", NULL - }; + }; GtkApplication *app = GTK_APPLICATION (user_data); GtkWindow *window = gtk_application_get_active_window (app); - char *version; - - version = g_strdup_printf ("%s\nRunning against libadwaita %d.%d.%d, GTK %d.%d.%d", - ADW_VERSION_S, - adw_get_major_version (), - adw_get_minor_version (), - adw_get_micro_version (), - gtk_get_major_version (), - gtk_get_minor_version (), - gtk_get_micro_version ()); - - gtk_show_about_dialog (window, - "program-name", _("Adwaita Demo"), - "title", _("About Adwaita Demo"), - "logo-icon-name", "org.gnome.Adwaita1.Demo", - "version", version, - "copyright", "Copyright © 2017–2021 Purism SPC", - "comments", _("Tour of the features in Libadwaita"), - "website", "https://gitlab.gnome.org/GNOME/libadwaita", - "license-type", GTK_LICENSE_LGPL_2_1, - "authors", authors, - "artists", artists, - "translator-credits", _("translator-credits"), - NULL); - g_free (version); + char *debug_info; + GtkWidget *about; + + debug_info = adw_demo_generate_debug_info (); + + about = + g_object_new (ADW_TYPE_ABOUT_WINDOW, + "transient-for", window, + "application-icon", "org.gnome.Adwaita1.Demo", + "application-name", _("Adwaita Demo"), + "developer-name", _("The GNOME Project"), + "version", ADW_VERSION_S, + "website", "https://gitlab.gnome.org/GNOME/libadwaita", + "issue-url", "https://gitlab.gnome.org/GNOME/libadwaita/-/issues/new", + "debug-info", debug_info, + "debug-info-filename", "adwaita-1-demo-debug-info.txt", + "copyright", "© 2017–2022 Purism SPC", + "license-type", GTK_LICENSE_LGPL_2_1, + "developers", developers, + "designers", designers, + "artists", designers, + "translator-credits", _("translator-credits"), + NULL); + + adw_about_window_add_link (ADW_ABOUT_WINDOW (about), + _("_Documentation"), + "https://gnome.pages.gitlab.gnome.org/libadwaita/doc/main/"); + adw_about_window_add_link (ADW_ABOUT_WINDOW (about), + _("_Chat"), + "https://matrix.to/#/#libadwaita:gnome.org"); + + gtk_window_present (GTK_WINDOW (about)); + + g_free (debug_info); } static void diff --git a/demo/adwaita-demo.gresources.xml b/demo/adwaita-demo.gresources.xml index b27a6b98cc95d3951d726ec9ec8f61c55dfe2dc4..b307ecea341ef1d6d48e97441901ef63171193d7 100644 --- a/demo/adwaita-demo.gresources.xml +++ b/demo/adwaita-demo.gresources.xml @@ -25,6 +25,7 @@ icons/scalable/actions/view-sidebar-start-symbolic-rtl.svg icons/scalable/actions/view-sidebar-end-symbolic.svg icons/scalable/actions/view-sidebar-end-symbolic-rtl.svg + icons/scalable/actions/widget-about-symbolic.svg icons/scalable/actions/widget-carousel-symbolic.svg icons/scalable/actions/widget-clamp-symbolic.svg icons/scalable/actions/widget-dialog-symbolic.svg @@ -35,6 +36,7 @@ icons/scalable/actions/widget-toast-symbolic.svg icons/scalable/actions/widget-view-switcher-symbolic.svg icons/scalable/apps/org.gnome.Boxes.svg + icons/scalable/apps/org.example.Typeset.svg icons/scalable/status/dark-mode-symbolic.svg icons/scalable/status/light-mode-symbolic.svg icons/scalable/status/tab-audio-playing-symbolic.svg @@ -43,6 +45,7 @@ style-dark.css + pages/about/adw-demo-page-about.ui pages/animations/adw-demo-page-animations.ui pages/avatar/adw-demo-page-avatar.ui pages/buttons/adw-demo-page-buttons.ui diff --git a/demo/icons/org.example.Typeset.Source.svg b/demo/icons/org.example.Typeset.Source.svg new file mode 100644 index 0000000000000000000000000000000000000000..392d686ac613fc8cf33aba0ff5f4a4e55e2f1913 --- /dev/null +++ b/demo/icons/org.example.Typeset.Source.svg @@ -0,0 +1,3635 @@ + + + + + Adwaita Icon Template + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + GNOME Design Team + + + + + Adwaita Icon Template + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Hicolor + Symbolic + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + battery is full and there is no a/c connected. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/demo/icons/scalable/actions/widget-about-symbolic.svg b/demo/icons/scalable/actions/widget-about-symbolic.svg new file mode 100644 index 0000000000000000000000000000000000000000..12329841a47b71a21ce79b957b739e5b4e685554 --- /dev/null +++ b/demo/icons/scalable/actions/widget-about-symbolic.svg @@ -0,0 +1,4 @@ + + + + diff --git a/demo/icons/scalable/apps/org.example.Typeset.svg b/demo/icons/scalable/apps/org.example.Typeset.svg new file mode 100644 index 0000000000000000000000000000000000000000..b93b5b886c809940fee93604586081acc0c404c6 --- /dev/null +++ b/demo/icons/scalable/apps/org.example.Typeset.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/demo/meson.build b/demo/meson.build index 13280a647c8bbf933dc9f481e65892acdaec1b0a..d41d19e91e3c05668f2c3dd7e39d36f0862f195a 100644 --- a/demo/meson.build +++ b/demo/meson.build @@ -2,6 +2,17 @@ if get_option('examples') subdir('data') +demo_config_data = configuration_data() +demo_config_data.set_quoted('ADW_DEMO_VCS_TAG', '@VCS_TAG@') + +demo_config_h = vcs_tag( + input: configure_file( + output: 'config.h.in', + configuration: demo_config_data + ), + output: 'config.h' +) + adwaita_demo_resources = gnome.compile_resources( 'adwaita-demo-resources', 'adwaita-demo.gresources.xml', @@ -12,6 +23,7 @@ adwaita_demo_resources = gnome.compile_resources( adwaita_demo_sources = [ adwaita_demo_resources, + 'pages/about/adw-demo-page-about.c', 'pages/animations/adw-demo-page-animations.c', 'pages/avatar/adw-demo-page-avatar.c', 'pages/buttons/adw-demo-page-buttons.c', @@ -32,8 +44,10 @@ adwaita_demo_sources = [ 'pages/welcome/adw-demo-page-welcome.c', 'adwaita-demo.c', + 'adw-demo-debug-info.c', 'adw-demo-preferences-window.c', 'adw-demo-window.c', + demo_config_h, libadwaita_generated_headers, ] diff --git a/demo/pages/about/adw-demo-page-about.c b/demo/pages/about/adw-demo-page-about.c new file mode 100644 index 0000000000000000000000000000000000000000..a7f67e05d4b168ea03fe348fc15738193a84e1f8 --- /dev/null +++ b/demo/pages/about/adw-demo-page-about.c @@ -0,0 +1,99 @@ +#include "adw-demo-page-about.h" + +#include + +struct _AdwDemoPageAbout +{ + AdwBin parent_instance; +}; + +G_DEFINE_TYPE (AdwDemoPageAbout, adw_demo_page_about, ADW_TYPE_BIN) + +static void +demo_run_cb (AdwDemoPageAbout *self) +{ + GtkRoot *root = gtk_widget_get_root (GTK_WIDGET (self)); + GtkWidget *about; + + const char *developers[] = { + "Angela Avery ", + NULL + }; + + const char *artists[] = { + "GNOME Design Team", + NULL + }; + + const char *special_thanks[] = { + "My cat", + NULL + }; + + const char *release_notes = "\ +

\ + This release adds the following features:\ +

\ +
    \ +
  • Added a way to export fonts.
  • \ +
  • Better support for monospace fonts.
  • \ +
  • Added a way to preview italic text.
  • \ +
  • Bug fixes and performance improvements.
  • \ +
  • Translation updates.
  • \ +
\ + "; + + about = + g_object_new (ADW_TYPE_ABOUT_WINDOW, + "transient-for", root, + "application-icon", "org.example.Typeset", + "application-name", _("Typeset"), + "developer-name", _("Angela Avery"), + "version", "1.2.3", + "release-notes-version", "1.2.0", + "release-notes", release_notes, + "comments", _("Typeset is an app that doesn’t exist and is used as an example content for this about window."), + "website", "https://example.org", + "issue-url", "https://example.org", + "support-url", "https://example.org", + "copyright", "© 2022 Angela Avery", + "license-type", GTK_LICENSE_LGPL_2_1, + "developers", developers, + "artists", artists, + "translator-credits", _("translator-credits"), + NULL); + + adw_about_window_add_link (ADW_ABOUT_WINDOW (about), + _("_Documentation"), + "https://gnome.pages.gitlab.gnome.org/libadwaita/doc/main/class.AboutWindow.html"); + + adw_about_window_add_legal_section (ADW_ABOUT_WINDOW (about), + _("Fonts"), + NULL, + GTK_LICENSE_CUSTOM, + "This application uses font data from somewhere."); + + adw_about_window_add_acknowledgement_section (ADW_ABOUT_WINDOW (about), + _("Special thanks to"), + special_thanks); + + gtk_window_present (GTK_WINDOW (about)); + + gtk_window_present (GTK_WINDOW (about)); +} + +static void +adw_demo_page_about_class_init (AdwDemoPageAboutClass *klass) +{ + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/Adwaita1/Demo/ui/pages/about/adw-demo-page-about.ui"); + + gtk_widget_class_install_action (widget_class, "demo.run", NULL, (GtkWidgetActionActivateFunc) demo_run_cb); +} + +static void +adw_demo_page_about_init (AdwDemoPageAbout *self) +{ + gtk_widget_init_template (GTK_WIDGET (self)); +} diff --git a/demo/pages/about/adw-demo-page-about.h b/demo/pages/about/adw-demo-page-about.h new file mode 100644 index 0000000000000000000000000000000000000000..35c733c94d0a3f116a1897a1a7e4e5d428a46540 --- /dev/null +++ b/demo/pages/about/adw-demo-page-about.h @@ -0,0 +1,11 @@ +#pragma once + +#include + +G_BEGIN_DECLS + +#define ADW_TYPE_DEMO_PAGE_ABOUT (adw_demo_page_about_get_type()) + +G_DECLARE_FINAL_TYPE (AdwDemoPageAbout, adw_demo_page_about, ADW, DEMO_PAGE_ABOUT, AdwBin) + +G_END_DECLS diff --git a/demo/pages/about/adw-demo-page-about.ui b/demo/pages/about/adw-demo-page-about.ui new file mode 100644 index 0000000000000000000000000000000000000000..0430d6a24051a2eaa63bd359c593b49221b35d76 --- /dev/null +++ b/demo/pages/about/adw-demo-page-about.ui @@ -0,0 +1,24 @@ + + + + + + diff --git a/doc/images/about-window-credits-dark.png b/doc/images/about-window-credits-dark.png new file mode 100644 index 0000000000000000000000000000000000000000..51af7c310eb70660361ae13d2f3b9d8d662cc28c Binary files /dev/null and b/doc/images/about-window-credits-dark.png differ diff --git a/doc/images/about-window-credits.png b/doc/images/about-window-credits.png new file mode 100644 index 0000000000000000000000000000000000000000..2491c0908ead0b38df44a420a2f7a7b7535422ab Binary files /dev/null and b/doc/images/about-window-credits.png differ diff --git a/doc/images/about-window-dark.png b/doc/images/about-window-dark.png new file mode 100644 index 0000000000000000000000000000000000000000..d6b04d4ae6a0fb8cfa61f641ab5023808f63df89 Binary files /dev/null and b/doc/images/about-window-dark.png differ diff --git a/doc/images/about-window.png b/doc/images/about-window.png new file mode 100644 index 0000000000000000000000000000000000000000..da4b5d7aac2936d2e0e2a2f4765e0a799706458b Binary files /dev/null and b/doc/images/about-window.png differ diff --git a/doc/libadwaita.toml.in b/doc/libadwaita.toml.in index 5be4004a5f444c826a946d2f55b92910c846cf6d..27de1b2463f6f7a97ec4238fd5827bc04ba663d5 100644 --- a/doc/libadwaita.toml.in +++ b/doc/libadwaita.toml.in @@ -54,6 +54,10 @@ content_files = [ "visual-index.md", ] content_images = [ + "images/about-window.png", + "images/about-window-dark.png", + "images/about-window-credits.png", + "images/about-window-credits-dark.png", "images/action-row.png", "images/action-row-dark.png", "images/adaptive-boxed-lists-narrow.png", diff --git a/doc/tools/data/about-window-credits.ui b/doc/tools/data/about-window-credits.ui new file mode 100644 index 0000000000000000000000000000000000000000..5f8de338684ef41298b8b8da3eb00114b3708959 --- /dev/null +++ b/doc/tools/data/about-window-credits.ui @@ -0,0 +1,44 @@ + + + + + + + 400 + + + 12 + 12 + 12 + 12 + + + Edgar Allan Poe + mailto:edgar@poe.com + True + + + adw-mail-send-symbolic + + + + + + + The GNOME Project + https://www.gnome.org + True + + + adw-external-link-symbolic + + + + + + + + + diff --git a/doc/tools/data/about-window.ui b/doc/tools/data/about-window.ui new file mode 100644 index 0000000000000000000000000000000000000000..b43699f6c77518e8b6193cc37b2d19ee3a1ab435 --- /dev/null +++ b/doc/tools/data/about-window.ui @@ -0,0 +1,14 @@ + + + + + + application-x-executable + Application + Developer Name + 1.2.3 + https://example.org + Author + © 2022 Example + + diff --git a/doc/visual-index.md b/doc/visual-index.md index 0a2f42d2b1c0a04cce88abe72792a066fd5d1f49..97079e66d9b9f48006add245ceb616a2318fe5d3 100644 --- a/doc/visual-index.md +++ b/doc/visual-index.md @@ -95,6 +95,13 @@ Slug: visual-index preferences-window ](class.PreferencesWindow.html) +## About Window + +[ + + about-window +](class.AboutWindow.html) + ## Navigation ### Carousel diff --git a/po/POTFILES.in b/po/POTFILES.in index 31db95b2dad9414b38ffd052cb83bb995ed847d7..1794c171372d1ddb88be29d4f2e6f627cb1656de 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -1,5 +1,7 @@ # List of source files containing translatable strings. # Please keep this file sorted alphabetically. +src/adw-about-window.c +src/adw-about-window.ui src/adw-entry-row.ui src/adw-inspector-page.c src/adw-inspector-page.ui diff --git a/src/adw-about-window.c b/src/adw-about-window.c new file mode 100644 index 0000000000000000000000000000000000000000..d6c43584f9fff31806527e6af1535a6d2eafcff0 --- /dev/null +++ b/src/adw-about-window.c @@ -0,0 +1,3222 @@ +/* + * Copyright (C) 2021-2022 Purism SPC + * + * SPDX-License-Identifier: LGPL-2.1-or-later + */ + +#include "config.h" +#include + +#include "adw-about-window.h" + +#include "adw-action-row.h" +#include "adw-leaflet.h" +#include "adw-macros-private.h" +#include "adw-message-dialog.h" +#include "adw-preferences-group.h" +#include "adw-toast-overlay.h" + +#define HEADERBAR_STYLE_THRESHOLD 190 + +/** + * AdwAboutWindow: + * + * A window showing information about the application. + * + * + * + * about-window + * + * + * An about window is typically opened when the user activates the `About …` + * item in the application's primary menu. All parts of the window are optional. + * + * ## Main page + * + * `AdwAboutWindow` prominently displays the application's icon, name, developer + * name and version. They can be set with the [property@AboutWindow:application-icon], + * [property@AboutWindow:application-name], + * [property@AboutWindow:developer-name] and [property@AboutWindow:version] + * respectively. + * + * ## What's New + * + * `AdwAboutWindow` provides a way for applications to display their release + * notes, set with the [property@AboutWindow:release-notes] property. + * + * Release notes are formatted the same way as + * [AppStream descriptions](https://freedesktop.org/software/appstream/docs/chap-Metadata.html#tag-description). + * + * The supported formatting options are: + * + * * Paragraph (`

`) + * * Ordered list (`

    `), with list items (`
  1. `) + * * Unordered list (`
      `), with list items (`
    • `) + * + * Within paragraphs and list items, emphasis (``) and inline code + * (``) text styles are supported. The emphasis is rendered in italic, + * while inline code is shown in a monospaced font. + * + * Any text outside paragraphs or list items is ignored. + * + * Nested lists are not supported. + * + * Only one version can be shown at a time. By default, the displayed version + * number matches [property@AboutWindow:version]. Use + * [property@AboutWindow:release-notes-version] to override it. + * + * ## Details + * + * The Details page displays the application comments and links. + * + * The comments can be set with the [property@AboutWindow:comments] property. + * Unlike [property@Gtk.AboutDialog:comments], this string can be long and + * detailed. It can also contain links and Pango markup. + * + * To set the application website, use [property@AboutWindow:website]. + * To add extra links below the website, use [method@AboutWindow.add_link]. + * + * If the Details page doesn't have any other content besides website, the + * website will be displayed on the main page instead. + * + * ## Troubleshooting + * + * `AdwAboutWindow` displays the following two links on the main page: + * + * * Support Questions, set with the [property@AboutWindow:support-url] property, + * * Report an Issue, set with the [property@AboutWindow:issue-url] property. + * + * Additionally, applications can provide debugging information. It will be + * shown separately on the Troubleshooting page. Use the + * [property@AboutWindow:debug-info] property to specify it. + * + * It's intended to be attached to issue reports when reporting issues against + * the application. As such, it cannot contain markup or links. + * + * `AdwAboutWindow` provides a quick way to save debug information to a file. + * When saving, [property@AboutWindow:debug-info-filename] would be used as + * the suggested filename. + * + * ## Credits and Acknowledgements + * + * The Credits page has the following default sections: + * + * * Developers, set with the [property@AboutWindow:developers] property, + * * Designers, set with the [property@AboutWindow:designers] property, + * * Artists, set with the [property@AboutWindow:artists] property, + * * Documenters, set with the [property@AboutWindow:documenters] property, + * * Translators, set with the [property@AboutWindow:translator-credits] property. + * + * When setting translator credits, use the strings `"translator-credits"` or + * `"translator_credits"` and mark them as translatable. + * + * The default sections that don't contain any names won't be displayed. + * + * The Credits page can also contain an arbitrary number of extra sections below + * the default ones. Use [method@AboutWindow.add_credit_section] to add them. + * + * The Acknowledgements page can be used to acknowledge additional people and + * organizations for their non-development contributions. Use + * [method@AboutWindow.add_acknowledgement_section] to add sections to it. For + * example, it can be used to list backers in a crowdfunded project or to give + * special thanks. + * + * Each of the people or organizations can have an email address or a website + * specified. To add a email address, use a string like + * `Edgar Allan Poe `. To specify a website with a title, use a + * string like `The GNOME Project https://www.gnome.org`: + * + * + * + * about-window-credits + * + * + * ## Legal + * + * The Legal page displays the copyright and licensing information for the + * application and other modules. + * + * The copyright string is set with the [property@AboutWindow:copyright] + * property and should be a short string of one or two lines, for example: + * `© 2022 Example`. + * + * Licensing information can be quickly set from a list of known licenses with + * the [property@AboutWindow:license-type] property. If the application's + * license is not in the list, [property@AboutWindow:license] can be used + * instead. + * + * To add information about other modules, such as application dependencies or + * data, use [method@AboutWindow.add_legal_section]. + * + * ## Constructing + * + * To make constructing an `AdwAboutWindow` as convenient as possible, you can + * use the function [func@show_about_window] which constructs and shows a + * window. + * + * ```c + * static void + * show_about (GtkApplication *app) + * { + * const char *developers[] = { + * "Angela Avery", + * NULL + * }; + * + * const char *designers[] = { + * "GNOME Design Team", + * NULL + * }; + * + * adw_show_about_window (gtk_application_get_active_window (app), + * "application-name", _("Example"), + * "application-icon", "org.example.App", + * "version", "1.2.3", + * "copyright", "© 2022 Angela Avery", + * "issue-url", "https://gitlab.gnome.org/example/example/-/issues/new", + * "license-type", GTK_LICENSE_GPL_3_0, + * "developers", developers, + * "designers", designers, + * "translator-credits", _("translator-credits"), + * NULL); + * } + * ``` + * + * ## CSS nodes + * + * `AdwAboutWindow` has a main CSS node with the name `window` and the + * style class `.about`. + * + * Since: 1.2 + */ + +/* Copied from GTK 4 for consistency with GtkAboutDialog. */ +typedef struct { + const char *name; + const char *url; +} LicenseInfo; + +/* Copied from GTK 4 for consistency with GtkAboutDialog. */ +/* LicenseInfo for each GtkLicense type; keep in the same order as the + * enumeration. */ +static const LicenseInfo gtk_license_info [] = { + { NULL, NULL }, + { NULL, NULL }, + { N_("GNU General Public License, version 2 or later"), "https://www.gnu.org/licenses/old-licenses/gpl-2.0.html" }, + { N_("GNU General Public License, version 3 or later"), "https://www.gnu.org/licenses/gpl-3.0.html" }, + { N_("GNU Lesser General Public License, version 2.1 or later"), "https://www.gnu.org/licenses/old-licenses/lgpl-2.1.html" }, + { N_("GNU Lesser General Public License, version 3 or later"), "https://www.gnu.org/licenses/lgpl-3.0.html" }, + { N_("BSD 2-Clause License"), "https://opensource.org/licenses/bsd-license.php" }, + { N_("The MIT License (MIT)"), "https://opensource.org/licenses/mit-license.php" }, + { N_("Artistic License 2.0"), "https://opensource.org/licenses/artistic-license-2.0.php" }, + { N_("GNU General Public License, version 2 only"), "https://www.gnu.org/licenses/old-licenses/gpl-2.0.html" }, + { N_("GNU General Public License, version 3 only"), "https://www.gnu.org/licenses/gpl-3.0.html" }, + { N_("GNU Lesser General Public License, version 2.1 only"), "https://www.gnu.org/licenses/old-licenses/lgpl-2.1.html" }, + { N_("GNU Lesser General Public License, version 3 only"), "https://www.gnu.org/licenses/lgpl-3.0.html" }, + { N_("GNU Affero General Public License, version 3 or later"), "https://www.gnu.org/licenses/agpl-3.0.html" }, + { N_("GNU Affero General Public License, version 3 only"), "https://www.gnu.org/licenses/agpl-3.0.html" }, + { N_("BSD 3-Clause License"), "https://opensource.org/licenses/BSD-3-Clause" }, + { N_("Apache License, Version 2.0"), "https://opensource.org/licenses/Apache-2.0" }, + { N_("Mozilla Public License 2.0"), "https://opensource.org/licenses/MPL-2.0" } +}; +/* Copied from GTK 4 for consistency with GtkAboutDialog. */ +/* Keep this static assertion updated with the last element of the enumeration, + * and make sure it matches the last element of the array. */ +G_STATIC_ASSERT (G_N_ELEMENTS (gtk_license_info) - 1 == GTK_LICENSE_MPL_2_0); + +typedef struct { + char *name; + char **people; +} CreditsSection; + +typedef struct { + char *title; + char *copyright; + char *license; + GtkLicense license_type; +} LegalSection; + +struct _AdwAboutWindow { + AdwWindow parent_instance; + + GtkWidget *leaflet; + GtkWidget *subpage_stack; + GtkWidget *toast_overlay; + GtkWidget *website_row; + GtkWidget *links_group; + GtkWidget *details_website_row; + GtkWidget *credits_box; + GtkWidget *legal_box; + GtkWidget *acknowledgements_box; + GtkWidget *debug_info_page; + GtkTextBuffer *release_notes_buffer; + + char *application_icon; + char *application_name; + char *developer_name; + char *version; + char *release_notes_version; + char *release_notes; + char *comments; + char *website; + char *support_url; + char *issue_url; + char *debug_info; + char *debug_info_filename; + char **developers; + char **designers; + char **artists; + char **documenters; + char *translator_credits; + GSList *credit_sections; + char *copyright; + char *license; + GtkLicense license_type; + GSList *legal_sections; + gboolean has_custom_links; +}; + +G_DEFINE_TYPE (AdwAboutWindow, adw_about_window, ADW_TYPE_WINDOW) + +enum { + PROP_0, + PROP_APPLICATION_ICON, + PROP_APPLICATION_NAME, + PROP_DEVELOPER_NAME, + PROP_VERSION, + PROP_RELEASE_NOTES_VERSION, + PROP_RELEASE_NOTES, + PROP_COMMENTS, + PROP_WEBSITE, + PROP_SUPPORT_URL, + PROP_ISSUE_URL, + PROP_DEBUG_INFO, + PROP_DEBUG_INFO_FILENAME, + PROP_DEVELOPERS, + PROP_DESIGNERS, + PROP_ARTISTS, + PROP_DOCUMENTERS, + PROP_TRANSLATOR_CREDITS, + PROP_COPYRIGHT, + PROP_LICENSE_TYPE, + PROP_LICENSE, + LAST_PROP, +}; + +static GParamSpec *props[LAST_PROP]; + +enum { + SIGNAL_ACTIVATE_LINK, + SIGNAL_LAST_SIGNAL, +}; + +static guint signals[SIGNAL_LAST_SIGNAL]; + +static void +free_credit_section (CreditsSection *section) +{ + g_free (section->name); + g_strfreev (section->people); + g_free (section); +} + +static void +free_legal_section (LegalSection *section) +{ + g_free (section->title); + g_free (section->license); + g_free (section); +} + +static gboolean +boolean_or (AdwAboutWindow *self, + int number, + ...) +{ + va_list args; + + va_start (args, number); + + for (gsize i = 0; i < number; i++) + if (va_arg (args, gboolean)) { + va_end (args); + + return TRUE; + } + + va_end (args); + + return FALSE; +} + +static gboolean +string_is_not_empty (AdwAboutWindow *self, + const char *string) +{ + return string && string[0]; +} + +static char * +get_headerbar_name (AdwAboutWindow *self, + double value) +{ + double threshold = 0; + + if (self->application_icon && *self->application_icon && + self->application_name && *self->application_name) + threshold = HEADERBAR_STYLE_THRESHOLD; + + return g_strdup (value > threshold ? "regular" : "top"); +} + +static inline void +activate_link (AdwAboutWindow *self, + const char *uri) +{ + gboolean ret = FALSE; + g_signal_emit (self, signals[SIGNAL_ACTIVATE_LINK], 0, uri, &ret); +} + +static gboolean +activate_link_cb (AdwAboutWindow *self, + const char *uri) +{ + activate_link (self, uri); + + return GDK_EVENT_STOP; +} + +static gboolean +activate_link_default_cb (AdwAboutWindow *self, + const char *uri) +{ + /* FIXME: We need our own alternative that uses AdwMessageDialog for errors */ + gtk_show_uri (GTK_WINDOW (self), uri, GDK_CURRENT_TIME); + + return GDK_EVENT_STOP; +} + +/* Adapted from text_buffer_new() in gtkaboutdialog.c */ +static void +parse_person (char *person, + char **name, + char **link, + gboolean *is_email) +{ + + const char *q1, *q2, *r1, *r2; + + q1 = strchr (person, '<'); + q2 = q1 ? strchr (q1, '>') : NULL; + r1 = strstr (person, "http://"); + r2 = strstr (person, "https://"); + + if (!r1 || (r1 && r2 && r2 < r1)) + r1 = r2; + if (r1) { + r2 = strpbrk (r1, " \n\t>"); + if (!r2) + r2 = strchr (r1, '\0'); + } else { + r2 = NULL; + } + + if (r1 && r2 && (!q1 || !q2 || (r1 <= q1 + 1))) { + q1 = r1; + q2 = r2; + } + + if (q1 && q2) { + *name = g_strndup (person, q1 - person); + *is_email = (*q1 == '<'); + + if (*is_email) + *link = g_strndup (q1 + 1, q2 - q1 - 1); + else + *link = g_strndup (q1, q2 - q1); + } else { + *name = g_strdup (person); + *link = NULL; + *is_email = FALSE; + } + + g_strstrip (*name); +} + +static void +add_credits_section (GtkWidget *box, + const char *title, + char **people) +{ + GtkWidget *group; + char **person; + + if (!people || !*people) + return; + + group = adw_preferences_group_new (); + adw_preferences_group_set_title (ADW_PREFERENCES_GROUP (group), title); + + for (person = people; *person; person++) { + GtkWidget *row; + char *name = NULL; + char *link = NULL; + gboolean is_email = FALSE; + + if (!*person) + continue; + + parse_person (*person, &name, &link, &is_email); + + row = adw_action_row_new (); + adw_preferences_row_set_use_markup (ADW_PREFERENCES_ROW (row), FALSE); + adw_preferences_row_set_title (ADW_PREFERENCES_ROW (row), name); + adw_preferences_group_add (ADW_PREFERENCES_GROUP (group), row); + + if (link) { + GtkWidget *image = gtk_image_new (); + + if (is_email) + gtk_image_set_from_icon_name (GTK_IMAGE (image), "adw-mail-send-symbolic"); + else + gtk_image_set_from_icon_name (GTK_IMAGE (image), "adw-external-link-symbolic"); + + adw_action_row_add_suffix (ADW_ACTION_ROW (row), image); + gtk_list_box_row_set_activatable (GTK_LIST_BOX_ROW (row), TRUE); + gtk_actionable_set_action_name (GTK_ACTIONABLE (row), "about.show-url"); + + if (is_email) { + char *escaped = g_uri_escape_string (link, NULL, FALSE); + char *mailto = g_strconcat ("mailto:", escaped, NULL); + + gtk_actionable_set_action_target (GTK_ACTIONABLE (row), "s", mailto); + + g_free (mailto); + g_free (escaped); + } else { + gtk_actionable_set_action_target (GTK_ACTIONABLE (row), "s", link); + } + + gtk_widget_set_tooltip_text (row, link); + } + + g_free (name); + g_free (link); + } + + gtk_box_append (GTK_BOX (box), group); +} + +static void +update_credits (AdwAboutWindow *self) +{ + GtkWidget *widget; + char **translators; + GSList *l; + + while ((widget = gtk_widget_get_first_child (self->credits_box))) + gtk_box_remove (GTK_BOX (self->credits_box), widget); + + if (self->translator_credits && + g_strcmp0 (self->translator_credits, "translator_credits") && + g_strcmp0 (self->translator_credits, "translator-credits")) + translators = g_strsplit (self->translator_credits, "\n", 0); + else + translators = NULL; + + add_credits_section (self->credits_box, _("Code by"), self->developers); + add_credits_section (self->credits_box, _("Design by"), self->designers); + add_credits_section (self->credits_box, _("Artwork by"), self->artists); + add_credits_section (self->credits_box, _("Documentation by"), self->documenters); + add_credits_section (self->credits_box, _("Translated by"), translators); + + for (l = self->credit_sections; l; l = l->next) { + CreditsSection *section = l->data; + + add_credits_section (self->credits_box, section->name, section->people); + } + + g_strfreev (translators); + + gtk_widget_set_visible (self->credits_box, + !!gtk_widget_get_first_child (self->credits_box)); +} + +static char * +get_license_text (GtkLicense license_type, + const char *license) +{ + if (license_type == GTK_LICENSE_UNKNOWN) + return NULL; + + if (license_type == GTK_LICENSE_CUSTOM) + return g_strdup (license); + + /* Translators: this is the license preamble; the string at the end + * contains the name of the license as link text. + */ + return g_strdup_printf (_("This application comes with absolutely no warranty. See the %s for details."), + gtk_license_info[license_type].url, + _(gtk_license_info[license_type].name)); +} + +static void +append_legal_section (AdwAboutWindow *self, + LegalSection *section, + gboolean force_title) +{ + GtkWidget *label; + char *license; + + if (force_title) + g_assert (section->title); + + license = get_license_text (section->license_type, section->license); + + if ((!section->copyright || !*section->copyright) && + (!license || !*license) && !force_title) { + g_free (license); + + return; + } + + if (section->title && *section->title) { + label = gtk_label_new (section->title); + gtk_label_set_wrap (GTK_LABEL (label), TRUE); + gtk_label_set_wrap_mode (GTK_LABEL (label), PANGO_WRAP_WORD_CHAR); + gtk_label_set_xalign (GTK_LABEL (label), 0); + gtk_widget_add_css_class (label, "heading"); + gtk_box_append (GTK_BOX (self->legal_box), label); + } + + if ((!section->copyright || !*section->copyright) && + (!license || !*license)) { + g_free (license); + + return; + } + + label = gtk_label_new (NULL); + gtk_label_set_wrap (GTK_LABEL (label), TRUE); + gtk_label_set_wrap_mode (GTK_LABEL (label), PANGO_WRAP_WORD_CHAR); + gtk_label_set_xalign (GTK_LABEL (label), 0); + gtk_label_set_selectable (GTK_LABEL (label), TRUE); + gtk_widget_add_css_class (label, "body"); + g_signal_connect_swapped (label, "activate-link", G_CALLBACK (activate_link_cb), self); + + if (section->copyright && *section->copyright && license && *license) { + char *text = g_strconcat (section->copyright, "\n\n", license, NULL); + + gtk_label_set_markup (GTK_LABEL (label), text); + + g_free (text); + } else if (section->copyright && *section->copyright) { + gtk_label_set_markup (GTK_LABEL (label), section->copyright); + } else { + gtk_label_set_markup (GTK_LABEL (label), license); + } + + gtk_box_append (GTK_BOX (self->legal_box), label); + + g_free (license); +} + +static void +update_legal (AdwAboutWindow *self) +{ + LegalSection default_section; + GtkWidget *widget; + GSList *l; + + while ((widget = gtk_widget_get_first_child (self->legal_box))) + gtk_box_remove (GTK_BOX (self->legal_box), widget); + + /* We only want to show the default title if there's more than one section */ + if (self->legal_sections) + default_section.title = _("This Application"); + else + default_section.title = NULL; + + default_section.copyright = self->copyright; + default_section.license_type = self->license_type; + default_section.license = self->license; + append_legal_section (self, &default_section, FALSE); + + for (l = self->legal_sections; l; l = l->next) + append_legal_section (self, l->data, TRUE); + + gtk_widget_set_visible (self->legal_box, + !!gtk_widget_get_first_child (self->legal_box)); +} + +typedef enum { + STATE_NONE, + STATE_PARAGRAPH, + STATE_UNORDERED_LIST, + STATE_UNORDERED_ITEM, + STATE_ORDERED_LIST, + STATE_ORDERED_ITEM, +} ReleaseNotesState; + +typedef struct { + GtkTextBuffer *buffer; + GtkTextIter iter; + + ReleaseNotesState state; + int n_item; + int section_start; + int paragraph_start; + gboolean last_trailing_space; +} ReleaseNotesParserData; + +static void +start_element_handler (GMarkupParseContext *context, + const char *element_name, + const char **attribute_names, + const char **attribute_values, + gpointer user_data, + GError **error) +{ + ReleaseNotesParserData *pdata = user_data; + + switch (pdata->state) { + case STATE_NONE: + if (!g_strcmp0 (element_name, "p")) { + pdata->state = STATE_PARAGRAPH; + pdata->paragraph_start = gtk_text_iter_get_offset (&pdata->iter); + } + + if (!g_strcmp0 (element_name, "ul")) + pdata->state = STATE_UNORDERED_LIST; + + if (!g_strcmp0 (element_name, "ol")) + pdata->state = STATE_ORDERED_LIST; + + if (pdata->state != STATE_NONE) { + pdata->section_start = gtk_text_iter_get_offset (&pdata->iter); + break; + } + + g_set_error (error, + G_MARKUP_ERROR, + G_MARKUP_ERROR_UNKNOWN_ELEMENT, + "Unexpected element '%s'", + element_name); + break; + + case STATE_PARAGRAPH: + case STATE_UNORDERED_ITEM: + case STATE_ORDERED_ITEM: + if (!g_strcmp0 (element_name, "em") || + !g_strcmp0 (element_name, "code")) { + break; + } + + g_set_error (error, + G_MARKUP_ERROR, + G_MARKUP_ERROR_UNKNOWN_ELEMENT, + "Unexpected element '%s'", + element_name); + break; + + case STATE_UNORDERED_LIST: + case STATE_ORDERED_LIST: + if (!g_strcmp0 (element_name, "li")) { + char *bullet; + + if (pdata->n_item > 1) + gtk_text_buffer_insert (pdata->buffer, &pdata->iter, "\n", -1); + + if (pdata->state == STATE_ORDERED_LIST) { + pdata->state = STATE_ORDERED_ITEM; + bullet = g_strdup_printf ("%d. ", pdata->n_item); + } else { + pdata->state = STATE_UNORDERED_ITEM; + bullet = g_strdup ("• "); + } + + gtk_text_buffer_insert_with_tags_by_name (pdata->buffer, &pdata->iter, bullet, -1, "bullet", NULL); + pdata->paragraph_start = gtk_text_iter_get_offset (&pdata->iter); + + g_free (bullet); + + break; + } + + g_set_error (error, + G_MARKUP_ERROR, + G_MARKUP_ERROR_UNKNOWN_ELEMENT, + "Unexpected element '%s'", + element_name); + break; + + default: + g_assert_not_reached (); + } + + if (!g_strcmp0 (element_name, "em") || + !g_strcmp0 (element_name, "code") || + !g_strcmp0 (element_name, "ul") || + !g_strcmp0 (element_name, "ol") || + !g_strcmp0 (element_name, "li")) { + g_markup_collect_attributes (element_name, + attribute_names, + attribute_values, + error, + G_MARKUP_COLLECT_INVALID, + NULL); + return; + } + + /* We don't support attributes anywhere, so error out if we encounter any. */ + g_markup_collect_attributes (element_name, + attribute_names, + attribute_values, + error, + G_MARKUP_COLLECT_INVALID, + NULL); +} + +static void +end_element_handler (GMarkupParseContext *context, + const char *element_name, + gpointer user_data, + GError **error) +{ + ReleaseNotesParserData *pdata = user_data; + + if (!g_strcmp0 (element_name, "p") || + !g_strcmp0 (element_name, "ul") || + !g_strcmp0 (element_name, "ol")) { + + if (pdata->section_start != gtk_text_iter_get_offset (&pdata->iter)) { + gtk_text_buffer_insert (pdata->buffer, &pdata->iter, "\n", -1); + + if (pdata->section_start > 0 && !g_strcmp0 (element_name, "p")) { + GtkTextIter start_iter; + gtk_text_buffer_get_iter_at_offset (pdata->buffer, &start_iter, pdata->section_start); + gtk_text_buffer_apply_tag_by_name (pdata->buffer, "section", &start_iter, &pdata->iter); + } + } + + pdata->state = STATE_NONE; + pdata->section_start = -1; + pdata->paragraph_start = -1; + pdata->n_item = 1; + return; + } + + if (!g_strcmp0 (element_name, "li")) { + if (pdata->state == STATE_UNORDERED_ITEM) + pdata->state = STATE_UNORDERED_LIST; + else if (pdata->state == STATE_ORDERED_ITEM) + pdata->state = STATE_ORDERED_LIST; + else + g_assert_not_reached (); + + if (pdata->section_start > 0 && pdata->n_item == 1) { + GtkTextIter start_iter; + gtk_text_buffer_get_iter_at_offset (pdata->buffer, &start_iter, + pdata->section_start); + gtk_text_buffer_apply_tag_by_name (pdata->buffer, "section", &start_iter, &pdata->iter); + } + + pdata->n_item++; + pdata->paragraph_start = -1; + return; + } +} + +static void +text_handler (GMarkupParseContext *context, + const char *text, + gsize text_len, + gpointer user_data, + GError **error) +{ + ReleaseNotesParserData *pdata = user_data; + const char *element; + char *escaped; + static GRegex *regex = NULL; + gboolean leading_space; + gboolean trailing_space; + + if (pdata->state != STATE_PARAGRAPH && + pdata->state != STATE_UNORDERED_ITEM && + pdata->state != STATE_ORDERED_ITEM) + return; + + /* Replace random amounts of spaces and newlines with single spaces */ + if (!regex) { + GError *regex_error = NULL; + + regex = g_regex_new ("\\s+", G_REGEX_OPTIMIZE, 0, ®ex_error); + + if (regex_error) { + g_error ("Couldn't compile regex: %s", regex_error->message); + g_error_free (regex_error); + } + } + + element = g_markup_parse_context_get_element (context); + + escaped = g_regex_replace_literal (regex, text, text_len, 0, " ", 0, error); + + if (*error) + return; + + if (!*escaped) { + g_free (escaped); + return; + } + + leading_space = *escaped == ' '; + trailing_space = escaped[strlen (escaped) - 1] == ' '; + + g_strstrip (escaped); + + /* This might have made the string empty, skip it in that case */ + if (!*escaped) { + g_free (escaped); + pdata->last_trailing_space = trailing_space; + return; + } + + /* We've got rid of inner spaces before and . Bring them back */ + if ((leading_space || pdata->last_trailing_space) && + pdata->paragraph_start != gtk_text_iter_get_offset (&pdata->iter)) + gtk_text_buffer_insert (pdata->buffer, &pdata->iter, " ", -1); + + if (!g_strcmp0 (element, "em") || !g_strcmp0 (element, "code")) + gtk_text_buffer_insert_with_tags_by_name (pdata->buffer, &pdata->iter, + escaped, -1, element, NULL); + else + gtk_text_buffer_insert (pdata->buffer, &pdata->iter, escaped, -1); + + pdata->last_trailing_space = trailing_space; + + g_free (escaped); +} + +static const GMarkupParser markup_parser = +{ + start_element_handler, + end_element_handler, + text_handler, + NULL, + NULL +}; + +static void +update_release_notes (AdwAboutWindow *self) +{ + GMarkupParseContext *context; + ReleaseNotesParserData pdata; + GError *error = NULL; + GtkTextIter end_iter; + const char *version = NULL; + + gtk_text_buffer_set_text (self->release_notes_buffer, "", -1); + + if (!self->release_notes || !*self->release_notes) + return; + + pdata.buffer = self->release_notes_buffer; + gtk_text_buffer_get_start_iter (self->release_notes_buffer, &pdata.iter); + + if (self->release_notes_version && *self->release_notes_version) + version = self->release_notes_version; + else if (self->version && *self->version) + version = self->version; + + if (version) { + char *heading = g_strdup_printf (_("Version %s"), version); + + gtk_text_buffer_insert_with_tags_by_name (self->release_notes_buffer, + &pdata.iter, heading, -1, + "heading", NULL); + gtk_text_buffer_insert (self->release_notes_buffer, &pdata.iter, "\n", -1); + + g_free (heading); + } + + pdata.state = STATE_NONE; + pdata.n_item = 0; + pdata.last_trailing_space = FALSE; + context = g_markup_parse_context_new (&markup_parser, 0, &pdata, NULL); + + if (!g_markup_parse_context_parse (context, self->release_notes, -1, &error) || + !g_markup_parse_context_end_parse (context, &error)) { + int line_number, char_number; + char *position; + g_markup_parse_context_get_position (context, &line_number, &char_number); + + g_critical ("Unable to parse release notes: %s at line %d, char %d", error->message, line_number, char_number); + + gtk_text_buffer_set_text (self->release_notes_buffer, "", -1); + gtk_text_buffer_get_start_iter (self->release_notes_buffer, &pdata.iter); + + gtk_text_buffer_insert (self->release_notes_buffer, &pdata.iter, _("Unable to parse release notes:"), -1); + gtk_text_buffer_insert (self->release_notes_buffer, &pdata.iter, "\n", -1); + + gtk_text_buffer_insert (self->release_notes_buffer, &pdata.iter, error->message, -1); + gtk_text_buffer_insert (self->release_notes_buffer, &pdata.iter, "\n", -1); + + position = g_strdup_printf (_("Line: %d, character: %d"), line_number, char_number); + gtk_text_buffer_insert (self->release_notes_buffer, &pdata.iter, position, -1); + + g_markup_parse_context_free (context); + g_error_free (error); + g_free (position); + + return; + } + + gtk_text_iter_backward_chars (&pdata.iter, 2); + gtk_text_buffer_get_end_iter (self->release_notes_buffer, &end_iter); + gtk_text_buffer_delete (self->release_notes_buffer, &pdata.iter, &end_iter); + + g_markup_parse_context_free (context); +} + +static void +update_links (AdwAboutWindow *self) +{ + gboolean has_website = self->website && *self->website; + gboolean has_comments = self->comments && *self->comments; + gboolean show_details = has_comments || self->has_custom_links; + + gtk_widget_set_visible (self->website_row, has_website && !show_details); + + gtk_widget_set_visible (self->details_website_row, has_website && show_details); + gtk_widget_set_visible (self->links_group, + (has_website && has_comments) || self->has_custom_links); +} + +static void +adw_about_window_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + AdwAboutWindow *self = ADW_ABOUT_WINDOW (object); + + switch (prop_id) { + case PROP_APPLICATION_ICON: + g_value_set_string (value, adw_about_window_get_application_icon (self)); + break; + case PROP_APPLICATION_NAME: + g_value_set_string (value, adw_about_window_get_application_name (self)); + break; + case PROP_DEVELOPER_NAME: + g_value_set_string (value, adw_about_window_get_developer_name (self)); + break; + case PROP_VERSION: + g_value_set_string (value, adw_about_window_get_version (self)); + break; + case PROP_RELEASE_NOTES_VERSION: + g_value_set_string (value, adw_about_window_get_release_notes_version (self)); + break; + case PROP_RELEASE_NOTES: + g_value_set_string (value, adw_about_window_get_release_notes (self)); + break; + case PROP_COMMENTS: + g_value_set_string (value, adw_about_window_get_comments (self)); + break; + case PROP_WEBSITE: + g_value_set_string (value, adw_about_window_get_website (self)); + break; + case PROP_SUPPORT_URL: + g_value_set_string (value, adw_about_window_get_support_url (self)); + break; + case PROP_ISSUE_URL: + g_value_set_string (value, adw_about_window_get_issue_url (self)); + break; + case PROP_DEBUG_INFO: + g_value_set_string (value, adw_about_window_get_debug_info (self)); + break; + case PROP_DEBUG_INFO_FILENAME: + g_value_set_string (value, adw_about_window_get_debug_info_filename (self)); + break; + case PROP_DEVELOPERS: + g_value_set_boxed (value, adw_about_window_get_developers (self)); + break; + case PROP_DESIGNERS: + g_value_set_boxed (value, adw_about_window_get_designers (self)); + break; + case PROP_ARTISTS: + g_value_set_boxed (value, adw_about_window_get_artists (self)); + break; + case PROP_DOCUMENTERS: + g_value_set_boxed (value, adw_about_window_get_documenters (self)); + break; + case PROP_TRANSLATOR_CREDITS: + g_value_set_string (value, adw_about_window_get_translator_credits (self)); + break; + case PROP_COPYRIGHT: + g_value_set_string (value, adw_about_window_get_copyright (self)); + break; + case PROP_LICENSE_TYPE: + g_value_set_enum (value, adw_about_window_get_license_type (self)); + break; + case PROP_LICENSE: + g_value_set_string (value, adw_about_window_get_license (self)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +adw_about_window_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + AdwAboutWindow *self = ADW_ABOUT_WINDOW (object); + + switch (prop_id) { + case PROP_APPLICATION_ICON: + adw_about_window_set_application_icon (self, g_value_get_string (value)); + break; + case PROP_APPLICATION_NAME: + adw_about_window_set_application_name (self, g_value_get_string (value)); + break; + case PROP_DEVELOPER_NAME: + adw_about_window_set_developer_name (self, g_value_get_string (value)); + break; + case PROP_VERSION: + adw_about_window_set_version (self, g_value_get_string (value)); + break; + case PROP_RELEASE_NOTES_VERSION: + adw_about_window_set_release_notes_version (self, g_value_get_string (value)); + break; + case PROP_RELEASE_NOTES: + adw_about_window_set_release_notes (self, g_value_get_string (value)); + break; + case PROP_COMMENTS: + adw_about_window_set_comments (self, g_value_get_string (value)); + break; + case PROP_WEBSITE: + adw_about_window_set_website (self, g_value_get_string (value)); + break; + case PROP_SUPPORT_URL: + adw_about_window_set_support_url (self, g_value_get_string (value)); + break; + case PROP_ISSUE_URL: + adw_about_window_set_issue_url (self, g_value_get_string (value)); + break; + case PROP_DEBUG_INFO: + adw_about_window_set_debug_info (self, g_value_get_string (value)); + break; + case PROP_DEBUG_INFO_FILENAME: + adw_about_window_set_debug_info_filename (self, g_value_get_string (value)); + break; + case PROP_DEVELOPERS: + adw_about_window_set_developers (self, g_value_get_boxed (value)); + break; + case PROP_DESIGNERS: + adw_about_window_set_designers (self, g_value_get_boxed (value)); + break; + case PROP_DOCUMENTERS: + adw_about_window_set_documenters (self, g_value_get_boxed (value)); + break; + case PROP_ARTISTS: + adw_about_window_set_artists (self, g_value_get_boxed (value)); + break; + case PROP_TRANSLATOR_CREDITS: + adw_about_window_set_translator_credits (self, g_value_get_string (value)); + break; + case PROP_COPYRIGHT: + adw_about_window_set_copyright (self, g_value_get_string (value)); + break; + case PROP_LICENSE_TYPE: + adw_about_window_set_license_type (self, g_value_get_enum (value)); + break; + case PROP_LICENSE: + adw_about_window_set_license (self, g_value_get_string (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +adw_about_window_finalize (GObject *object) +{ + AdwAboutWindow *self = ADW_ABOUT_WINDOW (object); + + g_free (self->application_icon); + g_free (self->application_name); + g_free (self->developer_name); + g_free (self->version); + g_free (self->release_notes_version); + g_free (self->release_notes); + g_free (self->comments); + g_free (self->website); + g_free (self->support_url); + g_free (self->issue_url); + g_free (self->debug_info); + g_free (self->debug_info_filename); + + g_strfreev (self->developers); + g_strfreev (self->designers); + g_strfreev (self->artists); + g_strfreev (self->documenters); + g_free (self->translator_credits); + g_slist_free_full (self->credit_sections, (GDestroyNotify) free_credit_section); + + g_free (self->copyright); + g_free (self->license); + g_slist_free_full (self->legal_sections, (GDestroyNotify) free_legal_section); + + G_OBJECT_CLASS (adw_about_window_parent_class)->finalize (object); +} + +static void +back_cb (AdwAboutWindow *self) +{ + adw_leaflet_navigate (ADW_LEAFLET (self->leaflet), ADW_NAVIGATION_DIRECTION_BACK); +} + +static void +subpage_cb (AdwAboutWindow *self, + const char *action_name, + GVariant *params) +{ + const char *name = g_variant_get_string (params, NULL); + + gtk_stack_set_visible_child_name (GTK_STACK (self->subpage_stack), name); + adw_leaflet_navigate (ADW_LEAFLET (self->leaflet), ADW_NAVIGATION_DIRECTION_FORWARD); +} + +static void +show_url_cb (AdwAboutWindow *self, + const char *action_name, + GVariant *params) +{ + const char *url = g_variant_get_string (params, NULL); + + activate_link (self, url); +} + +static void +show_url_property_cb (AdwAboutWindow *self, + const char *action_name, + GVariant *params) +{ + const char *property = g_variant_get_string (params, NULL); + char *url; + + g_object_get (self, property, &url, NULL); + + activate_link (self, url); + + g_free (url); +} + +static void +copy_property_cb (AdwAboutWindow *self, + const char *action_name, + GVariant *params) +{ + const char *property = g_variant_get_string (params, NULL); + char *value; + + g_object_get (self, property, &value, NULL); + + if (value && *value) { + GdkClipboard *clipboard = gtk_widget_get_clipboard (GTK_WIDGET (self)); + + gdk_clipboard_set_text (clipboard, value); + + adw_toast_overlay_add_toast (ADW_TOAST_OVERLAY (self->toast_overlay), + adw_toast_new (_("Copied to clipboard"))); + } + + g_free (value); +} + +static void +debug_cb (AdwAboutWindow *self) +{ + adw_leaflet_navigate (ADW_LEAFLET (self->leaflet), ADW_NAVIGATION_DIRECTION_FORWARD); +} + +static void +save_debug_info_response_cb (GtkFileChooser *chooser, + GtkResponseType response, + AdwAboutWindow *self) +{ + gtk_native_dialog_hide (GTK_NATIVE_DIALOG (chooser)); + + if (response == GTK_RESPONSE_ACCEPT) { + GFile *file = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (chooser)); + GError *error = NULL; + + g_file_replace_contents (file, + self->debug_info, + strlen (self->debug_info), + NULL, + FALSE, + G_FILE_CREATE_NONE, + NULL, + NULL, + &error); + + if (error) { + GtkWidget *dialog = adw_message_dialog_new (GTK_WINDOW (self), + _("Unable to save debugging information"), + NULL); + adw_message_dialog_format_body (ADW_MESSAGE_DIALOG (dialog), + "%s", error->message); + adw_message_dialog_add_response (ADW_MESSAGE_DIALOG (dialog), + "close", _("Close")); + + gtk_window_present (GTK_WINDOW (dialog)); + + g_error_free (error); + } + + g_object_unref (file); + } + + gtk_native_dialog_destroy (GTK_NATIVE_DIALOG (chooser)); +} + +static void +save_debug_info_cb (AdwAboutWindow *self) +{ + GtkFileChooserNative *chooser; + + chooser = gtk_file_chooser_native_new (_("Save debugging information"), + GTK_WINDOW (self), + GTK_FILE_CHOOSER_ACTION_SAVE, + _("_Save"), + _("_Cancel")); + + if (self->debug_info_filename && *self->debug_info_filename) + gtk_file_chooser_set_current_name (GTK_FILE_CHOOSER (chooser), self->debug_info_filename); + + gtk_native_dialog_set_modal (GTK_NATIVE_DIALOG (chooser), TRUE); + + g_signal_connect (chooser, "response", G_CALLBACK (save_debug_info_response_cb), self); + + gtk_native_dialog_show (GTK_NATIVE_DIALOG (chooser)); +} + +static gboolean +close_cb (GtkWidget *widget, + GVariant *args, + gpointer user_data) +{ + AdwAboutWindow *self = ADW_ABOUT_WINDOW (widget); + + if (adw_leaflet_navigate (ADW_LEAFLET (self->leaflet), ADW_NAVIGATION_DIRECTION_BACK)) + return GDK_EVENT_STOP; + + gtk_window_close (GTK_WINDOW (self)); + + return GDK_EVENT_STOP; +} + +static gboolean +save_debug_info_shortcut_cb (GtkWidget *widget, + GVariant *args, + gpointer user_data) +{ + AdwAboutWindow *self = ADW_ABOUT_WINDOW (widget); + + if (adw_leaflet_get_visible_child (ADW_LEAFLET (self->leaflet)) != self->debug_info_page) + return GDK_EVENT_PROPAGATE; + + save_debug_info_cb (self); + + return GDK_EVENT_STOP; +} + +static void +adw_about_window_class_init (AdwAboutWindowClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + object_class->finalize = adw_about_window_finalize; + object_class->get_property = adw_about_window_get_property; + object_class->set_property = adw_about_window_set_property; + + /** + * AdwAboutWindow:application-icon: (attributes org.gtk.Property.get=adw_about_window_get_application_icon org.gtk.Property.set=adw_about_window_set_application_icon) + * + * The name of the application icon. + * + * The icon is displayed at the top of the main page. + * + * Since: 1.2 + */ + props[PROP_APPLICATION_ICON] = + g_param_spec_string ("application-icon", NULL, NULL, + "", + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY); + + /** + * AdwAboutWindow:application-name: (attributes org.gtk.Property.get=adw_about_window_get_application_name org.gtk.Property.set=adw_about_window_set_application_name) + * + * The name of the application. + * + * The name is displayed at the top of the main page. + * + * Since: 1.2 + */ + props[PROP_APPLICATION_NAME] = + g_param_spec_string ("application-name", NULL, NULL, + "", + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY); + + /** + * AdwAboutWindow:developer-name: (attributes org.gtk.Property.get=adw_about_window_get_developer_name org.gtk.Property.set=adw_about_window_set_developer_name) + * + * The developer name. + * + * The developer name is displayed on the main page, under the application + * name. + * + * If the application is developed by multiple people, the developer name can + * be set to values like "AppName team", "AppName developers" or + * "The AppName project", and the individual contributors can be listed on the + * Credits page, with [property@AboutWindow:developers] and related + * properties. + * + * Since: 1.2 + */ + props[PROP_DEVELOPER_NAME] = + g_param_spec_string ("developer-name", NULL, NULL, + "", + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY); + + /** + * AdwAboutWindow:version: (attributes org.gtk.Property.get=adw_about_window_get_version org.gtk.Property.set=adw_about_window_set_version) + * + * The version of the application. + * + * The version is displayed on the main page. + * + * If [property@AboutWindow:release-notes-version] is not set, the version + * will also be displayed above the release notes on the What's New page. + * + * Since: 1.2 + */ + props[PROP_VERSION] = + g_param_spec_string ("version", NULL, NULL, + "", + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY); + + /** + * AdwAboutWindow:release-notes-version: (attributes org.gtk.Property.get=adw_about_window_get_release_notes_version org.gtk.Property.set=adw_about_window_set_release_notes_version) + * + * The version described by the application's release notes. + * + * The release notes version is displayed on the What's New page, above the + * release notes. + * + * If not set, [property@AboutWindow:version] will be used instead. + * + * For example, an application with the current version 2.0.2 might want to + * keep the release notes from 2.0.0, and set the release notes version + * accordingly. + * + * See [property@AboutWindow:release-notes]. + * + * Since: 1.2 + */ + props[PROP_RELEASE_NOTES_VERSION] = + g_param_spec_string ("release-notes-version", NULL, NULL, + "", + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY); + + /** + * AdwAboutWindow:release-notes: (attributes org.gtk.Property.get=adw_about_window_get_release_notes org.gtk.Property.set=adw_about_window_set_release_notes) + * + * The release notes of the application. + * + * Release notes are displayed on the the What's New page. + * + * Release notes are formatted the same way as + * [AppStream descriptions](https://freedesktop.org/software/appstream/docs/chap-Metadata.html#tag-description). + * + * The supported formatting options are: + * + * * Paragraph (`

      `) + * * Ordered list (`

        `), with list items (`
      1. `) + * * Unordered list (`
          `), with list items (`
        • `) + * + * Within paragraphs and list items, emphasis (``) and inline code + * (``) text styles are supported. The emphasis is rendered in italic, + * while inline code is shown in a monospaced font. + * + * Any text outside paragraphs or list items is ignored. + * + * Nested lists are not supported. + * + * `AdwAboutWindow` displays the version above the release notes. If set, the + * [property@AboutWindow:release-notes-version] of the property will be used + * as the version; otherwise, [property@AboutWindow:version] is used. + * + * Since: 1.2 + */ + props[PROP_RELEASE_NOTES] = + g_param_spec_string ("release-notes", NULL, NULL, + "", + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY); + + /** + * AdwAboutWindow:comments: (attributes org.gtk.Property.get=adw_about_window_get_comments org.gtk.Property.set=adw_about_window_set_comments) + * + * The comments about the application. + * + * Comments will be shown on the Details page, above links. + * + * Unlike [property@Gtk.AboutDialog:comments], this string can be long and + * detailed. It can also contain links and Pango markup. + * + * Since: 1.2 + */ + props[PROP_COMMENTS] = + g_param_spec_string ("comments", NULL, NULL, + "", + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY); + + /** + * AdwAboutWindow:website: (attributes org.gtk.Property.get=adw_about_window_get_website org.gtk.Property.set=adw_about_window_set_website) + * + * The URL of the application's website. + * + * Website is displayed on the Details page, below comments, or on the main + * page if the Details page doesn't have any other content. + * + * Applications can add other links below, see [method@AboutWindow.add_link]. + * + * Since: 1.2 + */ + props[PROP_WEBSITE] = + g_param_spec_string ("website", NULL, NULL, + "", + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY); + + /** + * AdwAboutWindow:support-url: (attributes org.gtk.Property.get=adw_about_window_get_support_url org.gtk.Property.set=adw_about_window_set_support_url) + * + * The URL of the application's support page. + * + * The support page link is displayed on the main page. + * + * Since: 1.2 + */ + props[PROP_SUPPORT_URL] = + g_param_spec_string ("support-url", NULL, NULL, + "", + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY); + + /** + * AdwAboutWindow:issue-url: (attributes org.gtk.Property.get=adw_about_window_get_issue_url org.gtk.Property.set=adw_about_window_set_issue_url) + * + * The URL for the application's issue tracker. + * + * The issue tracker link is displayed on the main page. + * + * Since: 1.2 + */ + props[PROP_ISSUE_URL] = + g_param_spec_string ("issue-url", NULL, NULL, + "", + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY); + + /** + * AdwAboutWindow:debug-info: (attributes org.gtk.Property.get=adw_about_window_get_debug_info org.gtk.Property.set=adw_about_window_set_debug_info) + * + * The debug information. + * + * Debug information will be shown on the Troubleshooting page. It's intended + * to be attached to issue reports when reporting issues against the + * application. + * + * `AdwAboutWindow` provides a quick way to save debug information to a file. + * When saving, [property@AboutWindow:debug-info-filename] would be used as + * the suggested filename. + * + * Debug information cannot contain markup or links. + * + * Since: 1.2 + */ + props[PROP_DEBUG_INFO] = + g_param_spec_string ("debug-info", NULL, NULL, + "", + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY); + + /** + * AdwAboutWindow:debug-info-filename: (attributes org.gtk.Property.get=adw_about_window_get_debug_info_filename org.gtk.Property.set=adw_about_window_set_debug_info_filename) + * + * The debug information filename. + * + * It will be used as the suggested filename when saving debug information to + * a file. + * + * See [property@AboutWindow:debug-info]. + * + * Since: 1.2 + */ + props[PROP_DEBUG_INFO_FILENAME] = + g_param_spec_string ("debug-info-filename", NULL, NULL, + "", + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY); + + /** + * AdwAboutWindow:developers: (attributes org.gtk.Property.get=adw_about_window_get_developers org.gtk.Property.set=adw_about_window_set_developers) + * + * The list of developers of the application. + * + * It will be displayed on the Credits page. + * + * Each name may contain email addresses and URLs, see the introduction for + * more details. + * + * See also: + * + * * [property@AboutWindow:designers] + * * [property@AboutWindow:artists] + * * [property@AboutWindow:documenters] + * * [property@AboutWindow:translator-credits] + * * [method@AboutWindow.add_credit_section] + * * [method@AboutWindow.add_acknowledgement_section] + * + * Since: 1.2 + */ + props[PROP_DEVELOPERS] = + g_param_spec_boxed ("developers", NULL, NULL, + G_TYPE_STRV, + G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY); + + /** + * AdwAboutWindow:designers: (attributes org.gtk.Property.get=adw_about_window_get_designers org.gtk.Property.set=adw_about_window_set_designers) + * + * The list of designers of the application. + * + * It will be displayed on the Credits page. + * + * Each name may contain email addresses and URLs, see the introduction for + * more details. + * + * See also: + * + * * [property@AboutWindow:developers] + * * [property@AboutWindow:artists] + * * [property@AboutWindow:documenters] + * * [property@AboutWindow:translator-credits] + * * [method@AboutWindow.add_credit_section] + * * [method@AboutWindow.add_acknowledgement_section] + * + * Since: 1.2 + */ + props[PROP_DESIGNERS] = + g_param_spec_boxed ("designers", NULL, NULL, + G_TYPE_STRV, + G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY); + + /** + * AdwAboutWindow:artists: (attributes org.gtk.Property.get=adw_about_window_get_artists org.gtk.Property.set=adw_about_window_set_artists) + * + * The list of artists of the application. + * + * It will be displayed on the Credits page. + * + * Each name may contain email addresses and URLs, see the introduction for + * more details. + * + * See also: + * + * * [property@AboutWindow:developers] + * * [property@AboutWindow:designers] + * * [property@AboutWindow:documenters] + * * [property@AboutWindow:translator-credits] + * * [method@AboutWindow.add_credit_section] + * * [method@AboutWindow.add_acknowledgement_section] + * + * Since: 1.2 + */ + props[PROP_ARTISTS] = + g_param_spec_boxed ("artists", NULL, NULL, + G_TYPE_STRV, + G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY); + + /** + * AdwAboutWindow:documenters: (attributes org.gtk.Property.get=adw_about_window_get_documenters org.gtk.Property.set=adw_about_window_set_documenters) + * + * The list of documenters of the application. + * + * It will be displayed on the Credits page. + * + * Each name may contain email addresses and URLs, see the introduction for + * more details. + * + * See also: + * + * * [property@AboutWindow:developers] + * * [property@AboutWindow:designers] + * * [property@AboutWindow:artists] + * * [property@AboutWindow:translator-credits] + * * [method@AboutWindow.add_credit_section] + * * [method@AboutWindow.add_acknowledgement_section] + * + * Since: 1.2 + */ + props[PROP_DOCUMENTERS] = + g_param_spec_boxed ("documenters", NULL, NULL, + G_TYPE_STRV, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY); + + /** + * AdwAboutWindow:translator-credits: (attributes org.gtk.Property.get=adw_about_window_get_translator_credits org.gtk.Property.set=adw_about_window_set_translator_credits) + * + * The translator credits string. + * + * It will be displayed on the Credits page. + * + * This string should be `"translator-credits"` or `"translator_credits"` and + * should be marked as translatable. + * + * The string may contain email addresses and URLs, see the introduction for + * more details. + * + * See also: + * + * * [property@AboutWindow:developers] + * * [property@AboutWindow:designers] + * * [property@AboutWindow:artists] + * * [property@AboutWindow:documenters] + * * [method@AboutWindow.add_credit_section] + * * [method@AboutWindow.add_acknowledgement_section] + * + * Since: 1.2 + */ + props[PROP_TRANSLATOR_CREDITS] = + g_param_spec_string ("translator-credits", NULL, NULL, + "", + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY); + + /** + * AdwAboutWindow:copyright: (attributes org.gtk.Property.get=adw_about_window_get_copyright org.gtk.Property.set=adw_about_window_set_copyright) + * + * The copyright information. + * + * This should be a short string of one or two lines, for example: + * `© 2022 Example`. + * + * The copyright information will be displayed on the Legal page, above the + * application license. + * + * [method@AboutWindow.add_legal_section] can be used to add copyright + * information for the application dependencies or other components. + * + * Since: 1.2 + */ + props[PROP_COPYRIGHT] = + g_param_spec_string ("copyright", NULL, NULL, + "", + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY); + + /** + * AdwAboutWindow:license-type: (attributes org.gtk.Property.get=adw_about_window_get_license_type org.gtk.Property.set=adw_about_window_set_license_type) + * + * The license type. + * + * Allows to set the application's license froma list of known licenses. + * + * If the application's license is not in the list, + * [property@AboutWindow:license] can be used instead. The license type will + * be automatically set to `GTK_LICENSE_CUSTOM` in that case. + * + * If set to `GTK_LICENSE_UNKNOWN`, no information will be displayed. + * + * If the license type is different from `GTK_LICENSE_CUSTOM`. + * [property@AboutWindow:license] will be cleared out. + * + * The license description will be displayed on the Legal page, below the + * copyright information. + * + * [method@AboutWindow.add_legal_section] can be used to add license + * information for the application dependencies or other components. + * + * Since: 1.2 + */ + props[PROP_LICENSE_TYPE] = + g_param_spec_enum ("license-type", NULL, NULL, + GTK_TYPE_LICENSE, + GTK_LICENSE_UNKNOWN, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY); + + /** + * AdwAboutWindow:license: (attributes org.gtk.Property.get=adw_about_window_get_license org.gtk.Property.set=adw_about_window_set_license) + * + * The license text. + * + * This can be used to set a custom text for the license if it can't be set + * via [property@AboutWindow:license-type]. + * + * When set, [property@AboutWindow:license-type] will be set to + * `GTK_LICENSE_CUSTOM`. + * + * The license text will be displayed on the Legal page, below the copyright + * information. + * + * License text can contain Pango markup and links. + * + * [method@AboutWindow.add_legal_section] can be used to add license + * information for the application dependencies or other components. + * + * Since: 1.2 + */ + props[PROP_LICENSE] = + g_param_spec_string ("license", NULL, NULL, + "", + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY); + + g_object_class_install_properties (object_class, LAST_PROP, props); + + /** + * AdwAboutWindow::activate-link: + * @self: an about window + * @uri: the URI to activate + * + * Emitted when a URL is activated. + * + * Applications may connect to it to override the default behavior, which is + * to call [func@Gtk.show_uri]. + * + * Returns: `TRUE` if the link has been activated + * + * Since: 1.0 + */ + signals[SIGNAL_ACTIVATE_LINK] = + g_signal_new ("activate-link", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + 0, + g_signal_accumulator_true_handled, + NULL, NULL, + G_TYPE_BOOLEAN, + 1, + G_TYPE_STRING); + + g_signal_override_class_handler ("activate-link", + G_TYPE_FROM_CLASS (klass), + G_CALLBACK (activate_link_default_cb)); + + gtk_widget_class_set_template_from_resource (widget_class, + "/org/gnome/Adwaita/ui/adw-about-window.ui"); + gtk_widget_class_bind_template_child (widget_class, AdwAboutWindow, leaflet); + gtk_widget_class_bind_template_child (widget_class, AdwAboutWindow, subpage_stack); + gtk_widget_class_bind_template_child (widget_class, AdwAboutWindow, toast_overlay); + gtk_widget_class_bind_template_child (widget_class, AdwAboutWindow, website_row); + gtk_widget_class_bind_template_child (widget_class, AdwAboutWindow, links_group); + gtk_widget_class_bind_template_child (widget_class, AdwAboutWindow, details_website_row); + gtk_widget_class_bind_template_child (widget_class, AdwAboutWindow, credits_box); + gtk_widget_class_bind_template_child (widget_class, AdwAboutWindow, legal_box); + gtk_widget_class_bind_template_child (widget_class, AdwAboutWindow, acknowledgements_box); + gtk_widget_class_bind_template_child (widget_class, AdwAboutWindow, debug_info_page); + gtk_widget_class_bind_template_child (widget_class, AdwAboutWindow, release_notes_buffer); + + gtk_widget_class_bind_template_callback (widget_class, boolean_or); + gtk_widget_class_bind_template_callback (widget_class, string_is_not_empty); + gtk_widget_class_bind_template_callback (widget_class, get_headerbar_name); + gtk_widget_class_bind_template_callback (widget_class, activate_link_cb); + + gtk_widget_class_install_action (widget_class, "about.back", NULL, + (GtkWidgetActionActivateFunc) back_cb); + gtk_widget_class_install_action (widget_class, "about.subpage", "s", + (GtkWidgetActionActivateFunc) subpage_cb); + gtk_widget_class_install_action (widget_class, "about.show-url", "s", + (GtkWidgetActionActivateFunc) show_url_cb); + gtk_widget_class_install_action (widget_class, "about.show-url-property", "s", + (GtkWidgetActionActivateFunc) show_url_property_cb); + gtk_widget_class_install_action (widget_class, "about.copy-property", "s", + (GtkWidgetActionActivateFunc) copy_property_cb); + gtk_widget_class_install_action (widget_class, "about.debug", NULL, + (GtkWidgetActionActivateFunc) debug_cb); + gtk_widget_class_install_action (widget_class, "about.save-debug-info", NULL, + (GtkWidgetActionActivateFunc) save_debug_info_cb); + + gtk_widget_class_add_binding (widget_class, GDK_KEY_Escape, 0, close_cb, NULL); + gtk_widget_class_add_binding (widget_class, GDK_KEY_S, GDK_CONTROL_MASK, + save_debug_info_shortcut_cb, NULL); +} + +static void +adw_about_window_init (AdwAboutWindow *self) +{ + self->application_icon = g_strdup (""); + self->application_name = g_strdup (""); + self->developer_name = g_strdup (""); + self->version = g_strdup (""); + self->release_notes_version = g_strdup (""); + self->release_notes = g_strdup (""); + self->comments = g_strdup (""); + self->website = g_strdup (""); + self->support_url = g_strdup (""); + self->issue_url = g_strdup (""); + self->debug_info = g_strdup (""); + self->debug_info_filename = g_strdup (""); + self->copyright = g_strdup (""); + self->license = g_strdup (""); + + gtk_widget_init_template (GTK_WIDGET (self)); + + gtk_text_buffer_create_tag (self->release_notes_buffer, "em", + "style", PANGO_STYLE_ITALIC, + NULL); + gtk_text_buffer_create_tag (self->release_notes_buffer, "code", + "family", "monospace", + NULL); + gtk_text_buffer_create_tag (self->release_notes_buffer, "bullet", + "font-features", "tnum=1", + "left-margin", 24, + "pixels-above-lines", 6, + NULL); + gtk_text_buffer_create_tag (self->release_notes_buffer, "section", + "pixels-above-lines", 12, + NULL); + gtk_text_buffer_create_tag (self->release_notes_buffer, "heading", + "weight", PANGO_WEIGHT_BOLD, + NULL); +} + +/** + * adw_about_window_new: + * + * Creates a new `AdwAboutWindow`. + * + * Returns: the newly created `AdwAboutWindow` + * + * Since: 1.2 + */ +GtkWidget * +adw_about_window_new (void) +{ + return g_object_new (ADW_TYPE_ABOUT_WINDOW, NULL); +} + +/** + * adw_about_window_get_application_icon: (attributes org.gtk.Method.get_property=application-icon) + * @self: an about window + * + * Gets the name of the application icon for @self. + * + * Returns: the application icon name + * + * Since: 1.2 + */ +const char * +adw_about_window_get_application_icon (AdwAboutWindow *self) +{ + g_return_val_if_fail (ADW_IS_ABOUT_WINDOW (self), NULL); + + return self->application_icon; +} + +/** + * adw_about_window_set_application_icon: (attributes org.gtk.Method.set_property=application-icon) + * @self: an about window + * @application_icon: the application icon name + * + * Sets the name of the application icon for @self. + * + * The icon is displayed at the top of the main page. + * + * Since: 1.2 + */ +void +adw_about_window_set_application_icon (AdwAboutWindow *self, + const char *application_icon) +{ + g_return_if_fail (ADW_IS_ABOUT_WINDOW (self)); + g_return_if_fail (application_icon != NULL); + + if (g_strcmp0 (self->application_icon, application_icon) == 0) + return; + + g_free (self->application_icon); + self->application_icon = g_strdup (application_icon); + + g_object_notify_by_pspec (G_OBJECT (self), props[PROP_APPLICATION_ICON]); +} + +/** + * adw_about_window_get_application_name: (attributes org.gtk.Method.get_property=application-name) + * @self: an about window + * + * Gets the application name for @self. + * + * Returns: the application name + * + * Since: 1.2 + */ +const char * +adw_about_window_get_application_name (AdwAboutWindow *self) +{ + g_return_val_if_fail (ADW_IS_ABOUT_WINDOW (self), NULL); + + return self->application_name; +} + +/** + * adw_about_window_set_application_name: (attributes org.gtk.Method.set_property=application-name) + * @self: an about window + * @application_name: the application name + * + * Sets the application name for @self. + * + * The name is displayed at the top of the main page. + * + * Since: 1.2 + */ +void +adw_about_window_set_application_name (AdwAboutWindow *self, + const char *application_name) +{ + g_return_if_fail (ADW_IS_ABOUT_WINDOW (self)); + g_return_if_fail (application_name != NULL); + + if (g_strcmp0 (self->application_name, application_name) == 0) + return; + + g_free (self->application_name); + self->application_name = g_strdup (application_name); + + g_object_notify_by_pspec (G_OBJECT (self), props[PROP_APPLICATION_NAME]); +} + +/** + * adw_about_window_get_developer_name: (attributes org.gtk.Method.get_property=developer-name) + * @self: an about window + * + * Gets the developer name for @self. + * + * Returns: the developer_name + * + * Since: 1.2 + */ +const char * +adw_about_window_get_developer_name (AdwAboutWindow *self) +{ + g_return_val_if_fail (ADW_IS_ABOUT_WINDOW (self), NULL); + + return self->developer_name; +} + +/** + * adw_about_window_set_developer_name: (attributes org.gtk.Method.set_property=developer-name) + * @self: an about window + * @developer_name: the developer name + * + * Sets the developer name for @self. + * + * The developer name is displayed on the main page, under the application name. + * + * If the application is developed by multiple people, the developer name can be + * set to values like "AppName team", "AppName developers" or + * "The AppName project", and the individual contributors can be listed on the + * Credits page, with [property@AboutWindow:developers] and related properties. + * + * Since: 1.2 + */ +void +adw_about_window_set_developer_name (AdwAboutWindow *self, + const char *developer_name) +{ + g_return_if_fail (ADW_IS_ABOUT_WINDOW (self)); + g_return_if_fail (developer_name != NULL); + + if (g_strcmp0 (self->developer_name, developer_name) == 0) + return; + + g_free (self->developer_name); + self->developer_name = g_strdup (developer_name); + + g_object_notify_by_pspec (G_OBJECT (self), props[PROP_DEVELOPER_NAME]); +} + +/** + * adw_about_window_get_version: (attributes org.gtk.Method.get_property=version) + * @self: an about window + * + * Gets the version for @self. + * + * Returns: the version + * + * Since: 1.2 + */ +const char * +adw_about_window_get_version (AdwAboutWindow *self) +{ + g_return_val_if_fail (ADW_IS_ABOUT_WINDOW (self), NULL); + + return self->version; +} + +/** + * adw_about_window_set_version: (attributes org.gtk.Method.set_property=version) + * @self: an about window + * @version: the version + * + * Sets the version for @self. + * + * The version is displayed on the main page. + * + * If [property@AboutWindow:release-notes-version] is not set, the version will + * also be displayed above the release notes on the What's New page. + * + * Since: 1.2 + */ +void +adw_about_window_set_version (AdwAboutWindow *self, + const char *version) +{ + g_return_if_fail (ADW_IS_ABOUT_WINDOW (self)); + g_return_if_fail (version != NULL); + + if (g_strcmp0 (self->version, version) == 0) + return; + + g_free (self->version); + self->version = g_strdup (version); + + g_object_notify_by_pspec (G_OBJECT (self), props[PROP_VERSION]); +} + +/** + * adw_about_window_get_release_notes_version: (attributes org.gtk.Method.get_property=release-notes-version) + * @self: an about window + * + * Gets the version described by the application's release notes. + * + * Returns: the release notes version + * + * Since: 1.2 + */ +const char * +adw_about_window_get_release_notes_version (AdwAboutWindow *self) +{ + g_return_val_if_fail (ADW_IS_ABOUT_WINDOW (self), NULL); + + return self->release_notes_version; +} + +/** + * adw_about_window_set_release_notes_version: (attributes org.gtk.Method.set_property=release-notes-version) + * @self: an about window + * @version: the release notes version + * + * Sets the version described by the application's release notes. + * + * The release notes version is displayed on the What's New page, above the + * release notes. + * + * If not set, [property@AboutWindow:version] will be used instead. + * + * For example, an application with the current version 2.0.2 might want to + * keep the release notes from 2.0.0, and set the release notes version + * accordingly. + * + * See [property@AboutWindow:release-notes]. + * + * Since: 1.2 + */ +void +adw_about_window_set_release_notes_version (AdwAboutWindow *self, + const char *version) +{ + g_return_if_fail (ADW_IS_ABOUT_WINDOW (self)); + g_return_if_fail (version != NULL); + + if (g_strcmp0 (self->release_notes_version, version) == 0) + return; + + g_free (self->release_notes_version); + self->release_notes_version = g_strdup (version); + + update_release_notes (self); + + g_object_notify_by_pspec (G_OBJECT (self), props[PROP_RELEASE_NOTES_VERSION]); +} + +/** + * adw_about_window_get_release_notes: (attributes org.gtk.Method.get_property=release-notes) + * @self: an about window + * + * Gets the release notes for @self. + * + * Returns: the release notes + * + * Since: 1.2 + */ +const char * +adw_about_window_get_release_notes (AdwAboutWindow *self) +{ + g_return_val_if_fail (ADW_IS_ABOUT_WINDOW (self), NULL); + + return self->release_notes; +} + +/** + * adw_about_window_set_release_notes: (attributes org.gtk.Method.set_property=release-notes) + * @self: an about window + * @release_notes: the release notes + * + * Sets the release notes for @self. + * + * Release notes are displayed on the the What's New page. + * + * Release notes are formatted the same way as + * [AppStream descriptions](https://freedesktop.org/software/appstream/docs/chap-Metadata.html#tag-description). + * + * The supported formatting options are: + * + * * Paragraph (`

          `) + * * Ordered list (`

            `), with list items (`
          1. `) + * * Unordered list (`
              `), with list items (`
            • `) + * + * Within paragraphs and list items, emphasis (``) and inline code + * (``) text styles are supported. The emphasis is rendered in italic, + * while inline code is shown in a monospaced font. + * + * Any text outside paragraphs or list items is ignored. + * + * Nested lists are not supported. + * + * `AdwAboutWindow` displays the version above the release notes. If set, the + * [property@AboutWindow:release-notes-version] of the property will be used + * as the version; otherwise, [property@AboutWindow:version] is used. + * + * Since: 1.2 + */ +void +adw_about_window_set_release_notes (AdwAboutWindow *self, + const char *release_notes) +{ + g_return_if_fail (ADW_IS_ABOUT_WINDOW (self)); + g_return_if_fail (release_notes != NULL); + + if (g_strcmp0 (self->release_notes, release_notes) == 0) + return; + + g_free (self->release_notes); + self->release_notes = g_strdup (release_notes); + + update_release_notes (self); + + g_object_notify_by_pspec (G_OBJECT (self), props[PROP_RELEASE_NOTES]); +} + +/** + * adw_about_window_get_comments: (attributes org.gtk.Method.get_property=comments) + * @self: an about window + * + * Gets the comments about the application. + * + * Returns: the comments + * + * Since: 1.2 + */ +const char * +adw_about_window_get_comments (AdwAboutWindow *self) +{ + g_return_val_if_fail (ADW_IS_ABOUT_WINDOW (self), NULL); + + return self->comments; +} + +/** + * adw_about_window_set_comments: (attributes org.gtk.Method.set_property=comments) + * @self: an about window + * @comments: the comments + * + * Sets the comments about the application. + * + * Comments will be shown on the Details page, above links. + * + * Unlike [property@Gtk.AboutDialog:comments], this string can be long and + * detailed. It can also contain links and Pango markup. + * + * Since: 1.2 + */ +void +adw_about_window_set_comments (AdwAboutWindow *self, + const char *comments) +{ + g_return_if_fail (ADW_IS_ABOUT_WINDOW (self)); + g_return_if_fail (comments != NULL); + + if (g_strcmp0 (self->comments, comments) == 0) + return; + + g_free (self->comments); + self->comments = g_strdup (comments); + + update_links (self); + + g_object_notify_by_pspec (G_OBJECT (self), props[PROP_COMMENTS]); +} + +/** + * adw_about_window_get_website: (attributes org.gtk.Method.get_property=website) + * @self: an about window + * + * Gets the application website URL for @self. + * + * Returns: the website URL + * + * Since: 1.2 + */ +const char * +adw_about_window_get_website (AdwAboutWindow *self) +{ + g_return_val_if_fail (ADW_IS_ABOUT_WINDOW (self), NULL); + + return self->website; +} + +/** + * adw_about_window_set_website: (attributes org.gtk.Method.set_property=website) + * @self: an about window + * @website: the website URL + * + * Sets the application website URL for @self. + * + * Website is displayed on the Details page, below comments, or on the main page + * if the Details page doesn't have any other content. + * + * Applications can add other links below, see [method@AboutWindow.add_link]. + * + * Since: 1.2 + */ +void +adw_about_window_set_website (AdwAboutWindow *self, + const char *website) +{ + g_return_if_fail (ADW_IS_ABOUT_WINDOW (self)); + g_return_if_fail (website != NULL); + + if (g_strcmp0 (self->website, website) == 0) + return; + + g_free (self->website); + self->website = g_strdup (website); + + update_links (self); + + g_object_notify_by_pspec (G_OBJECT (self), props[PROP_WEBSITE]); +} + +/** + * adw_about_window_get_support_url: (attributes org.gtk.Method.get_property=support-url) + * @self: an about window + * + * Gets the URL of the support page for @self. + * + * Returns: the support page URL + * + * Since: 1.2 + */ +const char * +adw_about_window_get_support_url (AdwAboutWindow *self) +{ + g_return_val_if_fail (ADW_IS_ABOUT_WINDOW (self), NULL); + + return self->support_url; +} + +/** + * adw_about_window_set_support_url: (attributes org.gtk.Method.set_property=support-url) + * @self: an about window + * @support_url: the support page URL + * + * Sets the URL of the support page for @self. + * + * The support page link is displayed on the main page. + * + * Since: 1.2 + */ +void +adw_about_window_set_support_url (AdwAboutWindow *self, + const char *support_url) +{ + g_return_if_fail (ADW_IS_ABOUT_WINDOW (self)); + g_return_if_fail (support_url != NULL); + + if (g_strcmp0 (self->support_url, support_url) == 0) + return; + + g_free (self->support_url); + self->support_url = g_strdup (support_url); + + g_object_notify_by_pspec (G_OBJECT (self), props[PROP_SUPPORT_URL]); +} + +/** + * adw_about_window_get_issue_url: (attributes org.gtk.Method.get_property=issue-url) + * @self: an about window + * + * Gets the issue tracker URL for @self. + * + * Returns: the issue tracker URL + * + * Since: 1.2 + */ +const char * +adw_about_window_get_issue_url (AdwAboutWindow *self) +{ + g_return_val_if_fail (ADW_IS_ABOUT_WINDOW (self), NULL); + + return self->issue_url; +} + +/** + * adw_about_window_set_issue_url: (attributes org.gtk.Method.set_property=issue-url) + * @self: an about window + * @issue_url: the issue tracker URL + * + * Sets the issue tracker URL for @self. + * + * The issue tracker link is displayed on the main page. + * + * Since: 1.2 + */ +void +adw_about_window_set_issue_url (AdwAboutWindow *self, + const char *issue_url) +{ + g_return_if_fail (ADW_IS_ABOUT_WINDOW (self)); + g_return_if_fail (issue_url != NULL); + + if (g_strcmp0 (self->issue_url, issue_url) == 0) + return; + + g_free (self->issue_url); + self->issue_url = g_strdup (issue_url); + + g_object_notify_by_pspec (G_OBJECT (self), props[PROP_ISSUE_URL]); +} + +/** + * adw_about_window_add_link: + * @self: an about window + * @title: the link title + * @url: the link URL + * + * Adds an extra link to the Details page. + * + * Extra links are displayed under the comment and website. + * + * Underlines in @title will be interpreted as indicating a mnemonic. + * + * See [property@AboutWindow:website]. + * + * Since: 1.2 + */ +void +adw_about_window_add_link (AdwAboutWindow *self, + const char *title, + const char *url) +{ + GtkWidget *row; + + g_return_if_fail (ADW_IS_ABOUT_WINDOW (self)); + g_return_if_fail (title != NULL); + g_return_if_fail (url != NULL); + + row = adw_action_row_new (); + adw_preferences_row_set_title (ADW_PREFERENCES_ROW (row), title); + adw_preferences_row_set_use_underline (ADW_PREFERENCES_ROW (row), TRUE); + + adw_action_row_add_suffix (ADW_ACTION_ROW (row), + gtk_image_new_from_icon_name ("adw-external-link-symbolic")); + + gtk_list_box_row_set_activatable (GTK_LIST_BOX_ROW (row), TRUE); + gtk_actionable_set_action_name (GTK_ACTIONABLE (row), "about.show-url"); + gtk_actionable_set_action_target (GTK_ACTIONABLE (row), "s", url); + + gtk_widget_set_tooltip_text (row, url); + + adw_preferences_group_add (ADW_PREFERENCES_GROUP (self->links_group), row); + + self->has_custom_links = TRUE; + + update_links (self); + +} + +/** + * adw_about_window_get_debug_info: (attributes org.gtk.Method.get_property=debug-info) + * @self: an about window + * + * Gets the debug information for @self. + * + * Returns: the debug information + * + * Since: 1.2 + */ +const char * +adw_about_window_get_debug_info (AdwAboutWindow *self) +{ + g_return_val_if_fail (ADW_IS_ABOUT_WINDOW (self), NULL); + + return self->debug_info; +} + +/** + * adw_about_window_set_debug_info: (attributes org.gtk.Method.set_property=debug-info) + * @self: an about window + * @debug_info: the debug information + * + * Sets the debug information for @self. + * + * Debug information will be shown on the Troubleshooting page. It's intended + * to be attached to issue reports when reporting issues against the + * application. + * + * `AdwAboutWindow` provides a quick way to save debug information to a file. + * When saving, [property@AboutWindow:debug-info-filename] would be used as + * the suggested filename. + * + * Debug information cannot contain markup or links. + * + * Since: 1.2 + */ +void +adw_about_window_set_debug_info (AdwAboutWindow *self, + const char *debug_info) +{ + g_return_if_fail (ADW_IS_ABOUT_WINDOW (self)); + g_return_if_fail (debug_info != NULL); + + if (g_strcmp0 (self->debug_info, debug_info) == 0) + return; + + g_free (self->debug_info); + self->debug_info = g_strdup (debug_info); + + g_object_notify_by_pspec (G_OBJECT (self), props[PROP_DEBUG_INFO]); +} + +/** + * adw_about_window_get_debug_info_filename: (attributes org.gtk.Method.get_property=debug-info-filename) + * @self: an about window + * + * Gets the debug information filename for @self. + * + * Returns: the debug information filename + * + * Since: 1.2 + */ +const char * +adw_about_window_get_debug_info_filename (AdwAboutWindow *self) +{ + g_return_val_if_fail (ADW_IS_ABOUT_WINDOW (self), NULL); + + return self->debug_info_filename; +} + +/** + * adw_about_window_set_debug_info_filename: (attributes org.gtk.Method.set_property=debug-info) + * @self: an about window + * @filename: the debug info filename + * + * Sets the debug information filename for @self. + * + * It will be used as the suggested filename when saving debug information to a + * file. + * + * See [property@AboutWindow:debug-info]. + * + * Since: 1.2 + */ +void +adw_about_window_set_debug_info_filename (AdwAboutWindow *self, + const char *filename) +{ + g_return_if_fail (ADW_IS_ABOUT_WINDOW (self)); + g_return_if_fail (filename != NULL); + + if (g_strcmp0 (self->debug_info_filename, filename) == 0) + return; + + g_free (self->debug_info_filename); + self->debug_info_filename = g_strdup (filename); + + g_object_notify_by_pspec (G_OBJECT (self), props[PROP_DEBUG_INFO_FILENAME]); +} + +/** + * adw_about_window_get_developers: (attributes org.gtk.Method.get_property=developers) + * @self: an about window + * + * Gets the list of developers of the application. + * + * Returns: (nullable) (transfer none) (array zero-terminated=1): The list of developers + * + * Since: 1.2 + */ +const char * const * +adw_about_window_get_developers (AdwAboutWindow *self) +{ + g_return_val_if_fail (ADW_IS_ABOUT_WINDOW (self), NULL); + + return (const char * const *) self->developers; +} + +/** + * adw_about_window_set_developers: (attributes org.gtk.Method.set_property=developers) + * @self: an about window + * @developers: (nullable) (transfer none) (array zero-terminated=1): the list of developers + * + * Sets the list of developers of the application. + * + * It will be displayed on the Credits page. + * + * Each name may contain email addresses and URLs, see the introduction for more + * details. + * + * See also: + * + * * [property@AboutWindow:designers] + * * [property@AboutWindow:artists] + * * [property@AboutWindow:documenters] + * * [property@AboutWindow:translator-credits] + * * [method@AboutWindow.add_credit_section] + * * [method@AboutWindow.add_acknowledgement_section] + * + * Since: 1.2 + */ +void +adw_about_window_set_developers (AdwAboutWindow *self, + const char **developers) +{ + g_return_if_fail (ADW_IS_ABOUT_WINDOW (self)); + + if ((const char **) self->developers == developers) + return; + + g_strfreev (self->developers); + self->developers = g_strdupv ((char **) developers); + + update_credits (self); + + g_object_notify_by_pspec (G_OBJECT (self), props[PROP_DEVELOPERS]); +} + +/** + * adw_about_window_get_designers: (attributes org.gtk.Method.get_property=designers) + * @self: an about window + * + * Gets the list of designers of the application. + * + * Returns: (nullable) (transfer none) (array zero-terminated=1): The list of designers + * + * Since: 1.2 + */ +const char * const * +adw_about_window_get_designers (AdwAboutWindow *self) +{ + g_return_val_if_fail (ADW_IS_ABOUT_WINDOW (self), NULL); + + return (const char * const *) self->designers; +} + +/** + * adw_about_window_set_designers: (attributes org.gtk.Method.set_property=designers) + * @self: an about window + * @designers: (nullable) (transfer none) (array zero-terminated=1): the list of designers + * + * Sets the list of designers of the application. + * + * It will be displayed on the Credits page. + * + * Each name may contain email addresses and URLs, see the introduction for more + * details. + * + * See also: + * + * * [property@AboutWindow:developers] + * * [property@AboutWindow:artists] + * * [property@AboutWindow:documenters] + * * [property@AboutWindow:translator-credits] + * * [method@AboutWindow.add_credit_section] + * * [method@AboutWindow.add_acknowledgement_section] + * + * Since: 1.2 + */ +void +adw_about_window_set_designers (AdwAboutWindow *self, + const char **designers) +{ + g_return_if_fail (ADW_IS_ABOUT_WINDOW (self)); + + if ((const char **) self->designers == designers) + return; + + g_strfreev (self->designers); + self->designers = g_strdupv ((char **) designers); + + update_credits (self); + + g_object_notify_by_pspec (G_OBJECT (self), props[PROP_DESIGNERS]); +} + +/** + * adw_about_window_get_artists: (attributes org.gtk.Method.get_property=artists) + * @self: an about window + * + * Gets the list of artists of the application. + * + * Returns: (nullable) (transfer none) (array zero-terminated=1): The list of artists + * + * Since: 1.2 + */ +const char * const * +adw_about_window_get_artists (AdwAboutWindow *self) +{ + g_return_val_if_fail (ADW_IS_ABOUT_WINDOW (self), NULL); + + return (const char * const *) self->artists; +} + +/** + * adw_about_window_set_artists: (attributes org.gtk.Method.set_property=artists) + * @self: an about window + * @artists: (nullable) (transfer none) (array zero-terminated=1): the list of artists + * + * Sets the list of artists of the application. + * + * It will be displayed on the Credits page. + * + * Each name may contain email addresses and URLs, see the introduction for more + * details. + * + * See also: + * + * * [property@AboutWindow:developers] + * * [property@AboutWindow:designers] + * * [property@AboutWindow:documenters] + * * [property@AboutWindow:translator-credits] + * * [method@AboutWindow.add_credit_section] + * * [method@AboutWindow.add_acknowledgement_section] + * + * Since: 1.2 + */ +void +adw_about_window_set_artists (AdwAboutWindow *self, + const char **artists) +{ + g_return_if_fail (ADW_IS_ABOUT_WINDOW (self)); + + if ((const char **) self->artists == artists) + return; + + g_strfreev (self->artists); + self->artists = g_strdupv ((char **) artists); + + update_credits (self); + + g_object_notify_by_pspec (G_OBJECT (self), props[PROP_ARTISTS]); +} + +/** + * adw_about_window_get_documenters: (attributes org.gtk.Method.get_property=documenters) + * @self: an about window + * + * Gets the list of documenters of the application. + * + * Returns: (nullable) (transfer none) (array zero-terminated=1): The list of documenters + * + * Since: 1.2 + */ +const char * const * +adw_about_window_get_documenters (AdwAboutWindow *self) +{ + g_return_val_if_fail (ADW_IS_ABOUT_WINDOW (self), NULL); + + return (const char * const *) self->documenters; +} + +/** + * adw_about_window_set_documenters: (attributes org.gtk.Method.set_property=documenters) + * @self: an about window + * @documenters: (nullable) (transfer none) (array zero-terminated=1): the list of documenters + * + * Sets the list of documenters of the application. + * + * It will be displayed on the Credits page. + * + * Each name may contain email addresses and URLs, see the introduction for more + * details. + * + * See also: + * + * * [property@AboutWindow:developers] + * * [property@AboutWindow:designers] + * * [property@AboutWindow:artists] + * * [property@AboutWindow:translator-credits] + * * [method@AboutWindow.add_credit_section] + * * [method@AboutWindow.add_acknowledgement_section] + * + * Since: 1.2 + */ +void +adw_about_window_set_documenters (AdwAboutWindow *self, + const char **documenters) +{ + g_return_if_fail (ADW_IS_ABOUT_WINDOW (self)); + + if ((const char **) self->documenters == documenters) + return; + + g_strfreev (self->documenters); + self->documenters = g_strdupv ((char **) documenters); + + update_credits (self); + + g_object_notify_by_pspec (G_OBJECT (self), props[PROP_DOCUMENTERS]); +} + +/** + * adw_about_window_get_translator_credits: (attributes org.gtk.Method.get_property=translator-credits) + * @self: an about window + * + * Gets the translator credits string. + * + * Returns: The translator credits string + * + * Since: 1.2 + */ +const char * +adw_about_window_get_translator_credits (AdwAboutWindow *self) +{ + g_return_val_if_fail (ADW_IS_ABOUT_WINDOW (self), NULL); + + return self->translator_credits; +} + +/** + * adw_about_window_set_translator_credits: (attributes org.gtk.Method.set_property=translator-credits) + * @self: an about window + * @translator_credits: the translator credits + * + * Sets the translator credits string. + * + * It will be displayed on the Credits page. + * + * This string should be `"translator-credits"` or `"translator_credits"` and + * should be marked as translatable. + * + * The string may contain email addresses and URLs, see the introduction for + * more details. + * + * See also: + * + * * [property@AboutWindow:developers] + * * [property@AboutWindow:designers] + * * [property@AboutWindow:artists] + * * [property@AboutWindow:documenters] + * * [method@AboutWindow.add_credit_section] + * * [method@AboutWindow.add_acknowledgement_section] + * + * Since: 1.2 + */ +void +adw_about_window_set_translator_credits (AdwAboutWindow *self, + const char *translator_credits) +{ + g_return_if_fail (ADW_IS_ABOUT_WINDOW (self)); + g_return_if_fail (translator_credits != NULL); + + if (self->translator_credits == translator_credits) + return; + + g_free (self->translator_credits); + self->translator_credits = g_strdup (translator_credits); + + update_credits (self); + + g_object_notify_by_pspec (G_OBJECT (self), props[PROP_TRANSLATOR_CREDITS]); +} + +/** + * adw_about_window_add_credit_section: + * @self: an about window + * @name: (nullable): the section name + * @people: (array zero-terminated=1): the list of names + * + * Adds an extra section to the Credits page. + * + * Extra sections are displayed below the standard categories. + * + * Each name may contain email addresses and URLs, see the introduction for more + * details. + * + * See also: + * + * * [property@AboutWindow:developers] + * * [property@AboutWindow:designers] + * * [property@AboutWindow:artists] + * * [property@AboutWindow:documenters] + * * [property@AboutWindow:translator-credits] + * * [method@AboutWindow.add_acknowledgement_section] + * + * Since: 1.2 + */ +void +adw_about_window_add_credit_section (AdwAboutWindow *self, + const char *name, + const char **people) +{ + CreditsSection *section; + + g_return_if_fail (ADW_IS_ABOUT_WINDOW (self)); + g_return_if_fail (people != NULL); + + section = g_new0 (CreditsSection, 1); + section->name = g_strdup (name); + section->people = g_strdupv ((char **) people); + + self->credit_sections = g_slist_append (self->credit_sections, section); + + update_credits (self); +} + +/** + * adw_about_window_add_acknowledgement_section: + * @self: an about window + * @name: (nullable): the section name + * @people: (array zero-terminated=1): the list of names + * + * Adds a section to the Acknowledgements page. + * + * This can be used to acknowledge additional people and organizations for their + * non-development contributions - for example, backers in a crowdfunded + * project. + * + * Each name may contain email addresses and URLs, see the introduction for more + * details. + * + * See also: + * + * * [property@AboutWindow:developers] + * * [property@AboutWindow:designers] + * * [property@AboutWindow:artists] + * * [property@AboutWindow:documenters] + * * [property@AboutWindow:translator-credits] + * * [method@AboutWindow.add_credit_section] + * + * Since: 1.2 + */ +void +adw_about_window_add_acknowledgement_section (AdwAboutWindow *self, + const char *name, + const char **people) +{ + g_return_if_fail (ADW_IS_ABOUT_WINDOW (self)); + g_return_if_fail (people != NULL); + + add_credits_section (self->acknowledgements_box, name, (char **) people); + + gtk_widget_show (self->acknowledgements_box); +} + +/** + * adw_about_window_get_copyright: (attributes org.gtk.Method.get_property=copyright) + * @self: an about window + * + * Gets the copyright information for @self. + * + * Returns: the copyright information + * + * Since: 1.2 + */ +const char * +adw_about_window_get_copyright (AdwAboutWindow *self) +{ + g_return_val_if_fail (ADW_IS_ABOUT_WINDOW (self), NULL); + + return self->copyright; +} + +/** + * adw_about_window_set_copyright: (attributes org.gtk.Method.set_property=copyright) + * @self: an about window + * @copyright: the copyright information + * + * Sets the copyright information for @self. + * + * This should be a short string of one or two lines, for example: + * `© 2022 Example`. + * + * The copyright information will be displayed on the Legal page, before the + * application license. + * + * [method@AboutWindow.add_legal_section] can be used to add copyright + * information for the application dependencies or other components. + * + * Since: 1.2 + */ +void +adw_about_window_set_copyright (AdwAboutWindow *self, + const char *copyright) +{ + g_return_if_fail (ADW_IS_ABOUT_WINDOW (self)); + g_return_if_fail (copyright != NULL); + + if (g_strcmp0 (self->copyright, copyright) == 0) + return; + + g_free (self->copyright); + self->copyright = g_strdup (copyright); + + update_legal (self); + + g_object_notify_by_pspec (G_OBJECT (self), props[PROP_COPYRIGHT]); +} + +/** + * adw_about_window_get_license_type: (attributes org.gtk.Method.get_property=license-type) + * @self: an about window + * + * Gets the license type for @self. + * + * Returns: the license type + * + * Since: 1.2 + */ +GtkLicense +adw_about_window_get_license_type (AdwAboutWindow *self) +{ + g_return_val_if_fail (ADW_IS_ABOUT_WINDOW (self), GTK_LICENSE_UNKNOWN); + + return self->license_type; +} + +/** + * adw_about_window_set_license_type: (attributes org.gtk.Method.set_property=license-type) + * @self: an about window + * @license_type: the license type + * + * Sets the license for @self from a list of known licenses. + * + * If the application's license is not in the list, + * [property@AboutWindow:license] can be used instead. The license type will be + * automatically set to `GTK_LICENSE_CUSTOM` in that case. + * + * If @license_type is `GTK_LICENSE_UNKNOWN`, no information will be displayed. + * + * If @license_type is different from `GTK_LICENSE_CUSTOM`. + * [property@AboutWindow:license] will be cleared out. + * + * The license description will be displayed on the Legal page, below the + * copyright information. + * + * [method@AboutWindow.add_legal_section] can be used to add license information + * for the application dependencies or other components. + * + * Since: 1.2 + */ +void +adw_about_window_set_license_type (AdwAboutWindow *self, + GtkLicense license_type) +{ + g_return_if_fail (ADW_IS_ABOUT_WINDOW (self)); + g_return_if_fail (license_type >= GTK_LICENSE_UNKNOWN && + license_type < G_N_ELEMENTS (gtk_license_info)); + + if (self->license_type == license_type) + return; + + if (license_type != GTK_LICENSE_CUSTOM) { + g_free (self->license); + self->license = g_strdup (""); + } + + self->license_type = license_type; + + update_legal (self); + + g_object_notify_by_pspec (G_OBJECT (self), props[PROP_LICENSE]); + g_object_notify_by_pspec (G_OBJECT (self), props[PROP_LICENSE_TYPE]); +} + +/** + * adw_about_window_get_license: (attributes org.gtk.Method.get_property=license) + * @self: an about window + * + * Gets the license for @self. + * + * Returns: the license + * + * Since: 1.2 + */ +const char * +adw_about_window_get_license (AdwAboutWindow *self) +{ + g_return_val_if_fail (ADW_IS_ABOUT_WINDOW (self), NULL); + + return self->license; +} + +/** + * adw_about_window_set_license: (attributes org.gtk.Method.set_property=license) + * @self: an about window + * @license: the license + * + * Sets the license for @self. + * + * This can be used to set a custom text for the license if it can't be set via + * [property@AboutWindow:license-type]. + * + * When set, [property@AboutWindow:license-type] will be set to + * `GTK_LICENSE_CUSTOM`. + * + * The license text will be displayed on the Legal page, below the copyright + * information. + * + * License text can contain Pango markup and links. + * + * [method@AboutWindow.add_legal_section] can be used to add license information + * for the application dependencies or other components. + * + * Since: 1.2 + */ +void +adw_about_window_set_license (AdwAboutWindow *self, + const char *license) +{ + g_return_if_fail (ADW_IS_ABOUT_WINDOW (self)); + g_return_if_fail (license != NULL); + + if (g_strcmp0 (self->license, license) == 0) + return; + + g_object_freeze_notify (G_OBJECT (self)); + + g_free (self->license); + self->license = g_strdup (license); + self->license_type = GTK_LICENSE_CUSTOM; + + update_legal (self); + + g_object_notify_by_pspec (G_OBJECT (self), props[PROP_LICENSE]); + g_object_notify_by_pspec (G_OBJECT (self), props[PROP_LICENSE_TYPE]); + + g_object_thaw_notify (G_OBJECT (self)); +} + +/** + * adw_about_window_add_legal_section: + * @self: an about window + * @title: the name of the section + * @copyright: (nullable): a copyright string + * @license_type: the type of license + * @license: (nullable): custom license information + * + * Adds an extra section to the Legal page. + * + * Extra sections will be displayed below the application's own information. + * + * The parameters @copyright, @license_type and @license will be used to present + * the it the same way as [property@AboutWindow:copyright], + * [property@AboutWindow:license-type] and [property@AboutWindow:license] are + * for the application's own information. + * + * See those properties for more details. + * + * This can be useful to attribute the application dependencies or data. + * + * Examples: + * + * ```c + * adw_about_window_add_legal_section (ADW_ABOUT_WINDOW (about), + * _("Copyright and a known license"), + * "© 2022 Example", + * GTK_LICENSE_LGPL_2_1, + * NULL); + * + * adw_about_window_add_legal_section (ADW_ABOUT_WINDOW (about), + * _("Copyright and custom license"), + * "© 2022 Example", + * GTK_LICENSE_CUSTOM, + * "Custom license text"); + * + * adw_about_window_add_legal_section (ADW_ABOUT_WINDOW (about), + * _("Copyright only"), + * "© 2022 Example", + * GTK_LICENSE_UNKNOWN, + * NULL); + * + * adw_about_window_add_legal_section (ADW_ABOUT_WINDOW (about), + * _("Custom license only"), + * NULL, + * GTK_LICENSE_CUSTOM, + * "Something completely custom here."); + * ``` + * + * Since: 1.2 + */ +void +adw_about_window_add_legal_section (AdwAboutWindow *self, + const char *title, + const char *copyright, + GtkLicense license_type, + const char *license) +{ + LegalSection *section; + + g_return_if_fail (ADW_IS_ABOUT_WINDOW (self)); + g_return_if_fail (title != NULL); + g_return_if_fail (license_type >= GTK_LICENSE_UNKNOWN && + license_type < G_N_ELEMENTS (gtk_license_info)); + + section = g_new0 (LegalSection, 1); + section->title = g_strdup (title); + section->copyright = g_strdup (copyright); + section->license_type = license_type; + section->license = g_strdup (license); + + self->legal_sections = g_slist_append (self->legal_sections, section); + + update_legal (self); +} + +/** + * adw_show_about_window: + * @parent: (nullable): the parent top-level window + * @first_property_name: the name of the first property + * @...: value of first property, followed by more pairs of property name and + * value, `NULL`-terminated + * + * A convenience function for showing an application’s about window. + */ +void +adw_show_about_window (GtkWindow *parent, + const char *first_property_name, + ...) +{ + GtkWidget *window; + va_list var_args; + + window = adw_about_window_new (); + + va_start (var_args, first_property_name); + g_object_set_valist (G_OBJECT (window), first_property_name, var_args); + va_end (var_args); + + if (parent) + gtk_window_set_transient_for (GTK_WINDOW (window), parent); + + gtk_window_present (GTK_WINDOW (window)); +} diff --git a/src/adw-about-window.h b/src/adw-about-window.h new file mode 100644 index 0000000000000000000000000000000000000000..e6a041d4f2a15e4196f7dd2118e8f8a7271d812d --- /dev/null +++ b/src/adw-about-window.h @@ -0,0 +1,175 @@ +/* + * Copyright (C) 2021-2022 Purism SPC + * + * SPDX-License-Identifier: LGPL-2.1-or-later + */ + +#pragma once + +#if !defined(_ADWAITA_INSIDE) && !defined(ADWAITA_COMPILATION) +#error "Only can be included directly." +#endif + +#include "adw-version.h" + +#include +#include "adw-window.h" + +G_BEGIN_DECLS + +#define ADW_TYPE_ABOUT_WINDOW (adw_about_window_get_type()) + +ADW_AVAILABLE_IN_1_2 +G_DECLARE_FINAL_TYPE (AdwAboutWindow, adw_about_window, ADW, ABOUT_WINDOW, AdwWindow) + +ADW_AVAILABLE_IN_1_2 +GtkWidget *adw_about_window_new (void) G_GNUC_WARN_UNUSED_RESULT; + +ADW_AVAILABLE_IN_1_2 +const char *adw_about_window_get_application_name (AdwAboutWindow *self); +ADW_AVAILABLE_IN_1_2 +void adw_about_window_set_application_name (AdwAboutWindow *self, + const char *application_name); + +ADW_AVAILABLE_IN_1_2 +const char *adw_about_window_get_application_icon (AdwAboutWindow *self); +ADW_AVAILABLE_IN_1_2 +void adw_about_window_set_application_icon (AdwAboutWindow *self, + const char *application_icon); + +ADW_AVAILABLE_IN_1_2 +const char *adw_about_window_get_developer_name (AdwAboutWindow *self); +ADW_AVAILABLE_IN_1_2 +void adw_about_window_set_developer_name (AdwAboutWindow *self, + const char *developer_name); + +ADW_AVAILABLE_IN_1_2 +const char *adw_about_window_get_version (AdwAboutWindow *self); +ADW_AVAILABLE_IN_1_2 +void adw_about_window_set_version (AdwAboutWindow *self, + const char *version); + +ADW_AVAILABLE_IN_1_2 +const char *adw_about_window_get_release_notes_version (AdwAboutWindow *self); +ADW_AVAILABLE_IN_1_2 +void adw_about_window_set_release_notes_version (AdwAboutWindow *self, + const char *version); + +ADW_AVAILABLE_IN_1_2 +const char *adw_about_window_get_release_notes (AdwAboutWindow *self); +ADW_AVAILABLE_IN_1_2 +void adw_about_window_set_release_notes (AdwAboutWindow *self, + const char *release_notes); + +ADW_AVAILABLE_IN_1_2 +const char *adw_about_window_get_comments (AdwAboutWindow *self); +ADW_AVAILABLE_IN_1_2 +void adw_about_window_set_comments (AdwAboutWindow *self, + const char *comments); + +ADW_AVAILABLE_IN_1_2 +const char *adw_about_window_get_website (AdwAboutWindow *self); +ADW_AVAILABLE_IN_1_2 +void adw_about_window_set_website (AdwAboutWindow *self, + const char *website); + +ADW_AVAILABLE_IN_1_2 +const char *adw_about_window_get_support_url (AdwAboutWindow *self); +ADW_AVAILABLE_IN_1_2 +void adw_about_window_set_support_url (AdwAboutWindow *self, + const char *support_url); + +ADW_AVAILABLE_IN_1_2 +const char *adw_about_window_get_issue_url (AdwAboutWindow *self); +ADW_AVAILABLE_IN_1_2 +void adw_about_window_set_issue_url (AdwAboutWindow *self, + const char *issue_url); + +ADW_AVAILABLE_IN_1_2 +void adw_about_window_add_link (AdwAboutWindow *self, + const char *title, + const char *url); + +ADW_AVAILABLE_IN_1_2 +const char *adw_about_window_get_debug_info (AdwAboutWindow *self); +ADW_AVAILABLE_IN_1_2 +void adw_about_window_set_debug_info (AdwAboutWindow *self, + const char *debug_info); + +ADW_AVAILABLE_IN_1_2 +const char *adw_about_window_get_debug_info_filename (AdwAboutWindow *self); +ADW_AVAILABLE_IN_1_2 +void adw_about_window_set_debug_info_filename (AdwAboutWindow *self, + const char *filename); + +ADW_AVAILABLE_IN_1_2 +const char * const *adw_about_window_get_developers (AdwAboutWindow *self); +ADW_AVAILABLE_IN_1_2 +void adw_about_window_set_developers (AdwAboutWindow *self, + const char **developers); + +ADW_AVAILABLE_IN_1_2 +const char * const *adw_about_window_get_designers (AdwAboutWindow *self); +ADW_AVAILABLE_IN_1_2 +void adw_about_window_set_designers (AdwAboutWindow *self, + const char **designers); + +ADW_AVAILABLE_IN_1_2 +const char * const *adw_about_window_get_artists (AdwAboutWindow *self); +ADW_AVAILABLE_IN_1_2 +void adw_about_window_set_artists (AdwAboutWindow *self, + const char **artists); + +ADW_AVAILABLE_IN_1_2 +const char * const *adw_about_window_get_documenters (AdwAboutWindow *self); +ADW_AVAILABLE_IN_1_2 +void adw_about_window_set_documenters (AdwAboutWindow *self, + const char **documenters); + +ADW_AVAILABLE_IN_1_2 +const char *adw_about_window_get_translator_credits (AdwAboutWindow *self); +ADW_AVAILABLE_IN_1_2 +void adw_about_window_set_translator_credits (AdwAboutWindow *self, + const char *translator_credits); + +ADW_AVAILABLE_IN_1_2 +void adw_about_window_add_credit_section (AdwAboutWindow *self, + const char *name, + const char **people); + +ADW_AVAILABLE_IN_1_2 +void adw_about_window_add_acknowledgement_section (AdwAboutWindow *self, + const char *name, + const char **people); + +ADW_AVAILABLE_IN_1_2 +const char *adw_about_window_get_copyright (AdwAboutWindow *self); +ADW_AVAILABLE_IN_1_2 +void adw_about_window_set_copyright (AdwAboutWindow *self, + const char *copyright); + +ADW_AVAILABLE_IN_1_2 +GtkLicense adw_about_window_get_license_type (AdwAboutWindow *self); +ADW_AVAILABLE_IN_1_2 +void adw_about_window_set_license_type (AdwAboutWindow *self, + GtkLicense license_type); + +ADW_AVAILABLE_IN_1_2 +const char *adw_about_window_get_license (AdwAboutWindow *self); +ADW_AVAILABLE_IN_1_2 +void adw_about_window_set_license (AdwAboutWindow *self, + const char *license); + +ADW_AVAILABLE_IN_1_2 +void adw_about_window_add_legal_section (AdwAboutWindow *self, + const char *title, + const char *copyright, + GtkLicense license_type, + const char *license); + +ADW_AVAILABLE_IN_1_2 +void adw_show_about_window (GtkWindow *parent, + const char *first_property_name, + ...); + +G_END_DECLS diff --git a/src/adw-about-window.ui b/src/adw-about-window.ui new file mode 100644 index 0000000000000000000000000000000000000000..d3c1ff914833efe7cb3c41215938a3d7369686b1 --- /dev/null +++ b/src/adw-about-window.ui @@ -0,0 +1,811 @@ + + + + + diff --git a/src/adwaita.gresources.xml b/src/adwaita.gresources.xml index da724d866306fc570d1098366ba4ffb2352a0647..58a0d7c44a8b473e77bc975eba2b8bf6008c9eea 100644 --- a/src/adwaita.gresources.xml +++ b/src/adwaita.gresources.xml @@ -4,11 +4,14 @@ glsl/fade.glsl glsl/mask.glsl icons/scalable/actions/adw-entry-apply-symbolic.svg + icons/scalable/actions/adw-external-link-symbolic.svg icons/scalable/actions/adw-expander-arrow-symbolic.svg + icons/scalable/actions/adw-mail-send-symbolic.svg icons/scalable/status/avatar-default-symbolic.svg icons/scalable/status/adw-tab-icon-missing-symbolic.svg + adw-about-window.ui adw-action-row.ui adw-combo-row.ui adw-entry-row.ui diff --git a/src/adwaita.h b/src/adwaita.h index a2fda744a30bcf2f34544c7ed594b8e5b4bb0f6a..509a9740f60be809f5b9dce20d97efa46cb9b200 100644 --- a/src/adwaita.h +++ b/src/adwaita.h @@ -21,6 +21,7 @@ G_BEGIN_DECLS #define _ADWAITA_INSIDE #include "adw-version.h" +#include "adw-about-window.h" #include "adw-action-row.h" #include "adw-animation.h" #include "adw-animation-target.h" diff --git a/src/icons/scalable/actions/adw-external-link-symbolic.svg b/src/icons/scalable/actions/adw-external-link-symbolic.svg new file mode 100644 index 0000000000000000000000000000000000000000..549d0f12ce00eef90182085ffdf7c730004e63f8 --- /dev/null +++ b/src/icons/scalable/actions/adw-external-link-symbolic.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/icons/scalable/actions/adw-mail-send-symbolic.svg b/src/icons/scalable/actions/adw-mail-send-symbolic.svg new file mode 100644 index 0000000000000000000000000000000000000000..932bd11c026c5e2966f11c022ae0554e79278a6e --- /dev/null +++ b/src/icons/scalable/actions/adw-mail-send-symbolic.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/meson.build b/src/meson.build index eb2d9041220fb1315a1388fff30250481bd2a189..b2c474987d3aacea89a82b61a7db37d08b3d7f4d 100644 --- a/src/meson.build +++ b/src/meson.build @@ -81,6 +81,7 @@ libadwaita_private_sources += adw_private_enums libadwaita_generated_headers += [adw_public_enums[1]] src_headers = [ + 'adw-about-window.h', 'adw-action-row.h', 'adw-animation.h', 'adw-animation-target.h', @@ -145,6 +146,7 @@ libadwaita_init_public_types = custom_target('adw-public-types.c', ) src_sources = [ + 'adw-about-window.c', 'adw-action-row.c', 'adw-animation.c', 'adw-animation-target.c', diff --git a/src/stylesheet/widgets/_misc.scss b/src/stylesheet/widgets/_misc.scss index 6c4ff1f7833ff6baa191eb4d341b234110fca6e7..3d35a672735df2435e86fb67384cbda05b50e5a6 100644 --- a/src/stylesheet/widgets/_misc.scss +++ b/src/stylesheet/widgets/_misc.scss @@ -96,6 +96,52 @@ statusbar { padding: 6px 10px 6px 10px; } +/****************** + * AdwAboutWindow * + ******************/ + +window.about { + .main-page { + scrollbar.vertical { + // Keep in sync with header bar height + margin-top: 47px; + background: none; + box-shadow: none; + } + + > viewport > clamp > box { + margin: 12px; + margin-top: 59px; // 12px + 47px + border-spacing: 6px; + + > box { + margin-top: 18px; + border-spacing: 18px; + margin-bottom: 6px; + } + } + + .app-version { + padding: 3px 18px; + color: $accent_color; + border-radius: 999px; + margin-top: 3px; + } + } + + .subpage { + > viewport > clamp > box { + margin: 18px 12px; + border-spacing: 18px; + } + + > clamp > textview { + background: none; + color: inherit; + } + } +} + /***************** * AdwStatusPage * *****************/ diff --git a/tests/meson.build b/tests/meson.build index b2a660e5dc09ee76ae7402ab8469ce8bfafcf570..2f17a06c727b2a682e92b40fdb151064ef5e7c69 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -24,6 +24,7 @@ if cc.get_argument_syntax() != 'msvc' endif test_names = [ + 'test-about-window', 'test-action-row', 'test-animation', 'test-animation-target', diff --git a/tests/test-about-window.c b/tests/test-about-window.c new file mode 100644 index 0000000000000000000000000000000000000000..f9c8f6b759195c7741cbce7ec657dadafe758ba4 --- /dev/null +++ b/tests/test-about-window.c @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2022 Purism SPC + * + * SPDX-License-Identifier: LGPL-2.1-or-later + * + * Author: Alexander Mikhaylenko + */ + +#include + +static void +test_adw_about_window_create (void) +{ + AdwAboutWindow *window = ADW_ABOUT_WINDOW (adw_about_window_new ()); + + const char *developers[] = { + "Angela Avery", + NULL + }; + + const char *designers[] = { + "GNOME Design Team", + NULL + }; + + const char *artists[] = { + "GNOME Design Team", + NULL + }; + + const char *documenters[] = { + "Angela Avery", + NULL + }; + + const char *credits[] = { + "Angela Avery", + NULL + }; + + const char *acknowledgements[] = { + "Angela Avery", + NULL + }; + + g_assert_nonnull (window); + + g_object_set (G_OBJECT (window), + "application-name", "Example", + "application-icon", "org.gnome.Example", + "developer-name", "Angela Avery", + "version", "1.2.3", + "release-notes-version", "1.2.0", + "release-notes", "

              Example

              ", + "comments", "Comments", + "website", "https://example.org", + "issue-url", "https://example.org", + "support-url", "https://example.org", + "debug-info", "Debug", + "debug-info-filename", "debug.txt", + "developers", developers, + "designers", designers, + "artists", artists, + "documenters", documenters, + "translator-credits", "translator-credits", + "copyright", "© 2022 Angela Avery", + "license-type", GTK_LICENSE_GPL_3_0, + NULL); + + g_assert_cmpstr (adw_about_window_get_application_name (window), ==, "Example"); + g_assert_cmpstr (adw_about_window_get_application_icon (window), ==, "org.gnome.Example"); + g_assert_cmpstr (adw_about_window_get_developer_name (window), ==, "Angela Avery"); + g_assert_cmpstr (adw_about_window_get_version (window), ==, "1.2.3"); + g_assert_cmpstr (adw_about_window_get_release_notes_version (window), ==, "1.2.0"); + g_assert_cmpstr (adw_about_window_get_release_notes (window), ==, "

              Example

              "); + g_assert_cmpstr (adw_about_window_get_comments (window), ==, "Comments"); + g_assert_cmpstr (adw_about_window_get_website (window), ==, "https://example.org"); + g_assert_cmpstr (adw_about_window_get_issue_url (window), ==, "https://example.org"); + g_assert_cmpstr (adw_about_window_get_support_url (window), ==, "https://example.org"); + g_assert_cmpstr (adw_about_window_get_debug_info (window), ==, "Debug"); + g_assert_cmpstr (adw_about_window_get_debug_info_filename (window), ==, "debug.txt"); + g_assert_cmpstrv (adw_about_window_get_developers (window), developers); + g_assert_cmpstrv (adw_about_window_get_designers (window), designers); + g_assert_cmpstrv (adw_about_window_get_artists (window), artists); + g_assert_cmpstrv (adw_about_window_get_documenters (window), documenters); + g_assert_cmpstr (adw_about_window_get_translator_credits (window), ==, "translator-credits"); + g_assert_cmpstr (adw_about_window_get_copyright (window), ==, "© 2022 Angela Avery"); + g_assert_cmpuint (adw_about_window_get_license_type (window), ==, GTK_LICENSE_GPL_3_0); + + adw_about_window_add_link (window, "Example", "https://example.org"); + adw_about_window_add_credit_section (window, "Example", credits); + adw_about_window_add_acknowledgement_section (window, "Example", acknowledgements); + adw_about_window_add_legal_section (window, "Example", "© 2022 Example", GTK_LICENSE_GPL_3_0, NULL); + adw_about_window_add_legal_section (window, "Example", "© 2022 Example", GTK_LICENSE_CUSTOM, "License"); + + g_assert_finalize_object (window); +} + +int +main (int argc, + char *argv[]) +{ + gtk_test_init (&argc, &argv, NULL); + adw_init (); + + g_test_add_func ("/Adwaita/AboutWindow/create", test_adw_about_window_create); + + return g_test_run (); +}