/* GTK - The GIMP Toolkit * Copyright (C) 2001 CodeFactory AB * Copyright (C) 2001, 2002 Anders Carlsson * Copyright (C) 2003, 2004 Matthias Clasen * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library. If not, see . */ /* * Author: Anders Carlsson * * Modified by the GTK+ Team and others 1997-2004. See the AUTHORS * file for a list of people on the GTK+ Team. See the ChangeLog * files for a list of changes. These files are distributed with * GTK+ at ftp://ftp.gtk.org/pub/gtk/. */ #include "config.h" #include #include #include "gtkaboutdialog.h" #include "gtkbutton.h" #include "gtkgrid.h" #include "gtkbox.h" #include "gtkicontheme.h" #include "gtkimage.h" #include "gtklabel.h" #include "gtkmarshalers.h" #include "gtkstack.h" #include "gtkorientable.h" #include "gtkscrolledwindow.h" #include "gtktextview.h" #include "gtkshow.h" #include "gtkmain.h" #include "gtktogglebutton.h" #include "gtktypebuiltins.h" #include "gtkstack.h" #include "gtkstackswitcher.h" #include "gtksettings.h" #include "gtkheaderbar.h" #include "gtkprivate.h" #include "gtkintl.h" #include "gtkeventcontrollermotion.h" #include "gtkeventcontrollerkey.h" #include "gtkgestureclick.h" #include "gtkstylecontext.h" /** * GtkAboutDialog: * * The `GtkAboutDialog` offers a simple way to display information about * a program. * * The shown information includes the programs' logo, name, copyright, * website and license. It is also possible to give credits to the authors, * documenters, translators and artists who have worked on the program. * * An about dialog is typically opened when the user selects the `About` * option from the `Help` menu. All parts of the dialog are optional. * * ![An example GtkAboutDialog](aboutdialog.png) * * About dialogs often contain links and email addresses. `GtkAboutDialog` * displays these as clickable links. By default, it calls [func@Gtk.show_uri] * when a user clicks one. The behaviour can be overridden with the * [signal@Gtk.AboutDialog::activate-link] signal. * * To specify a person with an email address, use a string like * `Edgar Allan Poe `. To specify a website with a title, * use a string like `GTK team https://www.gtk.org`. * * To make constructing a `GtkAboutDialog` as convenient as possible, you can * use the function [func@Gtk.show_about_dialog] which constructs and shows * a dialog and keeps it around so that it can be shown again. * * Note that GTK sets a default title of `_("About %s")` on the dialog * window (where `%s` is replaced by the name of the application, but in * order to ensure proper translation of the title, applications should * set the title property explicitly when constructing a `GtkAboutDialog`, * as shown in the following example: * * ```c * GFile *logo_file = g_file_new_for_path ("./logo.png"); * GdkTexture *example_logo = gdk_texture_new_from_file (logo_file, NULL); * g_object_unref (logo_file); * * gtk_show_about_dialog (NULL, * "program-name", "ExampleCode", * "logo", example_logo, * "title", _("About ExampleCode"), * NULL); * ``` * * ## CSS nodes * * `GtkAboutDialog` has a single CSS node with the name `window` and style * class `.aboutdialog`. */ typedef struct { const char *name; const char *url; } LicenseInfo; /* LicenseInfo for each GtkLicense type; keep in the same order as the enumeration */ static const LicenseInfo gtk_license_info [] = { { N_("License"), NULL }, { N_("Custom License") , 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" } }; /* 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 *heading; char **people; } CreditSection; typedef struct _GtkAboutDialogClass GtkAboutDialogClass; struct _GtkAboutDialog { GtkWindow parent_instance; char *name; char *version; char *copyright; char *comments; char *website_url; char *website_text; char *translator_credits; char *license; char *system_information; char **authors; char **documenters; char **artists; GSList *credit_sections; gboolean credits_page_initialized; gboolean license_page_initialized; gboolean system_page_initialized; GtkWidget *stack; GtkWidget *stack_switcher; GtkWidget *logo_image; GtkWidget *name_label; GtkWidget *version_label; GtkWidget *comments_label; GtkWidget *copyright_label; GtkWidget *license_label; GtkWidget *website_label; GtkWidget *credits_page; GtkWidget *license_page; GtkWidget *system_page; GtkWidget *credits_grid; GtkWidget *license_view; GtkWidget *system_view; GPtrArray *visited_links; GtkLicense license_type; guint hovering_over_link : 1; guint wrap_license : 1; guint in_child_changed : 1; }; struct _GtkAboutDialogClass { GtkWindowClass parent_class; gboolean (*activate_link) (GtkAboutDialog *dialog, const char *uri); }; enum { PROP_0, PROP_NAME, PROP_VERSION, PROP_COPYRIGHT, PROP_COMMENTS, PROP_WEBSITE, PROP_WEBSITE_LABEL, PROP_LICENSE, PROP_SYSTEM_INFORMATION, PROP_AUTHORS, PROP_DOCUMENTERS, PROP_TRANSLATOR_CREDITS, PROP_ARTISTS, PROP_LOGO, PROP_LOGO_ICON_NAME, PROP_WRAP_LICENSE, PROP_LICENSE_TYPE, LAST_PROP }; static void gtk_about_dialog_finalize (GObject *object); static void gtk_about_dialog_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec); static void gtk_about_dialog_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec); static void update_name_version (GtkAboutDialog *about); static void follow_if_link (GtkAboutDialog *about, GtkTextView *text_view, GtkTextIter *iter); static void set_cursor_if_appropriate (GtkAboutDialog *about, GtkTextView *text_view, int x, int y); static void populate_credits_page (GtkAboutDialog *about); static void populate_license_page (GtkAboutDialog *about); static void populate_system_page (GtkAboutDialog *about); static gboolean gtk_about_dialog_activate_link (GtkAboutDialog *about, const char *uri); static gboolean emit_activate_link (GtkAboutDialog *about, const char *uri); static gboolean text_view_key_pressed (GtkEventController *controller, guint keyval, guint keycode, GdkModifierType state, GtkAboutDialog *about); static void text_view_released (GtkGestureClick *press, int n, double x, double y, GtkAboutDialog *about); static void text_view_motion (GtkEventControllerMotion *motion, double x, double y, GtkAboutDialog *about); enum { ACTIVATE_LINK, LAST_SIGNAL }; static guint signals[LAST_SIGNAL] = { 0 }; static GParamSpec *props[LAST_PROP] = { NULL, }; G_DEFINE_TYPE (GtkAboutDialog, gtk_about_dialog, GTK_TYPE_WINDOW) static gboolean stack_visible_child_notify (GtkStack *stack, GParamSpec *pspec, GtkAboutDialog *about) { GtkWidget *child; child = gtk_stack_get_visible_child (stack); if (child == about->credits_page) { if (!about->credits_page_initialized) { populate_credits_page (about); about->credits_page_initialized = TRUE; } } else if (child == about->license_page) { if (!about->license_page_initialized) { populate_license_page (about); about->license_page_initialized = TRUE; } } else if (child == about->system_page) { if (!about->system_page_initialized) { populate_system_page (about); about->system_page_initialized = TRUE; } } return FALSE; } static void gtk_about_dialog_map (GtkWidget *widget) { GtkAboutDialog *about = GTK_ABOUT_DIALOG (widget); if (gtk_widget_get_visible (about->stack_switcher)) gtk_widget_grab_focus (gtk_widget_get_first_child (about->stack_switcher)); GTK_WIDGET_CLASS (gtk_about_dialog_parent_class)->map (widget); } static void gtk_about_dialog_class_init (GtkAboutDialogClass *klass) { GObjectClass *object_class; GtkWidgetClass *widget_class; object_class = (GObjectClass *)klass; widget_class = (GtkWidgetClass *)klass; object_class->set_property = gtk_about_dialog_set_property; object_class->get_property = gtk_about_dialog_get_property; object_class->finalize = gtk_about_dialog_finalize; widget_class->map = gtk_about_dialog_map; klass->activate_link = gtk_about_dialog_activate_link; /** * GtkAboutDialog::activate-link: * @label: The object on which the signal was emitted * @uri: the URI that is activated * * Emitted every time a URL is activated. * * Applications may connect to it to override the default behaviour, * which is to call [func@Gtk.show_uri]. * * Returns: `TRUE` if the link has been activated */ signals[ACTIVATE_LINK] = g_signal_new (I_("activate-link"), G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GtkAboutDialogClass, activate_link), _gtk_boolean_handled_accumulator, NULL, _gtk_marshal_BOOLEAN__STRING, G_TYPE_BOOLEAN, 1, G_TYPE_STRING); /** * GtkAboutDialog:program-name: (attributes org.gtk.Property.get=gtk_about_dialog_get_program_name org.gtk.Property.set=gtk_about_dialog_set_program_name) * * The name of the program. * * If this is not set, it defaults to the value returned by * `g_get_application_name()`. */ props[PROP_NAME] = g_param_spec_string ("program-name", P_("Program name"), P_("The name of the program. If this is not set, it defaults to g_get_application_name()"), NULL, GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY); /** * GtkAboutDialog:version: (attributes org.gtk.Property.get=gtk_about_dialog_get_version org.gtk.Property.set=gtk_about_dialog_set_version) * * The version of the program. */ props[PROP_VERSION] = g_param_spec_string ("version", P_("Program version"), P_("The version of the program"), NULL, GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY); /** * GtkAboutDialog:copyright: (attributes org.gtk.Property.get=gtk_about_dialog_get_copyright org.gtk.Property.set=gtk_about_dialog_set_copyright) * * Copyright information for the program. */ props[PROP_COPYRIGHT] = g_param_spec_string ("copyright", P_("Copyright string"), P_("Copyright information for the program"), NULL, GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY); /** * GtkAboutDialog:comments: (attributes org.gtk.Property.get=gtk_about_dialog_get_comments org.gtk.Property.set=gtk_about_dialog_set_comments) * * Comments about the program. * * This string is displayed in a label in the main dialog, thus it * should be a short explanation of the main purpose of the program, * not a detailed list of features. */ props[PROP_COMMENTS] = g_param_spec_string ("comments", P_("Comments string"), P_("Comments about the program"), NULL, GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY); /** * GtkAboutDialog:license: (attributes org.gtk.Property.get=gtk_about_dialog_get_license org.gtk.Property.set=gtk_about_dialog_set_license) * * The license of the program, as free-form text. * * This string is displayed in a text view in a secondary dialog, therefore * it is fine to use a long multi-paragraph text. Note that the text is only * wrapped in the text view if the "wrap-license" property is set to `TRUE`; * otherwise the text itself must contain the intended linebreaks. * * When setting this property to a non-`NULL` value, the * [property@Gtk.AboutDialog:license-type] property is set to * `GTK_LICENSE_CUSTOM` as a side effect. * * The text may contain links in this format `` * and email references in the form ``, and these will * be converted into clickable links. */ props[PROP_LICENSE] = g_param_spec_string ("license", P_("License"), P_("The license of the program"), NULL, GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY); /** * GtkAboutDialog:system-information: (attributes org.gtk.Property.get=gtk_about_dialog_get_system_information org.gtk.Property.set=gtk_about_dialog_set_system_information) * * Information about the system on which the program is running. * * This information is displayed in a separate page, therefore it is fine * to use a long multi-paragraph text. Note that the text should contain * the intended linebreaks. * * The text may contain links in this format `` * and email references in the form ``, and these will * be converted into clickable links. */ props[PROP_SYSTEM_INFORMATION] = g_param_spec_string ("system-information", P_("System Information"), P_("Information about the system on which the program is running"), NULL, GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY); /** * GtkAboutDialog:license-type: (attributes org.gtk.Property.get=gtk_about_dialog_get_license_type org.gtk.Property.set=gtk_about_dialog_set_license_type) * * The license of the program. * * The `GtkAboutDialog` will automatically fill out a standard disclaimer * and link the user to the appropriate online resource for the license * text. * * If `GTK_LICENSE_UNKNOWN` is used, the link used will be the same * specified in the [property@Gtk.AboutDialog:website] property. * * If `GTK_LICENSE_CUSTOM` is used, the current contents of the * [property@Gtk.AboutDialog:license] property are used. * * For any other [enum@Gtk.License] value, the contents of the * [property@Gtk.AboutDialog:license] property are also set by this property as * a side effect. */ props[PROP_LICENSE_TYPE] = g_param_spec_enum ("license-type", P_("License Type"), P_("The license type of the program"), GTK_TYPE_LICENSE, GTK_LICENSE_UNKNOWN, GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY); /** * GtkAboutDialog:website: (attributes org.gtk.Property.get=gtk_about_dialog_get_website org.gtk.Property.set=gtk_about_dialog_set_website) * * The URL for the link to the website of the program. * * This should be a string starting with `http://` or `https://`. */ props[PROP_WEBSITE] = g_param_spec_string ("website", P_("Website URL"), P_("The URL for the link to the website of the program"), NULL, GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY); /** * GtkAboutDialog:website-label: (attributes org.gtk.Property.get=gtk_about_dialog_get_website_label org.gtk.Property.set=gtk_about_dialog_set_website_label) * * The label for the link to the website of the program. */ props[PROP_WEBSITE_LABEL] = g_param_spec_string ("website-label", P_("Website label"), P_("The label for the link to the website of the program"), NULL, GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY); /** * GtkAboutDialog:authors: (attributes org.gtk.Property.get=gtk_about_dialog_get_authors org.gtk.Property.set=gtk_about_dialog_set_authors) * * The authors of the program, as a `NULL`-terminated array of strings. * * Each string may contain email addresses and URLs, which will be displayed * as links, see the introduction for more details. */ props[PROP_AUTHORS] = g_param_spec_boxed ("authors", P_("Authors"), P_("List of authors of the program"), G_TYPE_STRV, GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY); /** * GtkAboutDialog:documenters: (attributes org.gtk.Property.get=gtk_about_dialog_get_documenters org.gtk.Property.set=gtk_about_dialog_set_documenters) * * The people documenting the program, as a `NULL`-terminated array of strings. * * Each string may contain email addresses and URLs, which will be displayed * as links, see the introduction for more details. */ props[PROP_DOCUMENTERS] = g_param_spec_boxed ("documenters", P_("Documenters"), P_("List of people documenting the program"), G_TYPE_STRV, GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY); /** * GtkAboutDialog:artists: (attributes org.gtk.Property.get=gtk_about_dialog_get_artists org.gtk.Property.set=gtk_about_dialog_set_artists) * * The people who contributed artwork to the program, as a `NULL`-terminated * array of strings. * * Each string may contain email addresses and URLs, which will be displayed * as links. */ props[PROP_ARTISTS] = g_param_spec_boxed ("artists", P_("Artists"), P_("List of people who have contributed artwork to the program"), G_TYPE_STRV, GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY); /** * GtkAboutDialog:translator-credits: (attributes org.gtk.Property.get=gtk_about_dialog_get_translator_credits org.gtk.Property.set=gtk_about_dialog_set_translator_credits) * * Credits to the translators. * * This string should be marked as translatable. * * The string may contain email addresses and URLs, which will be displayed * as links, see the introduction for more details. */ props[PROP_TRANSLATOR_CREDITS] = g_param_spec_string ("translator-credits", P_("Translator credits"), P_("Credits to the translators. This string should be marked as translatable"), NULL, GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY); /** * GtkAboutDialog:logo: (attributes org.gtk.Property.get=gtk_about_dialog_get_logo org.gtk.Property.set=gtk_about_dialog_set_logo) * * A logo for the about box. * * If it is `NULL`, the default window icon set with * [id@gtk_window_set_default_icon_name] will be used. */ props[PROP_LOGO] = g_param_spec_object ("logo", P_("Logo"), P_("A logo for the about box."), GDK_TYPE_PAINTABLE, GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY); /** * GtkAboutDialog:logo-icon-name: (attributes org.gtk.Property.get=gtk_about_dialog_get_logo_icon_name org.gtk.Property.set=gtk_about_dialog_set_logo_icon_name) * * A named icon to use as the logo for the about box. * * This property overrides the [property@Gtk.AboutDialog:logo] property. */ props[PROP_LOGO_ICON_NAME] = g_param_spec_string ("logo-icon-name", P_("Logo Icon Name"), P_("A named icon to use as the logo for the about box."), NULL, GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY); /** * GtkAboutDialog:wrap-license: (attributes org.gtk.Property.get=gtk_about_dialog_get_wrap_license org.gtk.Property.set=gtk_about_dialog_set_wrap_license) * * Whether to wrap the text in the license dialog. */ props[PROP_WRAP_LICENSE] = g_param_spec_boolean ("wrap-license", P_("Wrap license"), P_("Whether to wrap the license text."), FALSE, GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY); g_object_class_install_properties (object_class, LAST_PROP, props); /* * Key bindings */ gtk_widget_class_add_binding_action (widget_class, GDK_KEY_Escape, 0, "window.close", NULL); /* Bind class to template */ gtk_widget_class_set_template_from_resource (widget_class, "/org/gtk/libgtk/ui/gtkaboutdialog.ui"); gtk_widget_class_bind_template_child (widget_class, GtkAboutDialog, stack); gtk_widget_class_bind_template_child (widget_class, GtkAboutDialog, stack_switcher); gtk_widget_class_bind_template_child (widget_class, GtkAboutDialog, logo_image); gtk_widget_class_bind_template_child (widget_class, GtkAboutDialog, name_label); gtk_widget_class_bind_template_child (widget_class, GtkAboutDialog, version_label); gtk_widget_class_bind_template_child (widget_class, GtkAboutDialog, comments_label); gtk_widget_class_bind_template_child (widget_class, GtkAboutDialog, copyright_label); gtk_widget_class_bind_template_child (widget_class, GtkAboutDialog, license_label); gtk_widget_class_bind_template_child (widget_class, GtkAboutDialog, website_label); gtk_widget_class_bind_template_child (widget_class, GtkAboutDialog, credits_page); gtk_widget_class_bind_template_child (widget_class, GtkAboutDialog, license_page); gtk_widget_class_bind_template_child (widget_class, GtkAboutDialog, system_page); gtk_widget_class_bind_template_child (widget_class, GtkAboutDialog, credits_grid); gtk_widget_class_bind_template_child (widget_class, GtkAboutDialog, license_view); gtk_widget_class_bind_template_child (widget_class, GtkAboutDialog, system_view); gtk_widget_class_bind_template_callback (widget_class, emit_activate_link); gtk_widget_class_bind_template_callback (widget_class, text_view_released); gtk_widget_class_bind_template_callback (widget_class, text_view_motion); gtk_widget_class_bind_template_callback (widget_class, text_view_key_pressed); gtk_widget_class_bind_template_callback (widget_class, stack_visible_child_notify); } static gboolean emit_activate_link (GtkAboutDialog *about, const char *uri) { gboolean handled = FALSE; g_signal_emit (about, signals[ACTIVATE_LINK], 0, uri, &handled); return TRUE; } static void update_stack_switcher_visibility (GtkAboutDialog *about) { GtkStackPage *page; gboolean any_visible = FALSE; page = gtk_stack_get_page (GTK_STACK (about->stack), about->credits_page); any_visible |= gtk_stack_page_get_visible (page); page = gtk_stack_get_page (GTK_STACK (about->stack), about->license_page); any_visible |= gtk_stack_page_get_visible (page); page = gtk_stack_get_page (GTK_STACK (about->stack), about->system_page); any_visible |= gtk_stack_page_get_visible (page); gtk_widget_set_visible (about->stack_switcher, any_visible); } static void update_license_button_visibility (GtkAboutDialog *about) { GtkStackPage *page; page = gtk_stack_get_page (GTK_STACK (about->stack), about->license_page); gtk_stack_page_set_visible (page, about->license_type == GTK_LICENSE_CUSTOM && about->license != NULL && about->license[0] != '\0'); update_stack_switcher_visibility (about); } static void update_system_button_visibility (GtkAboutDialog *about) { GtkStackPage *page; page = gtk_stack_get_page (GTK_STACK (about->stack), about->system_page); gtk_stack_page_set_visible (page, about->system_information != NULL && about->system_information[0] != '\0'); update_stack_switcher_visibility (about); } static void update_credits_button_visibility (GtkAboutDialog *about) { gboolean show; GtkStackPage *page; page = gtk_stack_get_page (GTK_STACK (about->stack), about->credits_page); show = (about->authors != NULL || about->documenters != NULL || about->artists != NULL || about->credit_sections != NULL || (about->translator_credits != NULL && strcmp (about->translator_credits, "translator_credits") && strcmp (about->translator_credits, "translator-credits"))); gtk_stack_page_set_visible (page, show); update_stack_switcher_visibility (about); } static void gtk_about_dialog_init (GtkAboutDialog *about) { /* Data */ about->name = NULL; about->version = NULL; about->copyright = NULL; about->comments = NULL; about->website_url = NULL; about->website_text = NULL; about->translator_credits = NULL; about->license = NULL; about->authors = NULL; about->documenters = NULL; about->artists = NULL; about->hovering_over_link = FALSE; about->wrap_license = FALSE; about->license_type = GTK_LICENSE_UNKNOWN; about->visited_links = g_ptr_array_new_with_free_func (g_free); gtk_widget_init_template (GTK_WIDGET (about)); gtk_stack_set_visible_child_name (GTK_STACK (about->stack), "main"); update_stack_switcher_visibility (about); /* force defaults */ gtk_about_dialog_set_program_name (about, NULL); gtk_about_dialog_set_logo (about, NULL); } static void destroy_credit_section (gpointer data) { CreditSection *cs = data; g_free (cs->heading); g_strfreev (cs->people); g_slice_free (CreditSection, data); } static void gtk_about_dialog_finalize (GObject *object) { GtkAboutDialog *about = GTK_ABOUT_DIALOG (object); g_free (about->name); g_free (about->version); g_free (about->copyright); g_free (about->comments); g_free (about->license); g_free (about->website_url); g_free (about->website_text); g_free (about->translator_credits); g_free (about->system_information); g_strfreev (about->authors); g_strfreev (about->documenters); g_strfreev (about->artists); g_slist_free_full (about->credit_sections, destroy_credit_section); g_ptr_array_unref (about->visited_links); G_OBJECT_CLASS (gtk_about_dialog_parent_class)->finalize (object); } static void gtk_about_dialog_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { GtkAboutDialog *about = GTK_ABOUT_DIALOG (object); switch (prop_id) { case PROP_NAME: gtk_about_dialog_set_program_name (about, g_value_get_string (value)); break; case PROP_VERSION: gtk_about_dialog_set_version (about, g_value_get_string (value)); break; case PROP_COMMENTS: gtk_about_dialog_set_comments (about, g_value_get_string (value)); break; case PROP_WEBSITE: gtk_about_dialog_set_website (about, g_value_get_string (value)); break; case PROP_WEBSITE_LABEL: gtk_about_dialog_set_website_label (about, g_value_get_string (value)); break; case PROP_LICENSE: gtk_about_dialog_set_license (about, g_value_get_string (value)); break; case PROP_SYSTEM_INFORMATION: gtk_about_dialog_set_system_information (about, g_value_get_string (value)); break; case PROP_LICENSE_TYPE: gtk_about_dialog_set_license_type (about, g_value_get_enum (value)); break; case PROP_COPYRIGHT: gtk_about_dialog_set_copyright (about, g_value_get_string (value)); break; case PROP_LOGO: gtk_about_dialog_set_logo (about, g_value_get_object (value)); break; case PROP_AUTHORS: gtk_about_dialog_set_authors (about, (const char **)g_value_get_boxed (value)); break; case PROP_DOCUMENTERS: gtk_about_dialog_set_documenters (about, (const char **)g_value_get_boxed (value)); break; case PROP_ARTISTS: gtk_about_dialog_set_artists (about, (const char **)g_value_get_boxed (value)); break; case PROP_TRANSLATOR_CREDITS: gtk_about_dialog_set_translator_credits (about, g_value_get_string (value)); break; case PROP_LOGO_ICON_NAME: gtk_about_dialog_set_logo_icon_name (about, g_value_get_string (value)); break; case PROP_WRAP_LICENSE: gtk_about_dialog_set_wrap_license (about, g_value_get_boolean (value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void gtk_about_dialog_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { GtkAboutDialog *about = GTK_ABOUT_DIALOG (object); switch (prop_id) { case PROP_NAME: g_value_set_string (value, about->name); break; case PROP_VERSION: g_value_set_string (value, about->version); break; case PROP_COPYRIGHT: g_value_set_string (value, about->copyright); break; case PROP_COMMENTS: g_value_set_string (value, about->comments); break; case PROP_WEBSITE: g_value_set_string (value, about->website_url); break; case PROP_WEBSITE_LABEL: g_value_set_string (value, about->website_text); break; case PROP_LICENSE: g_value_set_string (value, about->license); break; case PROP_SYSTEM_INFORMATION: g_value_set_string (value, about->system_information); break; case PROP_LICENSE_TYPE: g_value_set_enum (value, about->license_type); break; case PROP_TRANSLATOR_CREDITS: g_value_set_string (value, about->translator_credits); break; case PROP_AUTHORS: g_value_set_boxed (value, about->authors); break; case PROP_DOCUMENTERS: g_value_set_boxed (value, about->documenters); break; case PROP_ARTISTS: g_value_set_boxed (value, about->artists); break; case PROP_LOGO: if (gtk_image_get_storage_type (GTK_IMAGE (about->logo_image)) == GTK_IMAGE_PAINTABLE) g_value_set_object (value, gtk_image_get_paintable (GTK_IMAGE (about->logo_image))); else g_value_set_object (value, NULL); break; case PROP_LOGO_ICON_NAME: if (gtk_image_get_storage_type (GTK_IMAGE (about->logo_image)) == GTK_IMAGE_ICON_NAME) g_value_set_string (value, gtk_image_get_icon_name (GTK_IMAGE (about->logo_image))); else g_value_set_string (value, NULL); break; case PROP_WRAP_LICENSE: g_value_set_boolean (value, about->wrap_license); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static gboolean gtk_about_dialog_activate_link (GtkAboutDialog *about, const char *uri) { gtk_show_uri (GTK_WINDOW (about), uri, GDK_CURRENT_TIME); return TRUE; } static void update_website (GtkAboutDialog *about) { gtk_widget_show (about->website_label); if (about->website_url) { char *markup; if (about->website_text) { char *escaped; escaped = g_markup_escape_text (about->website_text, -1); markup = g_strdup_printf ("%s", about->website_url, escaped); g_free (escaped); } else { markup = g_strdup_printf ("%s", about->website_url, _("Website")); } gtk_label_set_markup (GTK_LABEL (about->website_label), markup); g_free (markup); } else { if (about->website_text) gtk_label_set_text (GTK_LABEL (about->website_label), about->website_text); else gtk_widget_hide (about->website_label); } } /** * gtk_about_dialog_get_program_name: (attributes org.gtk.Method.get_property=program-name) * @about: a `GtkAboutDialog` * * Returns the program name displayed in the about dialog. * * Returns: (nullable): The program name */ const char * gtk_about_dialog_get_program_name (GtkAboutDialog *about) { g_return_val_if_fail (GTK_IS_ABOUT_DIALOG (about), NULL); return about->name; } static void update_name_version (GtkAboutDialog *about) { char *title_string, *name_string; title_string = g_strdup_printf (_("About %s"), about->name); gtk_window_set_title (GTK_WINDOW (about), title_string); g_free (title_string); if (about->version != NULL) { gtk_label_set_markup (GTK_LABEL (about->version_label), about->version); gtk_widget_show (about->version_label); } else gtk_widget_hide (about->version_label); name_string = g_markup_printf_escaped ("%s", about->name); gtk_label_set_markup (GTK_LABEL (about->name_label), name_string); g_free (name_string); } /** * gtk_about_dialog_set_program_name: (attributes org.gtk.Method.set_property=program-name) * @about: a `GtkAboutDialog` * @name: (nullable): the program name * * Sets the name to display in the about dialog. * * If `name` is not set, the string returned * by `g_get_application_name()` is used. */ void gtk_about_dialog_set_program_name (GtkAboutDialog *about, const char *name) { char *tmp; g_return_if_fail (GTK_IS_ABOUT_DIALOG (about)); tmp = about->name; about->name = g_strdup (name ? name : g_get_application_name ()); g_free (tmp); update_name_version (about); g_object_notify_by_pspec (G_OBJECT (about), props[PROP_NAME]); } /** * gtk_about_dialog_get_version: (attributes org.gtk.Method.get_property=version) * @about: a `GtkAboutDialog` * * Returns the version string. * * Returns: (nullable): The version string */ const char * gtk_about_dialog_get_version (GtkAboutDialog *about) { g_return_val_if_fail (GTK_IS_ABOUT_DIALOG (about), NULL); return about->version; } /** * gtk_about_dialog_set_version: (attributes org.gtk.Method.set_property=version) * @about: a `GtkAboutDialog` * @version: (nullable): the version string * * Sets the version string to display in the about dialog. */ void gtk_about_dialog_set_version (GtkAboutDialog *about, const char *version) { char *tmp; g_return_if_fail (GTK_IS_ABOUT_DIALOG (about)); tmp = about->version; about->version = g_strdup (version); g_free (tmp); update_name_version (about); g_object_notify_by_pspec (G_OBJECT (about), props[PROP_VERSION]); } /** * gtk_about_dialog_get_copyright: (attributes org.gtk.Method.get_property=copyright) * @about: a `GtkAboutDialog` * * Returns the copyright string. * * Returns: (nullable): The copyright string */ const char * gtk_about_dialog_get_copyright (GtkAboutDialog *about) { g_return_val_if_fail (GTK_IS_ABOUT_DIALOG (about), NULL); return about->copyright; } /** * gtk_about_dialog_set_copyright: (attributes org.gtk.Method.set_property=copyright) * @about: a `GtkAboutDialog` * @copyright: (nullable): the copyright string * * Sets the copyright string to display in the about dialog. * * This should be a short string of one or two lines. */ void gtk_about_dialog_set_copyright (GtkAboutDialog *about, const char *copyright) { char *copyright_string, *tmp; g_return_if_fail (GTK_IS_ABOUT_DIALOG (about)); tmp = about->copyright; about->copyright = g_strdup (copyright); g_free (tmp); if (about->copyright != NULL) { copyright_string = g_markup_printf_escaped ("%s", about->copyright); gtk_label_set_markup (GTK_LABEL (about->copyright_label), copyright_string); g_free (copyright_string); gtk_widget_show (about->copyright_label); } else gtk_widget_hide (about->copyright_label); g_object_notify_by_pspec (G_OBJECT (about), props[PROP_COPYRIGHT]); } /** * gtk_about_dialog_get_comments: (attributes org.gtk.Method.set_property=comments) * @about: a `GtkAboutDialog` * * Returns the comments string. * * Returns: (nullable): The comments */ const char * gtk_about_dialog_get_comments (GtkAboutDialog *about) { g_return_val_if_fail (GTK_IS_ABOUT_DIALOG (about), NULL); return about->comments; } /** * gtk_about_dialog_set_comments: (attributes org.gtk.Method.set_property=comments) * @about: a `GtkAboutDialog` * @comments: (nullable): a comments string * * Sets the comments string to display in the about dialog. * * This should be a short string of one or two lines. */ void gtk_about_dialog_set_comments (GtkAboutDialog *about, const char *comments) { char *tmp; g_return_if_fail (GTK_IS_ABOUT_DIALOG (about)); tmp = about->comments; if (comments) { about->comments = g_strdup (comments); gtk_label_set_text (GTK_LABEL (about->comments_label), about->comments); gtk_widget_show (about->comments_label); } else { about->comments = NULL; gtk_widget_hide (about->comments_label); } g_free (tmp); g_object_notify_by_pspec (G_OBJECT (about), props[PROP_COMMENTS]); } /** * gtk_about_dialog_get_license: (attributes org.gtk.Method.get_property=license) * @about: a `GtkAboutDialog` * * Returns the license information. * * Returns: (nullable): The license information */ const char * gtk_about_dialog_get_license (GtkAboutDialog *about) { g_return_val_if_fail (GTK_IS_ABOUT_DIALOG (about), NULL); return about->license; } /** * gtk_about_dialog_set_license: (attributes org.gtk.Method.set_property=license) * @about: a `GtkAboutDialog` * @license: (nullable): the license information * * Sets the license information to be displayed in the * about dialog. * * If `license` is `NULL`, the license page is hidden. */ void gtk_about_dialog_set_license (GtkAboutDialog *about, const char *license) { char *tmp; g_return_if_fail (GTK_IS_ABOUT_DIALOG (about)); tmp = about->license; if (license) { about->license = g_strdup (license); about->license_type = GTK_LICENSE_CUSTOM; } else { about->license = NULL; about->license_type = GTK_LICENSE_UNKNOWN; } g_free (tmp); gtk_widget_hide (about->license_label); update_license_button_visibility (about); g_object_notify_by_pspec (G_OBJECT (about), props[PROP_LICENSE]); g_object_notify_by_pspec (G_OBJECT (about), props[PROP_LICENSE_TYPE]); } /** * gtk_about_dialog_get_system_information: (attributes org.gtk.Method.get_property=system-information) * @about: a `GtkAboutDialog` * * Returns the system information that is shown in the about dialog. * * Returns: (nullable): the system information */ const char * gtk_about_dialog_get_system_information (GtkAboutDialog *about) { g_return_val_if_fail (GTK_IS_ABOUT_DIALOG (about), NULL); return about->system_information; } /** * gtk_about_dialog_set_system_information: (attributes org.gtk.Method.set_property=system-information) * @about: a `GtkAboutDialog` * @system_information: (nullable): system information * * Sets the system information to be displayed in the about * dialog. * * If `system_information` is `NULL`, the system information * page is hidden. * * See [property@Gtk.AboutDialog:system-information]. */ void gtk_about_dialog_set_system_information (GtkAboutDialog *about, const char *system_information) { g_return_if_fail (GTK_IS_ABOUT_DIALOG (about)); g_free (about->system_information); about->system_information = g_strdup (system_information); update_system_button_visibility (about); g_object_notify_by_pspec (G_OBJECT (about), props[PROP_SYSTEM_INFORMATION]); } /** * gtk_about_dialog_get_wrap_license: (attributes org.gtk.Method.get_property=wrap-license) * @about: a `GtkAboutDialog` * * Returns whether the license text in the about dialog is * automatically wrapped. * * Returns: `TRUE` if the license text is wrapped */ gboolean gtk_about_dialog_get_wrap_license (GtkAboutDialog *about) { g_return_val_if_fail (GTK_IS_ABOUT_DIALOG (about), FALSE); return about->wrap_license; } /** * gtk_about_dialog_set_wrap_license: (attributes org.gtk.Method.set_property=wrap-license) * @about: a `GtkAboutDialog` * @wrap_license: whether to wrap the license * * Sets whether the license text in the about dialog should be * automatically wrapped. */ void gtk_about_dialog_set_wrap_license (GtkAboutDialog *about, gboolean wrap_license) { g_return_if_fail (GTK_IS_ABOUT_DIALOG (about)); wrap_license = wrap_license != FALSE; if (about->wrap_license != wrap_license) { about->wrap_license = wrap_license; g_object_notify_by_pspec (G_OBJECT (about), props[PROP_WRAP_LICENSE]); } } /** * gtk_about_dialog_get_website: (attributes org.gtk.Method.get_property=website) * @about: a `GtkAboutDialog` * * Returns the website URL. * * Returns: (nullable) (transfer none): The website URL */ const char * gtk_about_dialog_get_website (GtkAboutDialog *about) { g_return_val_if_fail (GTK_IS_ABOUT_DIALOG (about), NULL); return about->website_url; } /** * gtk_about_dialog_set_website: (attributes org.gtk.Method.set_property=website) * @about: a `GtkAboutDialog` * @website: (nullable): a URL string starting with `http://` * * Sets the URL to use for the website link. */ void gtk_about_dialog_set_website (GtkAboutDialog *about, const char *website) { char *tmp; g_return_if_fail (GTK_IS_ABOUT_DIALOG (about)); tmp = about->website_url; about->website_url = g_strdup (website); g_free (tmp); update_website (about); g_object_notify_by_pspec (G_OBJECT (about), props[PROP_WEBSITE]); } /** * gtk_about_dialog_get_website_label: (attributes org.gtk.Method.get_property=website-label) * @about: a `GtkAboutDialog` * * Returns the label used for the website link. * * Returns: (nullable) (transfer none): The label used for the website link */ const char * gtk_about_dialog_get_website_label (GtkAboutDialog *about) { g_return_val_if_fail (GTK_IS_ABOUT_DIALOG (about), NULL); return about->website_text; } /** * gtk_about_dialog_set_website_label: (attributes org.gtk.Method.set_property=website-label) * @about: a `GtkAboutDialog` * @website_label: the label used for the website link * * Sets the label to be used for the website link. */ void gtk_about_dialog_set_website_label (GtkAboutDialog *about, const char *website_label) { char *tmp; g_return_if_fail (GTK_IS_ABOUT_DIALOG (about)); tmp = about->website_text; about->website_text = g_strdup (website_label); g_free (tmp); update_website (about); g_object_notify_by_pspec (G_OBJECT (about), props[PROP_WEBSITE_LABEL]); } /** * gtk_about_dialog_get_authors: (attributes org.gtk.Method.get_property=authors) * @about: a `GtkAboutDialog` * * Returns the names of the authors which are displayed * in the credits page. * * Returns: (array zero-terminated=1) (transfer none): A * `NULL`-terminated string array containing the authors */ const char * const * gtk_about_dialog_get_authors (GtkAboutDialog *about) { g_return_val_if_fail (GTK_IS_ABOUT_DIALOG (about), NULL); return (const char * const *) about->authors; } /** * gtk_about_dialog_set_authors: (attributes org.gtk.Method.set_property=authors) * @about: a `GtkAboutDialog` * @authors: (array zero-terminated=1): the authors of the application * * Sets the names of the authors which are displayed * in the "Credits" page of the about dialog. */ void gtk_about_dialog_set_authors (GtkAboutDialog *about, const char **authors) { char **tmp; g_return_if_fail (GTK_IS_ABOUT_DIALOG (about)); tmp = about->authors; about->authors = g_strdupv ((char **)authors); g_strfreev (tmp); update_credits_button_visibility (about); g_object_notify_by_pspec (G_OBJECT (about), props[PROP_AUTHORS]); } /** * gtk_about_dialog_get_documenters: (attributes org.gtk.Method.get_property=documenters) * @about: a `GtkAboutDialog` * * Returns the name of the documenters which are displayed * in the credits page. * * Returns: (array zero-terminated=1) (transfer none): A * `NULL`-terminated string array containing the documenters */ const char * const * gtk_about_dialog_get_documenters (GtkAboutDialog *about) { g_return_val_if_fail (GTK_IS_ABOUT_DIALOG (about), NULL); return (const char * const *)about->documenters; } /** * gtk_about_dialog_set_documenters: (attributes org.gtk.Method.set_property=documenters) * @about: a `GtkAboutDialog` * @documenters: (array zero-terminated=1): the authors of the documentation * of the application * * Sets the names of the documenters which are displayed * in the "Credits" page. */ void gtk_about_dialog_set_documenters (GtkAboutDialog *about, const char **documenters) { char **tmp; g_return_if_fail (GTK_IS_ABOUT_DIALOG (about)); tmp = about->documenters; about->documenters = g_strdupv ((char **)documenters); g_strfreev (tmp); update_credits_button_visibility (about); g_object_notify_by_pspec (G_OBJECT (about), props[PROP_DOCUMENTERS]); } /** * gtk_about_dialog_get_artists: (attributes org.gtk.Method.get_property=artists) * @about: a `GtkAboutDialog` * * Returns the names of the artists which are displayed * in the credits page. * * Returns: (array zero-terminated=1) (transfer none): A * `NULL`-terminated string array containing the artists */ const char * const * gtk_about_dialog_get_artists (GtkAboutDialog *about) { g_return_val_if_fail (GTK_IS_ABOUT_DIALOG (about), NULL); return (const char * const *)about->artists; } /** * gtk_about_dialog_set_artists: (attributes org.gtk.Method.set_property=artists) * @about: a `GtkAboutDialog` * @artists: (array zero-terminated=1): the authors of the artwork * of the application * * Sets the names of the artists to be displayed * in the "Credits" page. */ void gtk_about_dialog_set_artists (GtkAboutDialog *about, const char **artists) { char **tmp; g_return_if_fail (GTK_IS_ABOUT_DIALOG (about)); tmp = about->artists; about->artists = g_strdupv ((char **)artists); g_strfreev (tmp); update_credits_button_visibility (about); g_object_notify_by_pspec (G_OBJECT (about), props[PROP_ARTISTS]); } /** * gtk_about_dialog_get_translator_credits: (attributes org.gtk.Method.get_property=translator-credits) * @about: a `GtkAboutDialog` * * Returns the translator credits string which is displayed * in the credits page. * * Returns: (nullable): The translator credits string */ const char * gtk_about_dialog_get_translator_credits (GtkAboutDialog *about) { g_return_val_if_fail (GTK_IS_ABOUT_DIALOG (about), NULL); return about->translator_credits; } /** * gtk_about_dialog_set_translator_credits: (attributes org.gtk.Method.set_property=translator-credits) * @about: a `GtkAboutDialog` * @translator_credits: (nullable): the translator credits * * Sets the translator credits string which is displayed in * the credits page. * * The intended use for this string is to display the translator * of the language which is currently used in the user interface. * Using `gettext()`, a simple way to achieve that is to mark the * string for translation: * * ```c * GtkWidget *about = gtk_about_dialog_new (); * gtk_about_dialog_set_translator_credits (GTK_ABOUT_DIALOG (about), * _("translator-credits")); * ``` * * It is a good idea to use the customary `msgid` “translator-credits” * for this purpose, since translators will already know the purpose of * that `msgid`, and since `GtkAboutDialog` will detect if “translator-credits” * is untranslated and omit translator credits. */ void gtk_about_dialog_set_translator_credits (GtkAboutDialog *about, const char *translator_credits) { char *tmp; g_return_if_fail (GTK_IS_ABOUT_DIALOG (about)); tmp = about->translator_credits; about->translator_credits = g_strdup (translator_credits); g_free (tmp); update_credits_button_visibility (about); g_object_notify_by_pspec (G_OBJECT (about), props[PROP_TRANSLATOR_CREDITS]); } /** * gtk_about_dialog_get_logo: (attributes org.gtk.Method.get_property=logo) * @about: a `GtkAboutDialog` * * Returns the paintable displayed as logo in the about dialog. * * Returns: (transfer none) (nullable): the paintable displayed as * logo or `NULL` if the logo is unset or has been set via * [method@Gtk.AboutDialog.set_logo_icon_name] */ GdkPaintable * gtk_about_dialog_get_logo (GtkAboutDialog *about) { g_return_val_if_fail (GTK_IS_ABOUT_DIALOG (about), NULL); if (gtk_image_get_storage_type (GTK_IMAGE (about->logo_image)) == GTK_IMAGE_PAINTABLE) return gtk_image_get_paintable (GTK_IMAGE (about->logo_image)); else return NULL; } /** * gtk_about_dialog_set_logo: (attributes org.gtk.Method.set_property=logo) * @about: a `GtkAboutDialog` * @logo: (nullable): a `GdkPaintable` * * Sets the logo in the about dialog. */ void gtk_about_dialog_set_logo (GtkAboutDialog *about, GdkPaintable *logo) { g_return_if_fail (GTK_IS_ABOUT_DIALOG (about)); g_return_if_fail (logo == NULL || GDK_IS_PAINTABLE (logo)); g_object_freeze_notify (G_OBJECT (about)); if (gtk_image_get_storage_type (GTK_IMAGE (about->logo_image)) == GTK_IMAGE_ICON_NAME) g_object_notify_by_pspec (G_OBJECT (about), props[PROP_LOGO_ICON_NAME]); gtk_image_set_from_paintable (GTK_IMAGE (about->logo_image), logo); g_object_notify_by_pspec (G_OBJECT (about), props[PROP_LOGO]); g_object_thaw_notify (G_OBJECT (about)); } /** * gtk_about_dialog_get_logo_icon_name: (attributes org.gtk.Method.get_property=logo-icon-name) * @about: a `GtkAboutDialog` * * Returns the icon name displayed as logo in the about dialog. * * Returns: (transfer none) (nullable): the icon name displayed as logo, * or `NULL` if the logo has been set via [method@Gtk.AboutDialog.set_logo] */ const char * gtk_about_dialog_get_logo_icon_name (GtkAboutDialog *about) { g_return_val_if_fail (GTK_IS_ABOUT_DIALOG (about), NULL); if (gtk_image_get_storage_type (GTK_IMAGE (about->logo_image)) != GTK_IMAGE_ICON_NAME) return NULL; return gtk_image_get_icon_name (GTK_IMAGE (about->logo_image)); } /** * gtk_about_dialog_set_logo_icon_name: (attributes org.gtk.Method.set_property=logo-icon-name) * @about: a `GtkAboutDialog` * @icon_name: (nullable): an icon name * * Sets the icon name to be displayed as logo in the about dialog. */ void gtk_about_dialog_set_logo_icon_name (GtkAboutDialog *about, const char *icon_name) { g_return_if_fail (GTK_IS_ABOUT_DIALOG (about)); g_object_freeze_notify (G_OBJECT (about)); if (gtk_image_get_storage_type (GTK_IMAGE (about->logo_image)) == GTK_IMAGE_PAINTABLE) g_object_notify_by_pspec (G_OBJECT (about), props[PROP_LOGO]); gtk_image_set_from_icon_name (GTK_IMAGE (about->logo_image), icon_name); g_object_notify_by_pspec (G_OBJECT (about), props[PROP_LOGO_ICON_NAME]); g_object_thaw_notify (G_OBJECT (about)); } static void follow_if_link (GtkAboutDialog *about, GtkTextView *text_view, GtkTextIter *iter) { GSList *tags = NULL, *tagp = NULL; char *uri = NULL; tags = gtk_text_iter_get_tags (iter); for (tagp = tags; tagp != NULL && !uri; tagp = tagp->next) { GtkTextTag *tag = tagp->data; uri = g_object_get_data (G_OBJECT (tag), "uri"); if (uri) emit_activate_link (about, uri); if (uri && !g_ptr_array_find_with_equal_func (about->visited_links, uri, (GCompareFunc)strcmp, NULL)) { GdkRGBA visited_link_color; GtkStyleContext *context = gtk_widget_get_style_context (GTK_WIDGET (about)); gtk_style_context_save (context); gtk_style_context_set_state (context, gtk_style_context_get_state (context) | GTK_STATE_FLAG_VISITED); gtk_style_context_get_color (context, &visited_link_color); gtk_style_context_restore (context); g_object_set (G_OBJECT (tag), "foreground-rgba", &visited_link_color, NULL); g_ptr_array_add (about->visited_links, g_strdup (uri)); } } g_slist_free (tags); } static gboolean text_view_key_pressed (GtkEventController *controller, guint keyval, guint keycode, GdkModifierType state, GtkAboutDialog *about) { GtkWidget *text_view; GtkTextIter iter; GtkTextBuffer *buffer; text_view = gtk_event_controller_get_widget (controller); switch (keyval) { case GDK_KEY_Return: case GDK_KEY_ISO_Enter: case GDK_KEY_KP_Enter: buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (text_view)); gtk_text_buffer_get_iter_at_mark (buffer, &iter, gtk_text_buffer_get_insert (buffer)); follow_if_link (about, GTK_TEXT_VIEW (text_view), &iter); break; default: break; } return FALSE; } static void text_view_released (GtkGestureClick *gesture, int n_press, double x, double y, GtkAboutDialog *about) { GtkWidget *text_view; GtkTextIter start, end, iter; GtkTextBuffer *buffer; int tx, ty; if (gtk_gesture_single_get_current_button (GTK_GESTURE_SINGLE (gesture)) != GDK_BUTTON_PRIMARY) return; text_view = gtk_event_controller_get_widget (GTK_EVENT_CONTROLLER (gesture)); buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (text_view)); /* we shouldn't follow a link if the user has selected something */ gtk_text_buffer_get_selection_bounds (buffer, &start, &end); if (gtk_text_iter_get_offset (&start) != gtk_text_iter_get_offset (&end)) return; gtk_text_view_window_to_buffer_coords (GTK_TEXT_VIEW (text_view), GTK_TEXT_WINDOW_WIDGET, x, y, &tx, &ty); gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW (text_view), &iter, tx, ty); follow_if_link (about, GTK_TEXT_VIEW (text_view), &iter); } static void set_cursor_if_appropriate (GtkAboutDialog *about, GtkTextView *text_view, int x, int y) { GSList *tags = NULL, *tagp = NULL; GtkTextIter iter; gboolean hovering_over_link = FALSE; gtk_text_view_get_iter_at_location (text_view, &iter, x, y); tags = gtk_text_iter_get_tags (&iter); for (tagp = tags; tagp != NULL; tagp = tagp->next) { GtkTextTag *tag = tagp->data; char *uri = g_object_get_data (G_OBJECT (tag), "uri"); if (uri != NULL) { hovering_over_link = TRUE; break; } } if (hovering_over_link != about->hovering_over_link) { about->hovering_over_link = hovering_over_link; if (hovering_over_link) gtk_widget_set_cursor_from_name (GTK_WIDGET (text_view), "pointer"); else gtk_widget_set_cursor_from_name (GTK_WIDGET (text_view), "text"); } g_slist_free (tags); } static void text_view_motion (GtkEventControllerMotion *motion, double x, double y, GtkAboutDialog *about) { int tx, ty; GtkWidget *widget; widget = gtk_event_controller_get_widget (GTK_EVENT_CONTROLLER (motion)); gtk_text_view_window_to_buffer_coords (GTK_TEXT_VIEW (widget), GTK_TEXT_WINDOW_WIDGET, x, y, &tx, &ty); set_cursor_if_appropriate (about, GTK_TEXT_VIEW (widget), tx, ty); } static GtkTextBuffer * text_buffer_new (GtkAboutDialog *about, char **strings) { char **p; char *q0, *q1, *q2, *r1, *r2; GtkTextBuffer *buffer; GdkRGBA color; GdkRGBA link_color; GdkRGBA visited_link_color; GtkTextIter start_iter, end_iter; GtkTextTag *tag; GtkStateFlags state = gtk_widget_get_state_flags (GTK_WIDGET (about)); GtkStyleContext *context = gtk_widget_get_style_context (GTK_WIDGET (about)); gtk_style_context_save (context); gtk_style_context_set_state (context, state | GTK_STATE_FLAG_LINK); gtk_style_context_get_color (context, &link_color); gtk_style_context_set_state (context, state | GTK_STATE_FLAG_VISITED); gtk_style_context_get_color (context, &visited_link_color); gtk_style_context_restore (context); buffer = gtk_text_buffer_new (NULL); for (p = strings; *p; p++) { q0 = *p; while (*q0) { q1 = strchr (q0, '<'); q2 = q1 ? strchr (q1, '>') : NULL; r1 = strstr (q0, "http://"); r2 = strstr (q0, "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) { GtkTextIter end; char *link; char *uri; const char *link_type; if (*q1 == '<') { gtk_text_buffer_insert_at_cursor (buffer, q0, q1 - q0 + 1); gtk_text_buffer_get_end_iter (buffer, &end); q1++; link_type = "email"; } else { gtk_text_buffer_insert_at_cursor (buffer, q0, q1 - q0); gtk_text_buffer_get_end_iter (buffer, &end); link_type = "uri"; } q0 = q2; link = g_strndup (q1, q2 - q1); if (g_ptr_array_find_with_equal_func (about->visited_links, link, (GCompareFunc)strcmp, NULL)) color = visited_link_color; else color = link_color; tag = gtk_text_buffer_create_tag (buffer, NULL, "foreground-rgba", &color, "underline", PANGO_UNDERLINE_SINGLE, NULL); if (strcmp (link_type, "email") == 0) { char *escaped; escaped = g_uri_escape_string (link, NULL, FALSE); uri = g_strconcat ("mailto:", escaped, NULL); g_free (escaped); } else { uri = g_strdup (link); } g_object_set_data_full (G_OBJECT (tag), I_("uri"), uri, g_free); gtk_text_buffer_insert_with_tags (buffer, &end, link, -1, tag, NULL); g_free (link); } else { gtk_text_buffer_insert_at_cursor (buffer, q0, -1); break; } } if (p[1]) gtk_text_buffer_insert_at_cursor (buffer, "\n", 1); } tag = gtk_text_buffer_create_tag (buffer, NULL, "scale", PANGO_SCALE_SMALL, NULL); gtk_text_buffer_get_start_iter (buffer, &start_iter); gtk_text_buffer_get_end_iter (buffer, &end_iter); gtk_text_buffer_apply_tag (buffer, tag, &start_iter, &end_iter); gtk_text_buffer_set_enable_undo (buffer, FALSE); return buffer; } static void add_credits_section (GtkAboutDialog *about, GtkGrid *grid, int *row, char *title, char **people) { GtkWidget *label; char *markup; char **p; char *q0, *q1, *q2, *r1, *r2; if (people == NULL) return; markup = g_strdup_printf ("%s", title); label = gtk_label_new (markup); gtk_label_set_use_markup (GTK_LABEL (label), TRUE); g_free (markup); gtk_widget_set_halign (label, GTK_ALIGN_END); gtk_widget_set_valign (label, GTK_ALIGN_CENTER); gtk_grid_attach (grid, label, 0, *row, 1, 1); for (p = people; *p; p++) { GString *str; str = g_string_new (""); q0 = *p; while (*q0) { q1 = strchr (q0, '<'); q2 = q1 ? strchr (q1, '>') : NULL; r1 = strstr (q0, "http://"); r2 = strstr (q0, "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))) { q1 = r1; q2 = r2; } else if (q1 && (q1[1] == 'a' || q1[1] == 'A') && q1[2] == ' ') { /* if it is a link leave it for the label to parse */ q1 = NULL; } if (q1 && q2) { char *link; char *text; char *name; if (*q1 == '<') { /* email */ char *escaped; text = g_strstrip (g_strndup (q0, q1 - q0)); name = g_markup_escape_text (text, -1); q1++; link = g_strndup (q1, q2 - q1); q2++; escaped = g_uri_escape_string (link, NULL, FALSE); g_string_append_printf (str, "%s", escaped, name[0] ? name : link); g_free (escaped); g_free (link); g_free (text); g_free (name); } else { /* uri */ text = g_strstrip (g_strndup (q0, q1 - q0)); name = g_markup_escape_text (text, -1); link = g_strndup (q1, q2 - q1); g_string_append_printf (str, "%s", link, name[0] ? name : link); g_free (link); g_free (text); g_free (name); } q0 = q2; } else { g_string_append (str, q0); break; } } g_string_append (str, ""); label = gtk_label_new (str->str); gtk_label_set_use_markup (GTK_LABEL (label), TRUE); gtk_label_set_selectable (GTK_LABEL (label), TRUE); g_signal_connect_swapped (label, "activate-link", G_CALLBACK (emit_activate_link), about); g_string_free (str, TRUE); gtk_widget_set_halign (label, GTK_ALIGN_START); gtk_widget_set_valign (label, GTK_ALIGN_CENTER); gtk_grid_attach (grid, label, 1, *row, 1, 1); gtk_widget_show (label); (*row)++; } /* skip one at the end */ label = gtk_label_new (""); gtk_grid_attach (grid, label, 1, *row, 1, 1); (*row)++; } static void populate_credits_page (GtkAboutDialog *about) { int row; row = 0; if (about->authors != NULL) add_credits_section (about, GTK_GRID (about->credits_grid), &row, _("Created by"), about->authors); if (about->documenters != NULL) add_credits_section (about, GTK_GRID (about->credits_grid), &row, _("Documented by"), about->documenters); /* Don't show an untranslated gettext msgid */ if (about->translator_credits != NULL && strcmp (about->translator_credits, "translator_credits") != 0 && strcmp (about->translator_credits, "translator-credits") != 0) { char **translators; translators = g_strsplit (about->translator_credits, "\n", 0); add_credits_section (about, GTK_GRID (about->credits_grid), &row, _("Translated by"), translators); g_strfreev (translators); } if (about->artists != NULL) add_credits_section (about, GTK_GRID (about->credits_grid), &row, _("Design by"), about->artists); if (about->credit_sections != NULL) { GSList *cs; for (cs = about->credit_sections; cs != NULL; cs = cs->next) { CreditSection *section = cs->data; add_credits_section (about, GTK_GRID (about->credits_grid), &row, section->heading, section->people); } } } static void populate_license_page (GtkAboutDialog *about) { GtkTextBuffer *buffer; char *strings[2]; gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (about->license_view), about->wrap_license ? GTK_WRAP_WORD : GTK_WRAP_NONE); strings[0] = about->license; strings[1] = NULL; buffer = text_buffer_new (about, strings); gtk_text_view_set_buffer (GTK_TEXT_VIEW (about->license_view), buffer); g_object_unref (buffer); } static void populate_system_page (GtkAboutDialog *about) { GtkTextBuffer *buffer; char *strings[2]; strings[0] = about->system_information; strings[1] = NULL; buffer = text_buffer_new (about, strings); gtk_text_view_set_buffer (GTK_TEXT_VIEW (about->system_view), buffer); g_object_unref (buffer); } /** * gtk_about_dialog_new: * * Creates a new `GtkAboutDialog`. * * Returns: a newly created `GtkAboutDialog` */ GtkWidget * gtk_about_dialog_new (void) { return g_object_new (GTK_TYPE_ABOUT_DIALOG, NULL); } static gboolean close_cb (GtkAboutDialog *about, gpointer user_data) { gtk_stack_set_visible_child_name (GTK_STACK (about->stack), "main"); gtk_widget_hide (GTK_WIDGET (about)); return TRUE; } /** * gtk_show_about_dialog: * @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 dialog. * * The constructed dialog is associated with the parent window and * reused for future invocations of this function. */ void gtk_show_about_dialog (GtkWindow *parent, const char *first_property_name, ...) { static GtkWidget *global_about_dialog = NULL; GtkWidget *dialog = NULL; va_list var_args; if (parent) dialog = g_object_get_data (G_OBJECT (parent), "gtk-about-dialog"); else dialog = global_about_dialog; if (!dialog) { dialog = gtk_about_dialog_new (); gtk_window_set_hide_on_close (GTK_WINDOW (dialog), TRUE); g_object_ref_sink (dialog); /* Hide the dialog on close request */ g_signal_connect (dialog, "close-request", G_CALLBACK (close_cb), NULL); va_start (var_args, first_property_name); g_object_set_valist (G_OBJECT (dialog), first_property_name, var_args); va_end (var_args); if (parent) { gtk_window_set_modal (GTK_WINDOW (dialog), TRUE); gtk_window_set_transient_for (GTK_WINDOW (dialog), parent); gtk_window_set_destroy_with_parent (GTK_WINDOW (dialog), TRUE); g_object_set_data_full (G_OBJECT (parent), I_("gtk-about-dialog"), dialog, g_object_unref); } else global_about_dialog = dialog; } gtk_window_present (GTK_WINDOW (dialog)); } /** * gtk_about_dialog_set_license_type: (attributes org.gtk.Method.set_property=license-type) * @about: a `GtkAboutDialog` * @license_type: the type of license * * Sets the license of the application showing the about dialog from a * list of known licenses. * * This function overrides the license set using * [method@Gtk.AboutDialog.set_license]. */ void gtk_about_dialog_set_license_type (GtkAboutDialog *about, GtkLicense license_type) { g_return_if_fail (GTK_IS_ABOUT_DIALOG (about)); g_return_if_fail (license_type >= GTK_LICENSE_UNKNOWN && license_type < G_N_ELEMENTS (gtk_license_info)); if (about->license_type != license_type) { g_object_freeze_notify (G_OBJECT (about)); about->license_type = license_type; /* custom licenses use the contents of the :license property */ if (about->license_type != GTK_LICENSE_CUSTOM) { const char *name; const char *url; char *license_string; GString *str; name = _(gtk_license_info[about->license_type].name); url = gtk_license_info[about->license_type].url; if (url == NULL) url = about->website_url; str = g_string_sized_new (256); /* Translators: this is the license preamble; the string at the end * contains the name of the license as link text. */ g_string_append_printf (str, _("This program comes with absolutely no warranty.\nSee the %s for details."), url, name); g_free (about->license); about->license = g_string_free (str, FALSE); about->wrap_license = TRUE; license_string = g_strdup_printf ("%s", about->license); gtk_label_set_markup (GTK_LABEL (about->license_label), license_string); g_free (license_string); gtk_widget_show (about->license_label); g_object_notify_by_pspec (G_OBJECT (about), props[PROP_WRAP_LICENSE]); g_object_notify_by_pspec (G_OBJECT (about), props[PROP_LICENSE]); } else { gtk_widget_show (about->license_label); } update_license_button_visibility (about); g_object_notify_by_pspec (G_OBJECT (about), props[PROP_LICENSE_TYPE]); g_object_thaw_notify (G_OBJECT (about)); } } /** * gtk_about_dialog_get_license_type: (attributes org.gtk.Method.get_property=license-type) * @about: a `GtkAboutDialog` * * Retrieves the license type. * * Returns: a [enum@Gtk.License] value */ GtkLicense gtk_about_dialog_get_license_type (GtkAboutDialog *about) { g_return_val_if_fail (GTK_IS_ABOUT_DIALOG (about), GTK_LICENSE_UNKNOWN); return about->license_type; } /** * gtk_about_dialog_add_credit_section: * @about: A `GtkAboutDialog` * @section_name: The name of the section * @people: (array zero-terminated=1): The people who belong to that section * * Creates a new section in the "Credits" page. */ void gtk_about_dialog_add_credit_section (GtkAboutDialog *about, const char *section_name, const char **people) { CreditSection *new_entry; g_return_if_fail (GTK_IS_ABOUT_DIALOG (about)); g_return_if_fail (section_name != NULL); g_return_if_fail (people != NULL); new_entry = g_slice_new (CreditSection); new_entry->heading = g_strdup ((char *)section_name); new_entry->people = g_strdupv ((char **)people); about->credit_sections = g_slist_append (about->credit_sections, new_entry); update_credits_button_visibility (about); }