1/* GTK - The GIMP Toolkit
2 * Copyright (C) 2001 CodeFactory AB
3 * Copyright (C) 2001, 2002 Anders Carlsson
4 * Copyright (C) 2003, 2004 Matthias Clasen <mclasen@redhat.com>
5 *
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Library General Public
8 * License as published by the Free Software Foundation; either
9 * version 2 of the License, or (at your option) any later version.
10 *
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Library General Public License for more details.
15 *
16 * You should have received a copy of the GNU Library General Public
17 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
18 */
19
20/*
21 * Author: Anders Carlsson <andersca@gnome.org>
22 *
23 * Modified by the GTK+ Team and others 1997-2004. See the AUTHORS
24 * file for a list of people on the GTK+ Team. See the ChangeLog
25 * files for a list of changes. These files are distributed with
26 * GTK+ at ftp://ftp.gtk.org/pub/gtk/.
27 */
28
29#include "config.h"
30
31#include <string.h>
32
33#include <cairo-gobject.h>
34
35#include "gtkaboutdialog.h"
36#include "gtkbutton.h"
37#include "gtkgrid.h"
38#include "gtkbox.h"
39#include "gtkicontheme.h"
40#include "gtkimage.h"
41#include "gtklabel.h"
42#include "gtkmarshalers.h"
43#include "gtkstack.h"
44#include "gtkorientable.h"
45#include "gtkscrolledwindow.h"
46#include "gtktextview.h"
47#include "gtkshow.h"
48#include "gtkmain.h"
49#include "gtktogglebutton.h"
50#include "gtktypebuiltins.h"
51#include "gtkstack.h"
52#include "gtkstackswitcher.h"
53#include "gtksettings.h"
54#include "gtkheaderbar.h"
55#include "gtkprivate.h"
56#include "gtkintl.h"
57#include "gtkeventcontrollermotion.h"
58#include "gtkeventcontrollerkey.h"
59#include "gtkgestureclick.h"
60#include "gtkstylecontext.h"
61
62
63/**
64 * GtkAboutDialog:
65 *
66 * The `GtkAboutDialog` offers a simple way to display information about
67 * a program.
68 *
69 * The shown information includes the programs' logo, name, copyright,
70 * website and license. It is also possible to give credits to the authors,
71 * documenters, translators and artists who have worked on the program.
72 *
73 * An about dialog is typically opened when the user selects the `About`
74 * option from the `Help` menu. All parts of the dialog are optional.
75 *
76 * ![An example GtkAboutDialog](aboutdialog.png)
77 *
78 * About dialogs often contain links and email addresses. `GtkAboutDialog`
79 * displays these as clickable links. By default, it calls [func@Gtk.show_uri]
80 * when a user clicks one. The behaviour can be overridden with the
81 * [signal@Gtk.AboutDialog::activate-link] signal.
82 *
83 * To specify a person with an email address, use a string like
84 * `Edgar Allan Poe <edgar@poe.com>`. To specify a website with a title,
85 * use a string like `GTK team https://www.gtk.org`.
86 *
87 * To make constructing a `GtkAboutDialog` as convenient as possible, you can
88 * use the function [func@Gtk.show_about_dialog] which constructs and shows
89 * a dialog and keeps it around so that it can be shown again.
90 *
91 * Note that GTK sets a default title of `_("About %s")` on the dialog
92 * window (where `%s` is replaced by the name of the application, but in
93 * order to ensure proper translation of the title, applications should
94 * set the title property explicitly when constructing a `GtkAboutDialog`,
95 * as shown in the following example:
96 *
97 * ```c
98 * GFile *logo_file = g_file_new_for_path ("./logo.png");
99 * GdkTexture *example_logo = gdk_texture_new_from_file (logo_file, NULL);
100 * g_object_unref (logo_file);
101 *
102 * gtk_show_about_dialog (NULL,
103 * "program-name", "ExampleCode",
104 * "logo", example_logo,
105 * "title", _("About ExampleCode"),
106 * NULL);
107 * ```
108 *
109 * ## CSS nodes
110 *
111 * `GtkAboutDialog` has a single CSS node with the name `window` and style
112 * class `.aboutdialog`.
113
114 */
115
116typedef struct
117{
118 const char *name;
119 const char *url;
120} LicenseInfo;
121
122/* LicenseInfo for each GtkLicense type; keep in the same order as the enumeration */
123static const LicenseInfo gtk_license_info [] = {
124 { N_("License"), NULL },
125 { N_("Custom License") , NULL },
126 { N_("GNU General Public License, version 2 or later"), "https://www.gnu.org/licenses/old-licenses/gpl-2.0.html" },
127 { N_("GNU General Public License, version 3 or later"), "https://www.gnu.org/licenses/gpl-3.0.html" },
128 { N_("GNU Lesser General Public License, version 2.1 or later"), "https://www.gnu.org/licenses/old-licenses/lgpl-2.1.html" },
129 { N_("GNU Lesser General Public License, version 3 or later"), "https://www.gnu.org/licenses/lgpl-3.0.html" },
130 { N_("BSD 2-Clause License"), "https://opensource.org/licenses/bsd-license.php" },
131 { N_("The MIT License (MIT)"), "https://opensource.org/licenses/mit-license.php" },
132 { N_("Artistic License 2.0"), "https://opensource.org/licenses/artistic-license-2.0.php" },
133 { N_("GNU General Public License, version 2 only"), "https://www.gnu.org/licenses/old-licenses/gpl-2.0.html" },
134 { N_("GNU General Public License, version 3 only"), "https://www.gnu.org/licenses/gpl-3.0.html" },
135 { N_("GNU Lesser General Public License, version 2.1 only"), "https://www.gnu.org/licenses/old-licenses/lgpl-2.1.html" },
136 { N_("GNU Lesser General Public License, version 3 only"), "https://www.gnu.org/licenses/lgpl-3.0.html" },
137 { N_("GNU Affero General Public License, version 3 or later"), "https://www.gnu.org/licenses/agpl-3.0.html" },
138 { N_("GNU Affero General Public License, version 3 only"), "https://www.gnu.org/licenses/agpl-3.0.html" },
139 { N_("BSD 3-Clause License"), "https://opensource.org/licenses/BSD-3-Clause" },
140 { N_("Apache License, Version 2.0"), "https://opensource.org/licenses/Apache-2.0" },
141 { N_("Mozilla Public License 2.0"), "https://opensource.org/licenses/MPL-2.0" }
142};
143/* Keep this static assertion updated with the last element of the
144 * enumeration, and make sure it matches the last element of the array */
145G_STATIC_ASSERT (G_N_ELEMENTS (gtk_license_info) - 1 == GTK_LICENSE_MPL_2_0);
146
147typedef struct
148{
149 char *heading;
150 char **people;
151} CreditSection;
152
153typedef struct _GtkAboutDialogClass GtkAboutDialogClass;
154
155struct _GtkAboutDialog
156{
157 GtkWindow parent_instance;
158
159 char *name;
160 char *version;
161 char *copyright;
162 char *comments;
163 char *website_url;
164 char *website_text;
165 char *translator_credits;
166 char *license;
167 char *system_information;
168
169 char **authors;
170 char **documenters;
171 char **artists;
172
173 GSList *credit_sections;
174
175 gboolean credits_page_initialized;
176 gboolean license_page_initialized;
177 gboolean system_page_initialized;
178
179 GtkWidget *stack;
180 GtkWidget *stack_switcher;
181
182 GtkWidget *logo_image;
183 GtkWidget *name_label;
184 GtkWidget *version_label;
185 GtkWidget *comments_label;
186 GtkWidget *copyright_label;
187 GtkWidget *license_label;
188 GtkWidget *website_label;
189
190 GtkWidget *credits_page;
191 GtkWidget *license_page;
192 GtkWidget *system_page;
193
194 GtkWidget *credits_grid;
195 GtkWidget *license_view;
196 GtkWidget *system_view;
197
198 GPtrArray *visited_links;
199
200 GtkLicense license_type;
201
202 guint hovering_over_link : 1;
203 guint wrap_license : 1;
204 guint in_child_changed : 1;
205};
206
207struct _GtkAboutDialogClass
208{
209 GtkWindowClass parent_class;
210
211 gboolean (*activate_link) (GtkAboutDialog *dialog,
212 const char *uri);
213};
214
215enum
216{
217 PROP_0,
218 PROP_NAME,
219 PROP_VERSION,
220 PROP_COPYRIGHT,
221 PROP_COMMENTS,
222 PROP_WEBSITE,
223 PROP_WEBSITE_LABEL,
224 PROP_LICENSE,
225 PROP_SYSTEM_INFORMATION,
226 PROP_AUTHORS,
227 PROP_DOCUMENTERS,
228 PROP_TRANSLATOR_CREDITS,
229 PROP_ARTISTS,
230 ,
231 PROP_LOGO_ICON_NAME,
232 PROP_WRAP_LICENSE,
233 PROP_LICENSE_TYPE,
234 LAST_PROP
235};
236
237static void gtk_about_dialog_finalize (GObject *object);
238static void gtk_about_dialog_get_property (GObject *object,
239 guint prop_id,
240 GValue *value,
241 GParamSpec *pspec);
242static void gtk_about_dialog_set_property (GObject *object,
243 guint prop_id,
244 const GValue *value,
245 GParamSpec *pspec);
246static void update_name_version (GtkAboutDialog *about);
247static void follow_if_link (GtkAboutDialog *about,
248 GtkTextView *text_view,
249 GtkTextIter *iter);
250static void set_cursor_if_appropriate (GtkAboutDialog *about,
251 GtkTextView *text_view,
252 int x,
253 int y);
254static void populate_credits_page (GtkAboutDialog *about);
255static void populate_license_page (GtkAboutDialog *about);
256static void populate_system_page (GtkAboutDialog *about);
257static gboolean gtk_about_dialog_activate_link (GtkAboutDialog *about,
258 const char *uri);
259static gboolean emit_activate_link (GtkAboutDialog *about,
260 const char *uri);
261static gboolean text_view_key_pressed (GtkEventController *controller,
262 guint keyval,
263 guint keycode,
264 GdkModifierType state,
265 GtkAboutDialog *about);
266static void text_view_released (GtkGestureClick *press,
267 int n,
268 double x,
269 double y,
270 GtkAboutDialog *about);
271static void text_view_motion (GtkEventControllerMotion *motion,
272 double x,
273 double y,
274 GtkAboutDialog *about);
275
276enum {
277 ACTIVATE_LINK,
278 LAST_SIGNAL
279};
280
281static guint signals[LAST_SIGNAL] = { 0 };
282static GParamSpec *props[LAST_PROP] = { NULL, };
283
284G_DEFINE_TYPE (GtkAboutDialog, gtk_about_dialog, GTK_TYPE_WINDOW)
285
286static gboolean
287stack_visible_child_notify (GtkStack *stack,
288 GParamSpec *pspec,
289 GtkAboutDialog *about)
290{
291 GtkWidget *child;
292
293 child = gtk_stack_get_visible_child (stack);
294 if (child == about->credits_page)
295 {
296 if (!about->credits_page_initialized)
297 {
298 populate_credits_page (about);
299 about->credits_page_initialized = TRUE;
300 }
301 }
302 else if (child == about->license_page)
303 {
304 if (!about->license_page_initialized)
305 {
306 populate_license_page (about);
307 about->license_page_initialized = TRUE;
308 }
309 }
310 else if (child == about->system_page)
311 {
312 if (!about->system_page_initialized)
313 {
314 populate_system_page (about);
315 about->system_page_initialized = TRUE;
316 }
317 }
318
319 return FALSE;
320}
321
322static void
323gtk_about_dialog_map (GtkWidget *widget)
324{
325 GtkAboutDialog *about = GTK_ABOUT_DIALOG (widget);
326
327 if (gtk_widget_get_visible (widget: about->stack_switcher))
328 gtk_widget_grab_focus (widget: gtk_widget_get_first_child (widget: about->stack_switcher));
329
330 GTK_WIDGET_CLASS (gtk_about_dialog_parent_class)->map (widget);
331}
332
333static void
334gtk_about_dialog_class_init (GtkAboutDialogClass *klass)
335{
336 GObjectClass *object_class;
337 GtkWidgetClass *widget_class;
338
339 object_class = (GObjectClass *)klass;
340 widget_class = (GtkWidgetClass *)klass;
341
342 object_class->set_property = gtk_about_dialog_set_property;
343 object_class->get_property = gtk_about_dialog_get_property;
344
345 object_class->finalize = gtk_about_dialog_finalize;
346
347 widget_class->map = gtk_about_dialog_map;
348
349 klass->activate_link = gtk_about_dialog_activate_link;
350
351 /**
352 * GtkAboutDialog::activate-link:
353 * @label: The object on which the signal was emitted
354 * @uri: the URI that is activated
355 *
356 * Emitted every time a URL is activated.
357 *
358 * Applications may connect to it to override the default behaviour,
359 * which is to call [func@Gtk.show_uri].
360 *
361 * Returns: `TRUE` if the link has been activated
362 */
363 signals[ACTIVATE_LINK] =
364 g_signal_new (I_("activate-link"),
365 G_TYPE_FROM_CLASS (object_class),
366 signal_flags: G_SIGNAL_RUN_LAST,
367 G_STRUCT_OFFSET (GtkAboutDialogClass, activate_link),
368 accumulator: _gtk_boolean_handled_accumulator, NULL,
369 c_marshaller: _gtk_marshal_BOOLEAN__STRING,
370 G_TYPE_BOOLEAN, n_params: 1, G_TYPE_STRING);
371
372 /**
373 * GtkAboutDialog:program-name: (attributes org.gtk.Property.get=gtk_about_dialog_get_program_name org.gtk.Property.set=gtk_about_dialog_set_program_name)
374 *
375 * The name of the program.
376 *
377 * If this is not set, it defaults to the value returned by
378 * `g_get_application_name()`.
379 */
380 props[PROP_NAME] =
381 g_param_spec_string (name: "program-name",
382 P_("Program name"),
383 P_("The name of the program. If this is not set, it defaults to g_get_application_name()"),
384 NULL,
385 GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
386
387 /**
388 * GtkAboutDialog:version: (attributes org.gtk.Property.get=gtk_about_dialog_get_version org.gtk.Property.set=gtk_about_dialog_set_version)
389 *
390 * The version of the program.
391 */
392 props[PROP_VERSION] =
393 g_param_spec_string (name: "version",
394 P_("Program version"),
395 P_("The version of the program"),
396 NULL,
397 GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
398
399 /**
400 * GtkAboutDialog:copyright: (attributes org.gtk.Property.get=gtk_about_dialog_get_copyright org.gtk.Property.set=gtk_about_dialog_set_copyright)
401 *
402 * Copyright information for the program.
403 */
404 props[PROP_COPYRIGHT] =
405 g_param_spec_string (name: "copyright",
406 P_("Copyright string"),
407 P_("Copyright information for the program"),
408 NULL,
409 GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
410
411 /**
412 * GtkAboutDialog:comments: (attributes org.gtk.Property.get=gtk_about_dialog_get_comments org.gtk.Property.set=gtk_about_dialog_set_comments)
413 *
414 * Comments about the program.
415 *
416 * This string is displayed in a label in the main dialog, thus it
417 * should be a short explanation of the main purpose of the program,
418 * not a detailed list of features.
419 */
420 props[PROP_COMMENTS] =
421 g_param_spec_string (name: "comments",
422 P_("Comments string"),
423 P_("Comments about the program"),
424 NULL,
425 GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
426
427 /**
428 * GtkAboutDialog:license: (attributes org.gtk.Property.get=gtk_about_dialog_get_license org.gtk.Property.set=gtk_about_dialog_set_license)
429 *
430 * The license of the program, as free-form text.
431 *
432 * This string is displayed in a text view in a secondary dialog, therefore
433 * it is fine to use a long multi-paragraph text. Note that the text is only
434 * wrapped in the text view if the "wrap-license" property is set to `TRUE`;
435 * otherwise the text itself must contain the intended linebreaks.
436 *
437 * When setting this property to a non-`NULL` value, the
438 * [property@Gtk.AboutDialog:license-type] property is set to
439 * `GTK_LICENSE_CUSTOM` as a side effect.
440 *
441 * The text may contain links in this format `<http://www.some.place/>`
442 * and email references in the form `<mail-to@some.body>`, and these will
443 * be converted into clickable links.
444 */
445 props[PROP_LICENSE] =
446 g_param_spec_string (name: "license",
447 P_("License"),
448 P_("The license of the program"),
449 NULL,
450 GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
451
452 /**
453 * GtkAboutDialog:system-information: (attributes org.gtk.Property.get=gtk_about_dialog_get_system_information org.gtk.Property.set=gtk_about_dialog_set_system_information)
454 *
455 * Information about the system on which the program is running.
456 *
457 * This information is displayed in a separate page, therefore it is fine
458 * to use a long multi-paragraph text. Note that the text should contain
459 * the intended linebreaks.
460 *
461 * The text may contain links in this format `<http://www.some.place/>`
462 * and email references in the form `<mail-to@some.body>`, and these will
463 * be converted into clickable links.
464 */
465 props[PROP_SYSTEM_INFORMATION] =
466 g_param_spec_string (name: "system-information",
467 P_("System Information"),
468 P_("Information about the system on which the program is running"),
469 NULL,
470 GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
471
472 /**
473 * GtkAboutDialog:license-type: (attributes org.gtk.Property.get=gtk_about_dialog_get_license_type org.gtk.Property.set=gtk_about_dialog_set_license_type)
474 *
475 * The license of the program.
476 *
477 * The `GtkAboutDialog` will automatically fill out a standard disclaimer
478 * and link the user to the appropriate online resource for the license
479 * text.
480 *
481 * If `GTK_LICENSE_UNKNOWN` is used, the link used will be the same
482 * specified in the [property@Gtk.AboutDialog:website] property.
483 *
484 * If `GTK_LICENSE_CUSTOM` is used, the current contents of the
485 * [property@Gtk.AboutDialog:license] property are used.
486 *
487 * For any other [enum@Gtk.License] value, the contents of the
488 * [property@Gtk.AboutDialog:license] property are also set by this property as
489 * a side effect.
490 */
491 props[PROP_LICENSE_TYPE] =
492 g_param_spec_enum (name: "license-type",
493 P_("License Type"),
494 P_("The license type of the program"),
495 enum_type: GTK_TYPE_LICENSE,
496 default_value: GTK_LICENSE_UNKNOWN,
497 GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
498
499 /**
500 * GtkAboutDialog:website: (attributes org.gtk.Property.get=gtk_about_dialog_get_website org.gtk.Property.set=gtk_about_dialog_set_website)
501 *
502 * The URL for the link to the website of the program.
503 *
504 * This should be a string starting with `http://` or `https://`.
505 */
506 props[PROP_WEBSITE] =
507 g_param_spec_string (name: "website",
508 P_("Website URL"),
509 P_("The URL for the link to the website of the program"),
510 NULL,
511 GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
512
513 /**
514 * GtkAboutDialog:website-label: (attributes org.gtk.Property.get=gtk_about_dialog_get_website_label org.gtk.Property.set=gtk_about_dialog_set_website_label)
515 *
516 * The label for the link to the website of the program.
517 */
518 props[PROP_WEBSITE_LABEL] =
519 g_param_spec_string (name: "website-label",
520 P_("Website label"),
521 P_("The label for the link to the website of the program"),
522 NULL,
523 GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
524
525 /**
526 * GtkAboutDialog:authors: (attributes org.gtk.Property.get=gtk_about_dialog_get_authors org.gtk.Property.set=gtk_about_dialog_set_authors)
527 *
528 * The authors of the program, as a `NULL`-terminated array of strings.
529 *
530 * Each string may contain email addresses and URLs, which will be displayed
531 * as links, see the introduction for more details.
532 */
533 props[PROP_AUTHORS] =
534 g_param_spec_boxed (name: "authors",
535 P_("Authors"),
536 P_("List of authors of the program"),
537 G_TYPE_STRV,
538 GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
539
540 /**
541 * GtkAboutDialog:documenters: (attributes org.gtk.Property.get=gtk_about_dialog_get_documenters org.gtk.Property.set=gtk_about_dialog_set_documenters)
542 *
543 * The people documenting the program, as a `NULL`-terminated array of strings.
544 *
545 * Each string may contain email addresses and URLs, which will be displayed
546 * as links, see the introduction for more details.
547 */
548 props[PROP_DOCUMENTERS] =
549 g_param_spec_boxed (name: "documenters",
550 P_("Documenters"),
551 P_("List of people documenting the program"),
552 G_TYPE_STRV,
553 GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
554
555 /**
556 * GtkAboutDialog:artists: (attributes org.gtk.Property.get=gtk_about_dialog_get_artists org.gtk.Property.set=gtk_about_dialog_set_artists)
557 *
558 * The people who contributed artwork to the program, as a `NULL`-terminated
559 * array of strings.
560 *
561 * Each string may contain email addresses and URLs, which will be displayed
562 * as links.
563 */
564 props[PROP_ARTISTS] =
565 g_param_spec_boxed (name: "artists",
566 P_("Artists"),
567 P_("List of people who have contributed artwork to the program"),
568 G_TYPE_STRV,
569 GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
570
571 /**
572 * GtkAboutDialog:translator-credits: (attributes org.gtk.Property.get=gtk_about_dialog_get_translator_credits org.gtk.Property.set=gtk_about_dialog_set_translator_credits)
573 *
574 * Credits to the translators.
575 *
576 * This string should be marked as translatable.
577 *
578 * The string may contain email addresses and URLs, which will be displayed
579 * as links, see the introduction for more details.
580 */
581 props[PROP_TRANSLATOR_CREDITS] =
582 g_param_spec_string (name: "translator-credits",
583 P_("Translator credits"),
584 P_("Credits to the translators. This string should be marked as translatable"),
585 NULL,
586 GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
587
588 /**
589 * GtkAboutDialog:logo: (attributes org.gtk.Property.get=gtk_about_dialog_get_logo org.gtk.Property.set=gtk_about_dialog_set_logo)
590 *
591 * A logo for the about box.
592 *
593 * If it is `NULL`, the default window icon set with
594 * [id@gtk_window_set_default_icon_name] will be used.
595 */
596 props[PROP_LOGO] =
597 g_param_spec_object (name: "logo",
598 P_("Logo"),
599 P_("A logo for the about box."),
600 GDK_TYPE_PAINTABLE,
601 GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
602
603 /**
604 * 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)
605 *
606 * A named icon to use as the logo for the about box.
607 *
608 * This property overrides the [property@Gtk.AboutDialog:logo] property.
609 */
610 props[PROP_LOGO_ICON_NAME] =
611 g_param_spec_string (name: "logo-icon-name",
612 P_("Logo Icon Name"),
613 P_("A named icon to use as the logo for the about box."),
614 NULL,
615 GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
616
617 /**
618 * GtkAboutDialog:wrap-license: (attributes org.gtk.Property.get=gtk_about_dialog_get_wrap_license org.gtk.Property.set=gtk_about_dialog_set_wrap_license)
619 *
620 * Whether to wrap the text in the license dialog.
621 */
622 props[PROP_WRAP_LICENSE] =
623 g_param_spec_boolean (name: "wrap-license",
624 P_("Wrap license"),
625 P_("Whether to wrap the license text."),
626 FALSE,
627 GTK_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
628
629 g_object_class_install_properties (oclass: object_class, n_pspecs: LAST_PROP, pspecs: props);
630
631 /*
632 * Key bindings
633 */
634
635 gtk_widget_class_add_binding_action (widget_class, GDK_KEY_Escape, mods: 0, action_name: "window.close", NULL);
636
637 /* Bind class to template
638 */
639 gtk_widget_class_set_template_from_resource (widget_class,
640 resource_name: "/org/gtk/libgtk/ui/gtkaboutdialog.ui");
641
642 gtk_widget_class_bind_template_child (widget_class, GtkAboutDialog, stack);
643 gtk_widget_class_bind_template_child (widget_class, GtkAboutDialog, stack_switcher);
644 gtk_widget_class_bind_template_child (widget_class, GtkAboutDialog, logo_image);
645 gtk_widget_class_bind_template_child (widget_class, GtkAboutDialog, name_label);
646 gtk_widget_class_bind_template_child (widget_class, GtkAboutDialog, version_label);
647 gtk_widget_class_bind_template_child (widget_class, GtkAboutDialog, comments_label);
648 gtk_widget_class_bind_template_child (widget_class, GtkAboutDialog, copyright_label);
649 gtk_widget_class_bind_template_child (widget_class, GtkAboutDialog, license_label);
650 gtk_widget_class_bind_template_child (widget_class, GtkAboutDialog, website_label);
651 gtk_widget_class_bind_template_child (widget_class, GtkAboutDialog, credits_page);
652 gtk_widget_class_bind_template_child (widget_class, GtkAboutDialog, license_page);
653 gtk_widget_class_bind_template_child (widget_class, GtkAboutDialog, system_page);
654 gtk_widget_class_bind_template_child (widget_class, GtkAboutDialog, credits_grid);
655 gtk_widget_class_bind_template_child (widget_class, GtkAboutDialog, license_view);
656 gtk_widget_class_bind_template_child (widget_class, GtkAboutDialog, system_view);
657
658 gtk_widget_class_bind_template_callback (widget_class, emit_activate_link);
659 gtk_widget_class_bind_template_callback (widget_class, text_view_released);
660 gtk_widget_class_bind_template_callback (widget_class, text_view_motion);
661 gtk_widget_class_bind_template_callback (widget_class, text_view_key_pressed);
662 gtk_widget_class_bind_template_callback (widget_class, stack_visible_child_notify);
663}
664
665static gboolean
666emit_activate_link (GtkAboutDialog *about,
667 const char *uri)
668{
669 gboolean handled = FALSE;
670
671 g_signal_emit (instance: about, signal_id: signals[ACTIVATE_LINK], detail: 0, uri, &handled);
672
673 return TRUE;
674}
675
676static void
677update_stack_switcher_visibility (GtkAboutDialog *about)
678{
679 GtkStackPage *page;
680 gboolean any_visible = FALSE;
681
682 page = gtk_stack_get_page (GTK_STACK (about->stack), child: about->credits_page);
683 any_visible |= gtk_stack_page_get_visible (self: page);
684 page = gtk_stack_get_page (GTK_STACK (about->stack), child: about->license_page);
685 any_visible |= gtk_stack_page_get_visible (self: page);
686 page = gtk_stack_get_page (GTK_STACK (about->stack), child: about->system_page);
687 any_visible |= gtk_stack_page_get_visible (self: page);
688
689 gtk_widget_set_visible (widget: about->stack_switcher, visible: any_visible);
690}
691
692static void
693update_license_button_visibility (GtkAboutDialog *about)
694{
695 GtkStackPage *page;
696
697 page = gtk_stack_get_page (GTK_STACK (about->stack), child: about->license_page);
698 gtk_stack_page_set_visible (self: page,
699 visible: about->license_type == GTK_LICENSE_CUSTOM &&
700 about->license != NULL &&
701 about->license[0] != '\0');
702
703 update_stack_switcher_visibility (about);
704}
705
706static void
707update_system_button_visibility (GtkAboutDialog *about)
708{
709 GtkStackPage *page;
710
711 page = gtk_stack_get_page (GTK_STACK (about->stack), child: about->system_page);
712 gtk_stack_page_set_visible (self: page,
713 visible: about->system_information != NULL &&
714 about->system_information[0] != '\0');
715
716 update_stack_switcher_visibility (about);
717}
718
719static void
720update_credits_button_visibility (GtkAboutDialog *about)
721{
722 gboolean show;
723 GtkStackPage *page;
724
725 page = gtk_stack_get_page (GTK_STACK (about->stack), child: about->credits_page);
726
727 show = (about->authors != NULL ||
728 about->documenters != NULL ||
729 about->artists != NULL ||
730 about->credit_sections != NULL ||
731 (about->translator_credits != NULL &&
732 strcmp (s1: about->translator_credits, s2: "translator_credits") &&
733 strcmp (s1: about->translator_credits, s2: "translator-credits")));
734 gtk_stack_page_set_visible (self: page, visible: show);
735
736 update_stack_switcher_visibility (about);
737}
738
739static void
740gtk_about_dialog_init (GtkAboutDialog *about)
741{
742 /* Data */
743 about->name = NULL;
744 about->version = NULL;
745 about->copyright = NULL;
746 about->comments = NULL;
747 about->website_url = NULL;
748 about->website_text = NULL;
749 about->translator_credits = NULL;
750 about->license = NULL;
751 about->authors = NULL;
752 about->documenters = NULL;
753 about->artists = NULL;
754
755 about->hovering_over_link = FALSE;
756 about->wrap_license = FALSE;
757
758 about->license_type = GTK_LICENSE_UNKNOWN;
759
760 about->visited_links = g_ptr_array_new_with_free_func (element_free_func: g_free);
761
762 gtk_widget_init_template (GTK_WIDGET (about));
763
764 gtk_stack_set_visible_child_name (GTK_STACK (about->stack), name: "main");
765 update_stack_switcher_visibility (about);
766
767 /* force defaults */
768 gtk_about_dialog_set_program_name (about, NULL);
769 gtk_about_dialog_set_logo (about, NULL);
770}
771
772static void
773destroy_credit_section (gpointer data)
774{
775 CreditSection *cs = data;
776 g_free (mem: cs->heading);
777 g_strfreev (str_array: cs->people);
778 g_slice_free (CreditSection, data);
779}
780
781static void
782gtk_about_dialog_finalize (GObject *object)
783{
784 GtkAboutDialog *about = GTK_ABOUT_DIALOG (object);
785
786 g_free (mem: about->name);
787 g_free (mem: about->version);
788 g_free (mem: about->copyright);
789 g_free (mem: about->comments);
790 g_free (mem: about->license);
791 g_free (mem: about->website_url);
792 g_free (mem: about->website_text);
793 g_free (mem: about->translator_credits);
794 g_free (mem: about->system_information);
795
796 g_strfreev (str_array: about->authors);
797 g_strfreev (str_array: about->documenters);
798 g_strfreev (str_array: about->artists);
799
800 g_slist_free_full (list: about->credit_sections, free_func: destroy_credit_section);
801 g_ptr_array_unref (array: about->visited_links);
802
803 G_OBJECT_CLASS (gtk_about_dialog_parent_class)->finalize (object);
804}
805
806static void
807gtk_about_dialog_set_property (GObject *object,
808 guint prop_id,
809 const GValue *value,
810 GParamSpec *pspec)
811{
812 GtkAboutDialog *about = GTK_ABOUT_DIALOG (object);
813
814 switch (prop_id)
815 {
816 case PROP_NAME:
817 gtk_about_dialog_set_program_name (about, name: g_value_get_string (value));
818 break;
819 case PROP_VERSION:
820 gtk_about_dialog_set_version (about, version: g_value_get_string (value));
821 break;
822 case PROP_COMMENTS:
823 gtk_about_dialog_set_comments (about, comments: g_value_get_string (value));
824 break;
825 case PROP_WEBSITE:
826 gtk_about_dialog_set_website (about, website: g_value_get_string (value));
827 break;
828 case PROP_WEBSITE_LABEL:
829 gtk_about_dialog_set_website_label (about, website_label: g_value_get_string (value));
830 break;
831 case PROP_LICENSE:
832 gtk_about_dialog_set_license (about, license: g_value_get_string (value));
833 break;
834 case PROP_SYSTEM_INFORMATION:
835 gtk_about_dialog_set_system_information (about, system_information: g_value_get_string (value));
836 break;
837 case PROP_LICENSE_TYPE:
838 gtk_about_dialog_set_license_type (about, license_type: g_value_get_enum (value));
839 break;
840 case PROP_COPYRIGHT:
841 gtk_about_dialog_set_copyright (about, copyright: g_value_get_string (value));
842 break;
843 case PROP_LOGO:
844 gtk_about_dialog_set_logo (about, logo: g_value_get_object (value));
845 break;
846 case PROP_AUTHORS:
847 gtk_about_dialog_set_authors (about, authors: (const char **)g_value_get_boxed (value));
848 break;
849 case PROP_DOCUMENTERS:
850 gtk_about_dialog_set_documenters (about, documenters: (const char **)g_value_get_boxed (value));
851 break;
852 case PROP_ARTISTS:
853 gtk_about_dialog_set_artists (about, artists: (const char **)g_value_get_boxed (value));
854 break;
855 case PROP_TRANSLATOR_CREDITS:
856 gtk_about_dialog_set_translator_credits (about, translator_credits: g_value_get_string (value));
857 break;
858 case PROP_LOGO_ICON_NAME:
859 gtk_about_dialog_set_logo_icon_name (about, icon_name: g_value_get_string (value));
860 break;
861 case PROP_WRAP_LICENSE:
862 gtk_about_dialog_set_wrap_license (about, wrap_license: g_value_get_boolean (value));
863 break;
864 default:
865 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
866 break;
867 }
868}
869
870static void
871gtk_about_dialog_get_property (GObject *object,
872 guint prop_id,
873 GValue *value,
874 GParamSpec *pspec)
875{
876 GtkAboutDialog *about = GTK_ABOUT_DIALOG (object);
877
878 switch (prop_id)
879 {
880 case PROP_NAME:
881 g_value_set_string (value, v_string: about->name);
882 break;
883 case PROP_VERSION:
884 g_value_set_string (value, v_string: about->version);
885 break;
886 case PROP_COPYRIGHT:
887 g_value_set_string (value, v_string: about->copyright);
888 break;
889 case PROP_COMMENTS:
890 g_value_set_string (value, v_string: about->comments);
891 break;
892 case PROP_WEBSITE:
893 g_value_set_string (value, v_string: about->website_url);
894 break;
895 case PROP_WEBSITE_LABEL:
896 g_value_set_string (value, v_string: about->website_text);
897 break;
898 case PROP_LICENSE:
899 g_value_set_string (value, v_string: about->license);
900 break;
901 case PROP_SYSTEM_INFORMATION:
902 g_value_set_string (value, v_string: about->system_information);
903 break;
904 case PROP_LICENSE_TYPE:
905 g_value_set_enum (value, v_enum: about->license_type);
906 break;
907 case PROP_TRANSLATOR_CREDITS:
908 g_value_set_string (value, v_string: about->translator_credits);
909 break;
910 case PROP_AUTHORS:
911 g_value_set_boxed (value, v_boxed: about->authors);
912 break;
913 case PROP_DOCUMENTERS:
914 g_value_set_boxed (value, v_boxed: about->documenters);
915 break;
916 case PROP_ARTISTS:
917 g_value_set_boxed (value, v_boxed: about->artists);
918 break;
919 case PROP_LOGO:
920 if (gtk_image_get_storage_type (GTK_IMAGE (about->logo_image)) == GTK_IMAGE_PAINTABLE)
921 g_value_set_object (value, v_object: gtk_image_get_paintable (GTK_IMAGE (about->logo_image)));
922 else
923 g_value_set_object (value, NULL);
924 break;
925 case PROP_LOGO_ICON_NAME:
926 if (gtk_image_get_storage_type (GTK_IMAGE (about->logo_image)) == GTK_IMAGE_ICON_NAME)
927 g_value_set_string (value, v_string: gtk_image_get_icon_name (GTK_IMAGE (about->logo_image)));
928 else
929 g_value_set_string (value, NULL);
930 break;
931 case PROP_WRAP_LICENSE:
932 g_value_set_boolean (value, v_boolean: about->wrap_license);
933 break;
934 default:
935 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
936 break;
937 }
938}
939
940static gboolean
941gtk_about_dialog_activate_link (GtkAboutDialog *about,
942 const char *uri)
943{
944 gtk_show_uri (GTK_WINDOW (about), uri, GDK_CURRENT_TIME);
945 return TRUE;
946}
947
948static void
949update_website (GtkAboutDialog *about)
950{
951 gtk_widget_show (widget: about->website_label);
952
953 if (about->website_url)
954 {
955 char *markup;
956
957 if (about->website_text)
958 {
959 char *escaped;
960
961 escaped = g_markup_escape_text (text: about->website_text, length: -1);
962 markup = g_strdup_printf (format: "<a href=\"%s\">%s</a>",
963 about->website_url, escaped);
964 g_free (mem: escaped);
965 }
966 else
967 {
968 markup = g_strdup_printf (format: "<a href=\"%s\">%s</a>",
969 about->website_url, _("Website"));
970 }
971
972 gtk_label_set_markup (GTK_LABEL (about->website_label), str: markup);
973 g_free (mem: markup);
974 }
975 else
976 {
977 if (about->website_text)
978 gtk_label_set_text (GTK_LABEL (about->website_label), str: about->website_text);
979 else
980 gtk_widget_hide (widget: about->website_label);
981 }
982}
983
984/**
985 * gtk_about_dialog_get_program_name: (attributes org.gtk.Method.get_property=program-name)
986 * @about: a `GtkAboutDialog`
987 *
988 * Returns the program name displayed in the about dialog.
989 *
990 * Returns: (nullable): The program name
991 */
992const char *
993gtk_about_dialog_get_program_name (GtkAboutDialog *about)
994{
995 g_return_val_if_fail (GTK_IS_ABOUT_DIALOG (about), NULL);
996
997 return about->name;
998}
999
1000static void
1001update_name_version (GtkAboutDialog *about)
1002{
1003 char *title_string, *name_string;
1004
1005 title_string = g_strdup_printf (_("About %s"), about->name);
1006 gtk_window_set_title (GTK_WINDOW (about), title: title_string);
1007 g_free (mem: title_string);
1008
1009 if (about->version != NULL)
1010 {
1011 gtk_label_set_markup (GTK_LABEL (about->version_label), str: about->version);
1012 gtk_widget_show (widget: about->version_label);
1013 }
1014 else
1015 gtk_widget_hide (widget: about->version_label);
1016
1017 name_string = g_markup_printf_escaped (format: "<span weight=\"bold\">%s</span>",
1018 about->name);
1019 gtk_label_set_markup (GTK_LABEL (about->name_label), str: name_string);
1020 g_free (mem: name_string);
1021}
1022
1023/**
1024 * gtk_about_dialog_set_program_name: (attributes org.gtk.Method.set_property=program-name)
1025 * @about: a `GtkAboutDialog`
1026 * @name: (nullable): the program name
1027 *
1028 * Sets the name to display in the about dialog.
1029 *
1030 * If `name` is not set, the string returned
1031 * by `g_get_application_name()` is used.
1032 */
1033void
1034gtk_about_dialog_set_program_name (GtkAboutDialog *about,
1035 const char *name)
1036{
1037 char *tmp;
1038
1039 g_return_if_fail (GTK_IS_ABOUT_DIALOG (about));
1040
1041 tmp = about->name;
1042 about->name = g_strdup (str: name ? name : g_get_application_name ());
1043 g_free (mem: tmp);
1044
1045 update_name_version (about);
1046
1047 g_object_notify_by_pspec (G_OBJECT (about), pspec: props[PROP_NAME]);
1048}
1049
1050
1051/**
1052 * gtk_about_dialog_get_version: (attributes org.gtk.Method.get_property=version)
1053 * @about: a `GtkAboutDialog`
1054 *
1055 * Returns the version string.
1056 *
1057 * Returns: (nullable): The version string
1058 */
1059const char *
1060gtk_about_dialog_get_version (GtkAboutDialog *about)
1061{
1062 g_return_val_if_fail (GTK_IS_ABOUT_DIALOG (about), NULL);
1063
1064 return about->version;
1065}
1066
1067/**
1068 * gtk_about_dialog_set_version: (attributes org.gtk.Method.set_property=version)
1069 * @about: a `GtkAboutDialog`
1070 * @version: (nullable): the version string
1071 *
1072 * Sets the version string to display in the about dialog.
1073 */
1074void
1075gtk_about_dialog_set_version (GtkAboutDialog *about,
1076 const char *version)
1077{
1078 char *tmp;
1079
1080 g_return_if_fail (GTK_IS_ABOUT_DIALOG (about));
1081
1082 tmp = about->version;
1083 about->version = g_strdup (str: version);
1084 g_free (mem: tmp);
1085
1086 update_name_version (about);
1087
1088 g_object_notify_by_pspec (G_OBJECT (about), pspec: props[PROP_VERSION]);
1089}
1090
1091/**
1092 * gtk_about_dialog_get_copyright: (attributes org.gtk.Method.get_property=copyright)
1093 * @about: a `GtkAboutDialog`
1094 *
1095 * Returns the copyright string.
1096 *
1097 * Returns: (nullable): The copyright string
1098 */
1099const char *
1100gtk_about_dialog_get_copyright (GtkAboutDialog *about)
1101{
1102 g_return_val_if_fail (GTK_IS_ABOUT_DIALOG (about), NULL);
1103
1104 return about->copyright;
1105}
1106
1107/**
1108 * gtk_about_dialog_set_copyright: (attributes org.gtk.Method.set_property=copyright)
1109 * @about: a `GtkAboutDialog`
1110 * @copyright: (nullable): the copyright string
1111 *
1112 * Sets the copyright string to display in the about dialog.
1113 *
1114 * This should be a short string of one or two lines.
1115 */
1116void
1117gtk_about_dialog_set_copyright (GtkAboutDialog *about,
1118 const char *copyright)
1119{
1120 char *copyright_string, *tmp;
1121
1122 g_return_if_fail (GTK_IS_ABOUT_DIALOG (about));
1123
1124 tmp = about->copyright;
1125 about->copyright = g_strdup (str: copyright);
1126 g_free (mem: tmp);
1127
1128 if (about->copyright != NULL)
1129 {
1130 copyright_string = g_markup_printf_escaped (format: "<span size=\"small\">%s</span>",
1131 about->copyright);
1132 gtk_label_set_markup (GTK_LABEL (about->copyright_label), str: copyright_string);
1133 g_free (mem: copyright_string);
1134
1135 gtk_widget_show (widget: about->copyright_label);
1136 }
1137 else
1138 gtk_widget_hide (widget: about->copyright_label);
1139
1140 g_object_notify_by_pspec (G_OBJECT (about), pspec: props[PROP_COPYRIGHT]);
1141}
1142
1143/**
1144 * gtk_about_dialog_get_comments: (attributes org.gtk.Method.set_property=comments)
1145 * @about: a `GtkAboutDialog`
1146 *
1147 * Returns the comments string.
1148 *
1149 * Returns: (nullable): The comments
1150 */
1151const char *
1152gtk_about_dialog_get_comments (GtkAboutDialog *about)
1153{
1154 g_return_val_if_fail (GTK_IS_ABOUT_DIALOG (about), NULL);
1155
1156 return about->comments;
1157}
1158
1159/**
1160 * gtk_about_dialog_set_comments: (attributes org.gtk.Method.set_property=comments)
1161 * @about: a `GtkAboutDialog`
1162 * @comments: (nullable): a comments string
1163 *
1164 * Sets the comments string to display in the about dialog.
1165 *
1166 * This should be a short string of one or two lines.
1167 */
1168void
1169gtk_about_dialog_set_comments (GtkAboutDialog *about,
1170 const char *comments)
1171{
1172 char *tmp;
1173
1174 g_return_if_fail (GTK_IS_ABOUT_DIALOG (about));
1175
1176 tmp = about->comments;
1177 if (comments)
1178 {
1179 about->comments = g_strdup (str: comments);
1180 gtk_label_set_text (GTK_LABEL (about->comments_label), str: about->comments);
1181 gtk_widget_show (widget: about->comments_label);
1182 }
1183 else
1184 {
1185 about->comments = NULL;
1186 gtk_widget_hide (widget: about->comments_label);
1187 }
1188 g_free (mem: tmp);
1189
1190 g_object_notify_by_pspec (G_OBJECT (about), pspec: props[PROP_COMMENTS]);
1191}
1192
1193/**
1194 * gtk_about_dialog_get_license: (attributes org.gtk.Method.get_property=license)
1195 * @about: a `GtkAboutDialog`
1196 *
1197 * Returns the license information.
1198 *
1199 * Returns: (nullable): The license information
1200 */
1201const char *
1202gtk_about_dialog_get_license (GtkAboutDialog *about)
1203{
1204 g_return_val_if_fail (GTK_IS_ABOUT_DIALOG (about), NULL);
1205
1206 return about->license;
1207}
1208
1209/**
1210 * gtk_about_dialog_set_license: (attributes org.gtk.Method.set_property=license)
1211 * @about: a `GtkAboutDialog`
1212 * @license: (nullable): the license information
1213 *
1214 * Sets the license information to be displayed in the
1215 * about dialog.
1216 *
1217 * If `license` is `NULL`, the license page is hidden.
1218 */
1219void
1220gtk_about_dialog_set_license (GtkAboutDialog *about,
1221 const char *license)
1222{
1223 char *tmp;
1224
1225 g_return_if_fail (GTK_IS_ABOUT_DIALOG (about));
1226
1227 tmp = about->license;
1228 if (license)
1229 {
1230 about->license = g_strdup (str: license);
1231 about->license_type = GTK_LICENSE_CUSTOM;
1232 }
1233 else
1234 {
1235 about->license = NULL;
1236 about->license_type = GTK_LICENSE_UNKNOWN;
1237 }
1238 g_free (mem: tmp);
1239
1240 gtk_widget_hide (widget: about->license_label);
1241
1242 update_license_button_visibility (about);
1243
1244 g_object_notify_by_pspec (G_OBJECT (about), pspec: props[PROP_LICENSE]);
1245 g_object_notify_by_pspec (G_OBJECT (about), pspec: props[PROP_LICENSE_TYPE]);
1246}
1247
1248/**
1249 * gtk_about_dialog_get_system_information: (attributes org.gtk.Method.get_property=system-information)
1250 * @about: a `GtkAboutDialog`
1251 *
1252 * Returns the system information that is shown in the about dialog.
1253 *
1254 * Returns: (nullable): the system information
1255 */
1256const char *
1257gtk_about_dialog_get_system_information (GtkAboutDialog *about)
1258{
1259 g_return_val_if_fail (GTK_IS_ABOUT_DIALOG (about), NULL);
1260
1261 return about->system_information;
1262}
1263
1264/**
1265 * gtk_about_dialog_set_system_information: (attributes org.gtk.Method.set_property=system-information)
1266 * @about: a `GtkAboutDialog`
1267 * @system_information: (nullable): system information
1268 *
1269 * Sets the system information to be displayed in the about
1270 * dialog.
1271 *
1272 * If `system_information` is `NULL`, the system information
1273 * page is hidden.
1274 *
1275 * See [property@Gtk.AboutDialog:system-information].
1276 */
1277void
1278gtk_about_dialog_set_system_information (GtkAboutDialog *about,
1279 const char *system_information)
1280{
1281 g_return_if_fail (GTK_IS_ABOUT_DIALOG (about));
1282
1283 g_free (mem: about->system_information);
1284 about->system_information = g_strdup (str: system_information);
1285 update_system_button_visibility (about);
1286
1287 g_object_notify_by_pspec (G_OBJECT (about), pspec: props[PROP_SYSTEM_INFORMATION]);
1288}
1289
1290/**
1291 * gtk_about_dialog_get_wrap_license: (attributes org.gtk.Method.get_property=wrap-license)
1292 * @about: a `GtkAboutDialog`
1293 *
1294 * Returns whether the license text in the about dialog is
1295 * automatically wrapped.
1296 *
1297 * Returns: `TRUE` if the license text is wrapped
1298 */
1299gboolean
1300gtk_about_dialog_get_wrap_license (GtkAboutDialog *about)
1301{
1302 g_return_val_if_fail (GTK_IS_ABOUT_DIALOG (about), FALSE);
1303
1304 return about->wrap_license;
1305}
1306
1307/**
1308 * gtk_about_dialog_set_wrap_license: (attributes org.gtk.Method.set_property=wrap-license)
1309 * @about: a `GtkAboutDialog`
1310 * @wrap_license: whether to wrap the license
1311 *
1312 * Sets whether the license text in the about dialog should be
1313 * automatically wrapped.
1314 */
1315void
1316gtk_about_dialog_set_wrap_license (GtkAboutDialog *about,
1317 gboolean wrap_license)
1318{
1319 g_return_if_fail (GTK_IS_ABOUT_DIALOG (about));
1320
1321 wrap_license = wrap_license != FALSE;
1322
1323 if (about->wrap_license != wrap_license)
1324 {
1325 about->wrap_license = wrap_license;
1326
1327 g_object_notify_by_pspec (G_OBJECT (about), pspec: props[PROP_WRAP_LICENSE]);
1328 }
1329}
1330
1331/**
1332 * gtk_about_dialog_get_website: (attributes org.gtk.Method.get_property=website)
1333 * @about: a `GtkAboutDialog`
1334 *
1335 * Returns the website URL.
1336 *
1337 * Returns: (nullable) (transfer none): The website URL
1338 */
1339const char *
1340gtk_about_dialog_get_website (GtkAboutDialog *about)
1341{
1342 g_return_val_if_fail (GTK_IS_ABOUT_DIALOG (about), NULL);
1343
1344 return about->website_url;
1345}
1346
1347/**
1348 * gtk_about_dialog_set_website: (attributes org.gtk.Method.set_property=website)
1349 * @about: a `GtkAboutDialog`
1350 * @website: (nullable): a URL string starting with `http://`
1351 *
1352 * Sets the URL to use for the website link.
1353 */
1354void
1355gtk_about_dialog_set_website (GtkAboutDialog *about,
1356 const char *website)
1357{
1358 char *tmp;
1359
1360 g_return_if_fail (GTK_IS_ABOUT_DIALOG (about));
1361
1362 tmp = about->website_url;
1363 about->website_url = g_strdup (str: website);
1364 g_free (mem: tmp);
1365
1366 update_website (about);
1367
1368 g_object_notify_by_pspec (G_OBJECT (about), pspec: props[PROP_WEBSITE]);
1369}
1370
1371/**
1372 * gtk_about_dialog_get_website_label: (attributes org.gtk.Method.get_property=website-label)
1373 * @about: a `GtkAboutDialog`
1374 *
1375 * Returns the label used for the website link.
1376 *
1377 * Returns: (nullable) (transfer none): The label used for the website link
1378 */
1379const char *
1380gtk_about_dialog_get_website_label (GtkAboutDialog *about)
1381{
1382 g_return_val_if_fail (GTK_IS_ABOUT_DIALOG (about), NULL);
1383
1384 return about->website_text;
1385}
1386
1387/**
1388 * gtk_about_dialog_set_website_label: (attributes org.gtk.Method.set_property=website-label)
1389 * @about: a `GtkAboutDialog`
1390 * @website_label: the label used for the website link
1391 *
1392 * Sets the label to be used for the website link.
1393 */
1394void
1395gtk_about_dialog_set_website_label (GtkAboutDialog *about,
1396 const char *website_label)
1397{
1398 char *tmp;
1399
1400 g_return_if_fail (GTK_IS_ABOUT_DIALOG (about));
1401
1402 tmp = about->website_text;
1403 about->website_text = g_strdup (str: website_label);
1404 g_free (mem: tmp);
1405
1406 update_website (about);
1407
1408 g_object_notify_by_pspec (G_OBJECT (about), pspec: props[PROP_WEBSITE_LABEL]);
1409}
1410
1411/**
1412 * gtk_about_dialog_get_authors: (attributes org.gtk.Method.get_property=authors)
1413 * @about: a `GtkAboutDialog`
1414 *
1415 * Returns the names of the authors which are displayed
1416 * in the credits page.
1417 *
1418 * Returns: (array zero-terminated=1) (transfer none): A
1419 * `NULL`-terminated string array containing the authors
1420 */
1421const char * const *
1422gtk_about_dialog_get_authors (GtkAboutDialog *about)
1423{
1424 g_return_val_if_fail (GTK_IS_ABOUT_DIALOG (about), NULL);
1425
1426 return (const char * const *) about->authors;
1427}
1428
1429/**
1430 * gtk_about_dialog_set_authors: (attributes org.gtk.Method.set_property=authors)
1431 * @about: a `GtkAboutDialog`
1432 * @authors: (array zero-terminated=1): the authors of the application
1433 *
1434 * Sets the names of the authors which are displayed
1435 * in the "Credits" page of the about dialog.
1436 */
1437void
1438gtk_about_dialog_set_authors (GtkAboutDialog *about,
1439 const char **authors)
1440{
1441 char **tmp;
1442
1443 g_return_if_fail (GTK_IS_ABOUT_DIALOG (about));
1444
1445 tmp = about->authors;
1446 about->authors = g_strdupv (str_array: (char **)authors);
1447 g_strfreev (str_array: tmp);
1448
1449 update_credits_button_visibility (about);
1450
1451 g_object_notify_by_pspec (G_OBJECT (about), pspec: props[PROP_AUTHORS]);
1452}
1453
1454/**
1455 * gtk_about_dialog_get_documenters: (attributes org.gtk.Method.get_property=documenters)
1456 * @about: a `GtkAboutDialog`
1457 *
1458 * Returns the name of the documenters which are displayed
1459 * in the credits page.
1460 *
1461 * Returns: (array zero-terminated=1) (transfer none): A
1462 * `NULL`-terminated string array containing the documenters
1463 */
1464const char * const *
1465gtk_about_dialog_get_documenters (GtkAboutDialog *about)
1466{
1467 g_return_val_if_fail (GTK_IS_ABOUT_DIALOG (about), NULL);
1468
1469 return (const char * const *)about->documenters;
1470}
1471
1472/**
1473 * gtk_about_dialog_set_documenters: (attributes org.gtk.Method.set_property=documenters)
1474 * @about: a `GtkAboutDialog`
1475 * @documenters: (array zero-terminated=1): the authors of the documentation
1476 * of the application
1477 *
1478 * Sets the names of the documenters which are displayed
1479 * in the "Credits" page.
1480 */
1481void
1482gtk_about_dialog_set_documenters (GtkAboutDialog *about,
1483 const char **documenters)
1484{
1485 char **tmp;
1486
1487 g_return_if_fail (GTK_IS_ABOUT_DIALOG (about));
1488
1489 tmp = about->documenters;
1490 about->documenters = g_strdupv (str_array: (char **)documenters);
1491 g_strfreev (str_array: tmp);
1492
1493 update_credits_button_visibility (about);
1494
1495 g_object_notify_by_pspec (G_OBJECT (about), pspec: props[PROP_DOCUMENTERS]);
1496}
1497
1498/**
1499 * gtk_about_dialog_get_artists: (attributes org.gtk.Method.get_property=artists)
1500 * @about: a `GtkAboutDialog`
1501 *
1502 * Returns the names of the artists which are displayed
1503 * in the credits page.
1504 *
1505 * Returns: (array zero-terminated=1) (transfer none): A
1506 * `NULL`-terminated string array containing the artists
1507 */
1508const char * const *
1509gtk_about_dialog_get_artists (GtkAboutDialog *about)
1510{
1511 g_return_val_if_fail (GTK_IS_ABOUT_DIALOG (about), NULL);
1512
1513 return (const char * const *)about->artists;
1514}
1515
1516/**
1517 * gtk_about_dialog_set_artists: (attributes org.gtk.Method.set_property=artists)
1518 * @about: a `GtkAboutDialog`
1519 * @artists: (array zero-terminated=1): the authors of the artwork
1520 * of the application
1521 *
1522 * Sets the names of the artists to be displayed
1523 * in the "Credits" page.
1524 */
1525void
1526gtk_about_dialog_set_artists (GtkAboutDialog *about,
1527 const char **artists)
1528{
1529 char **tmp;
1530
1531 g_return_if_fail (GTK_IS_ABOUT_DIALOG (about));
1532
1533 tmp = about->artists;
1534 about->artists = g_strdupv (str_array: (char **)artists);
1535 g_strfreev (str_array: tmp);
1536
1537 update_credits_button_visibility (about);
1538
1539 g_object_notify_by_pspec (G_OBJECT (about), pspec: props[PROP_ARTISTS]);
1540}
1541
1542/**
1543 * gtk_about_dialog_get_translator_credits: (attributes org.gtk.Method.get_property=translator-credits)
1544 * @about: a `GtkAboutDialog`
1545 *
1546 * Returns the translator credits string which is displayed
1547 * in the credits page.
1548 *
1549 * Returns: (nullable): The translator credits string
1550 */
1551const char *
1552gtk_about_dialog_get_translator_credits (GtkAboutDialog *about)
1553{
1554 g_return_val_if_fail (GTK_IS_ABOUT_DIALOG (about), NULL);
1555
1556 return about->translator_credits;
1557}
1558
1559/**
1560 * gtk_about_dialog_set_translator_credits: (attributes org.gtk.Method.set_property=translator-credits)
1561 * @about: a `GtkAboutDialog`
1562 * @translator_credits: (nullable): the translator credits
1563 *
1564 * Sets the translator credits string which is displayed in
1565 * the credits page.
1566 *
1567 * The intended use for this string is to display the translator
1568 * of the language which is currently used in the user interface.
1569 * Using `gettext()`, a simple way to achieve that is to mark the
1570 * string for translation:
1571 *
1572 * ```c
1573 * GtkWidget *about = gtk_about_dialog_new ();
1574 * gtk_about_dialog_set_translator_credits (GTK_ABOUT_DIALOG (about),
1575 * _("translator-credits"));
1576 * ```
1577 *
1578 * It is a good idea to use the customary `msgid` “translator-credits”
1579 * for this purpose, since translators will already know the purpose of
1580 * that `msgid`, and since `GtkAboutDialog` will detect if “translator-credits”
1581 * is untranslated and omit translator credits.
1582 */
1583void
1584gtk_about_dialog_set_translator_credits (GtkAboutDialog *about,
1585 const char *translator_credits)
1586{
1587 char *tmp;
1588
1589 g_return_if_fail (GTK_IS_ABOUT_DIALOG (about));
1590
1591 tmp = about->translator_credits;
1592 about->translator_credits = g_strdup (str: translator_credits);
1593 g_free (mem: tmp);
1594
1595 update_credits_button_visibility (about);
1596
1597 g_object_notify_by_pspec (G_OBJECT (about), pspec: props[PROP_TRANSLATOR_CREDITS]);
1598}
1599
1600/**
1601 * gtk_about_dialog_get_logo: (attributes org.gtk.Method.get_property=logo)
1602 * @about: a `GtkAboutDialog`
1603 *
1604 * Returns the paintable displayed as logo in the about dialog.
1605 *
1606 * Returns: (transfer none) (nullable): the paintable displayed as
1607 * logo or `NULL` if the logo is unset or has been set via
1608 * [method@Gtk.AboutDialog.set_logo_icon_name]
1609 */
1610GdkPaintable *
1611 (GtkAboutDialog *about)
1612{
1613 g_return_val_if_fail (GTK_IS_ABOUT_DIALOG (about), NULL);
1614
1615 if (gtk_image_get_storage_type (GTK_IMAGE (about->logo_image)) == GTK_IMAGE_PAINTABLE)
1616 return gtk_image_get_paintable (GTK_IMAGE (about->logo_image));
1617 else
1618 return NULL;
1619}
1620
1621/**
1622 * gtk_about_dialog_set_logo: (attributes org.gtk.Method.set_property=logo)
1623 * @about: a `GtkAboutDialog`
1624 * @logo: (nullable): a `GdkPaintable`
1625 *
1626 * Sets the logo in the about dialog.
1627 */
1628void
1629 (GtkAboutDialog *about,
1630 GdkPaintable *)
1631{
1632 g_return_if_fail (GTK_IS_ABOUT_DIALOG (about));
1633 g_return_if_fail (logo == NULL || GDK_IS_PAINTABLE (logo));
1634
1635 g_object_freeze_notify (G_OBJECT (about));
1636
1637 if (gtk_image_get_storage_type (GTK_IMAGE (about->logo_image)) == GTK_IMAGE_ICON_NAME)
1638 g_object_notify_by_pspec (G_OBJECT (about), pspec: props[PROP_LOGO_ICON_NAME]);
1639
1640 gtk_image_set_from_paintable (GTK_IMAGE (about->logo_image), paintable: logo);
1641
1642 g_object_notify_by_pspec (G_OBJECT (about), pspec: props[PROP_LOGO]);
1643
1644 g_object_thaw_notify (G_OBJECT (about));
1645}
1646
1647/**
1648 * gtk_about_dialog_get_logo_icon_name: (attributes org.gtk.Method.get_property=logo-icon-name)
1649 * @about: a `GtkAboutDialog`
1650 *
1651 * Returns the icon name displayed as logo in the about dialog.
1652 *
1653 * Returns: (transfer none) (nullable): the icon name displayed as logo,
1654 * or `NULL` if the logo has been set via [method@Gtk.AboutDialog.set_logo]
1655 */
1656const char *
1657gtk_about_dialog_get_logo_icon_name (GtkAboutDialog *about)
1658{
1659 g_return_val_if_fail (GTK_IS_ABOUT_DIALOG (about), NULL);
1660
1661 if (gtk_image_get_storage_type (GTK_IMAGE (about->logo_image)) != GTK_IMAGE_ICON_NAME)
1662 return NULL;
1663
1664 return gtk_image_get_icon_name (GTK_IMAGE (about->logo_image));
1665}
1666
1667/**
1668 * gtk_about_dialog_set_logo_icon_name: (attributes org.gtk.Method.set_property=logo-icon-name)
1669 * @about: a `GtkAboutDialog`
1670 * @icon_name: (nullable): an icon name
1671 *
1672 * Sets the icon name to be displayed as logo in the about dialog.
1673 */
1674void
1675gtk_about_dialog_set_logo_icon_name (GtkAboutDialog *about,
1676 const char *icon_name)
1677{
1678 g_return_if_fail (GTK_IS_ABOUT_DIALOG (about));
1679
1680 g_object_freeze_notify (G_OBJECT (about));
1681
1682 if (gtk_image_get_storage_type (GTK_IMAGE (about->logo_image)) == GTK_IMAGE_PAINTABLE)
1683 g_object_notify_by_pspec (G_OBJECT (about), pspec: props[PROP_LOGO]);
1684
1685 gtk_image_set_from_icon_name (GTK_IMAGE (about->logo_image), icon_name);
1686
1687 g_object_notify_by_pspec (G_OBJECT (about), pspec: props[PROP_LOGO_ICON_NAME]);
1688
1689 g_object_thaw_notify (G_OBJECT (about));
1690}
1691
1692static void
1693follow_if_link (GtkAboutDialog *about,
1694 GtkTextView *text_view,
1695 GtkTextIter *iter)
1696{
1697 GSList *tags = NULL, *tagp = NULL;
1698 char *uri = NULL;
1699
1700 tags = gtk_text_iter_get_tags (iter);
1701 for (tagp = tags; tagp != NULL && !uri; tagp = tagp->next)
1702 {
1703 GtkTextTag *tag = tagp->data;
1704
1705 uri = g_object_get_data (G_OBJECT (tag), key: "uri");
1706 if (uri)
1707 emit_activate_link (about, uri);
1708
1709 if (uri && !g_ptr_array_find_with_equal_func (haystack: about->visited_links, needle: uri, equal_func: (GCompareFunc)strcmp, NULL))
1710 {
1711 GdkRGBA visited_link_color;
1712 GtkStyleContext *context = gtk_widget_get_style_context (GTK_WIDGET (about));
1713 gtk_style_context_save (context);
1714 gtk_style_context_set_state (context, flags: gtk_style_context_get_state (context) | GTK_STATE_FLAG_VISITED);
1715 gtk_style_context_get_color (context, color: &visited_link_color);
1716 gtk_style_context_restore (context);
1717
1718 g_object_set (G_OBJECT (tag), first_property_name: "foreground-rgba", &visited_link_color, NULL);
1719
1720 g_ptr_array_add (array: about->visited_links, data: g_strdup (str: uri));
1721 }
1722 }
1723
1724 g_slist_free (list: tags);
1725}
1726
1727static gboolean
1728text_view_key_pressed (GtkEventController *controller,
1729 guint keyval,
1730 guint keycode,
1731 GdkModifierType state,
1732 GtkAboutDialog *about)
1733{
1734 GtkWidget *text_view;
1735 GtkTextIter iter;
1736 GtkTextBuffer *buffer;
1737
1738 text_view = gtk_event_controller_get_widget (controller);
1739
1740 switch (keyval)
1741 {
1742 case GDK_KEY_Return:
1743 case GDK_KEY_ISO_Enter:
1744 case GDK_KEY_KP_Enter:
1745 buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (text_view));
1746 gtk_text_buffer_get_iter_at_mark (buffer, iter: &iter,
1747 mark: gtk_text_buffer_get_insert (buffer));
1748 follow_if_link (about, GTK_TEXT_VIEW (text_view), iter: &iter);
1749 break;
1750
1751 default:
1752 break;
1753 }
1754
1755 return FALSE;
1756}
1757
1758static void
1759text_view_released (GtkGestureClick *gesture,
1760 int n_press,
1761 double x,
1762 double y,
1763 GtkAboutDialog *about)
1764{
1765 GtkWidget *text_view;
1766 GtkTextIter start, end, iter;
1767 GtkTextBuffer *buffer;
1768 int tx, ty;
1769
1770 if (gtk_gesture_single_get_current_button (GTK_GESTURE_SINGLE (gesture)) != GDK_BUTTON_PRIMARY)
1771 return;
1772
1773 text_view = gtk_event_controller_get_widget (GTK_EVENT_CONTROLLER (gesture));
1774 buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (text_view));
1775
1776 /* we shouldn't follow a link if the user has selected something */
1777 gtk_text_buffer_get_selection_bounds (buffer, start: &start, end: &end);
1778 if (gtk_text_iter_get_offset (iter: &start) != gtk_text_iter_get_offset (iter: &end))
1779 return;
1780
1781 gtk_text_view_window_to_buffer_coords (GTK_TEXT_VIEW (text_view),
1782 win: GTK_TEXT_WINDOW_WIDGET,
1783 window_x: x, window_y: y, buffer_x: &tx, buffer_y: &ty);
1784
1785 gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW (text_view), iter: &iter, x: tx, y: ty);
1786
1787 follow_if_link (about, GTK_TEXT_VIEW (text_view), iter: &iter);
1788}
1789
1790static void
1791set_cursor_if_appropriate (GtkAboutDialog *about,
1792 GtkTextView *text_view,
1793 int x,
1794 int y)
1795{
1796 GSList *tags = NULL, *tagp = NULL;
1797 GtkTextIter iter;
1798 gboolean hovering_over_link = FALSE;
1799
1800 gtk_text_view_get_iter_at_location (text_view, iter: &iter, x, y);
1801
1802 tags = gtk_text_iter_get_tags (iter: &iter);
1803 for (tagp = tags; tagp != NULL; tagp = tagp->next)
1804 {
1805 GtkTextTag *tag = tagp->data;
1806 char *uri = g_object_get_data (G_OBJECT (tag), key: "uri");
1807
1808 if (uri != NULL)
1809 {
1810 hovering_over_link = TRUE;
1811 break;
1812 }
1813 }
1814
1815 if (hovering_over_link != about->hovering_over_link)
1816 {
1817 about->hovering_over_link = hovering_over_link;
1818
1819 if (hovering_over_link)
1820 gtk_widget_set_cursor_from_name (GTK_WIDGET (text_view), name: "pointer");
1821 else
1822 gtk_widget_set_cursor_from_name (GTK_WIDGET (text_view), name: "text");
1823 }
1824
1825 g_slist_free (list: tags);
1826}
1827
1828static void
1829text_view_motion (GtkEventControllerMotion *motion,
1830 double x,
1831 double y,
1832 GtkAboutDialog *about)
1833{
1834 int tx, ty;
1835 GtkWidget *widget;
1836
1837 widget = gtk_event_controller_get_widget (GTK_EVENT_CONTROLLER (motion));
1838
1839 gtk_text_view_window_to_buffer_coords (GTK_TEXT_VIEW (widget),
1840 win: GTK_TEXT_WINDOW_WIDGET,
1841 window_x: x, window_y: y, buffer_x: &tx, buffer_y: &ty);
1842
1843 set_cursor_if_appropriate (about, GTK_TEXT_VIEW (widget), x: tx, y: ty);
1844}
1845
1846static GtkTextBuffer *
1847text_buffer_new (GtkAboutDialog *about,
1848 char **strings)
1849{
1850 char **p;
1851 char *q0, *q1, *q2, *r1, *r2;
1852 GtkTextBuffer *buffer;
1853 GdkRGBA color;
1854 GdkRGBA link_color;
1855 GdkRGBA visited_link_color;
1856 GtkTextIter start_iter, end_iter;
1857 GtkTextTag *tag;
1858 GtkStateFlags state = gtk_widget_get_state_flags (GTK_WIDGET (about));
1859 GtkStyleContext *context = gtk_widget_get_style_context (GTK_WIDGET (about));
1860
1861 gtk_style_context_save (context);
1862 gtk_style_context_set_state (context, flags: state | GTK_STATE_FLAG_LINK);
1863 gtk_style_context_get_color (context, color: &link_color);
1864 gtk_style_context_set_state (context, flags: state | GTK_STATE_FLAG_VISITED);
1865 gtk_style_context_get_color (context, color: &visited_link_color);
1866 gtk_style_context_restore (context);
1867 buffer = gtk_text_buffer_new (NULL);
1868
1869 for (p = strings; *p; p++)
1870 {
1871 q0 = *p;
1872 while (*q0)
1873 {
1874 q1 = strchr (s: q0, c: '<');
1875 q2 = q1 ? strchr (s: q1, c: '>') : NULL;
1876 r1 = strstr (haystack: q0, needle: "http://");
1877 r2 = strstr (haystack: q0, needle: "https://");
1878 if (!r1 || (r1 && r2 && r2 < r1))
1879 r1 = r2;
1880 if (r1)
1881 {
1882 r2 = strpbrk (s: r1, accept: " \n\t>");
1883 if (!r2)
1884 r2 = strchr (s: r1, c: '\0');
1885 }
1886 else
1887 r2 = NULL;
1888
1889 if (r1 && r2 && (!q1 || !q2 || (r1 <= q1 + 1)))
1890 {
1891 q1 = r1;
1892 q2 = r2;
1893 }
1894
1895 if (q1 && q2)
1896 {
1897 GtkTextIter end;
1898 char *link;
1899 char *uri;
1900 const char *link_type;
1901
1902 if (*q1 == '<')
1903 {
1904 gtk_text_buffer_insert_at_cursor (buffer, text: q0, len: q1 - q0 + 1);
1905 gtk_text_buffer_get_end_iter (buffer, iter: &end);
1906 q1++;
1907 link_type = "email";
1908 }
1909 else
1910 {
1911 gtk_text_buffer_insert_at_cursor (buffer, text: q0, len: q1 - q0);
1912 gtk_text_buffer_get_end_iter (buffer, iter: &end);
1913 link_type = "uri";
1914 }
1915
1916 q0 = q2;
1917
1918 link = g_strndup (str: q1, n: q2 - q1);
1919
1920 if (g_ptr_array_find_with_equal_func (haystack: about->visited_links, needle: link, equal_func: (GCompareFunc)strcmp, NULL))
1921 color = visited_link_color;
1922 else
1923 color = link_color;
1924
1925 tag = gtk_text_buffer_create_tag (buffer, NULL,
1926 first_property_name: "foreground-rgba", &color,
1927 "underline", PANGO_UNDERLINE_SINGLE,
1928 NULL);
1929 if (strcmp (s1: link_type, s2: "email") == 0)
1930 {
1931 char *escaped;
1932
1933 escaped = g_uri_escape_string (unescaped: link, NULL, FALSE);
1934 uri = g_strconcat (string1: "mailto:", escaped, NULL);
1935 g_free (mem: escaped);
1936 }
1937 else
1938 {
1939 uri = g_strdup (str: link);
1940 }
1941 g_object_set_data_full (G_OBJECT (tag), I_("uri"), data: uri, destroy: g_free);
1942 gtk_text_buffer_insert_with_tags (buffer, iter: &end, text: link, len: -1, first_tag: tag, NULL);
1943
1944 g_free (mem: link);
1945 }
1946 else
1947 {
1948 gtk_text_buffer_insert_at_cursor (buffer, text: q0, len: -1);
1949 break;
1950 }
1951 }
1952
1953 if (p[1])
1954 gtk_text_buffer_insert_at_cursor (buffer, text: "\n", len: 1);
1955 }
1956
1957 tag = gtk_text_buffer_create_tag (buffer, NULL,
1958 first_property_name: "scale", PANGO_SCALE_SMALL,
1959 NULL);
1960
1961 gtk_text_buffer_get_start_iter (buffer, iter: &start_iter);
1962 gtk_text_buffer_get_end_iter (buffer, iter: &end_iter);
1963 gtk_text_buffer_apply_tag (buffer, tag, start: &start_iter, end: &end_iter);
1964
1965 gtk_text_buffer_set_enable_undo (buffer, FALSE);
1966
1967 return buffer;
1968}
1969
1970static void
1971add_credits_section (GtkAboutDialog *about,
1972 GtkGrid *grid,
1973 int *row,
1974 char *title,
1975 char **people)
1976{
1977 GtkWidget *label;
1978 char *markup;
1979 char **p;
1980 char *q0, *q1, *q2, *r1, *r2;
1981
1982 if (people == NULL)
1983 return;
1984
1985 markup = g_strdup_printf (format: "<span size=\"small\">%s</span>", title);
1986 label = gtk_label_new (str: markup);
1987 gtk_label_set_use_markup (GTK_LABEL (label), TRUE);
1988 g_free (mem: markup);
1989 gtk_widget_set_halign (widget: label, align: GTK_ALIGN_END);
1990 gtk_widget_set_valign (widget: label, align: GTK_ALIGN_CENTER);
1991 gtk_grid_attach (grid, child: label, column: 0, row: *row, width: 1, height: 1);
1992
1993 for (p = people; *p; p++)
1994 {
1995 GString *str;
1996
1997 str = g_string_new (init: "<span size=\"small\">");
1998
1999 q0 = *p;
2000 while (*q0)
2001 {
2002 q1 = strchr (s: q0, c: '<');
2003 q2 = q1 ? strchr (s: q1, c: '>') : NULL;
2004 r1 = strstr (haystack: q0, needle: "http://");
2005 r2 = strstr (haystack: q0, needle: "https://");
2006 if (!r1 || (r1 && r2 && r2 < r1))
2007 r1 = r2;
2008 if (r1)
2009 {
2010 r2 = strpbrk (s: r1, accept: " \n\t");
2011 if (!r2)
2012 r2 = strchr (s: r1, c: '\0');
2013 }
2014 else
2015 r2 = NULL;
2016
2017 if (r1 && r2 && (!q1 || !q2 || (r1 < q1)))
2018 {
2019 q1 = r1;
2020 q2 = r2;
2021 }
2022 else if (q1 && (q1[1] == 'a' || q1[1] == 'A') && q1[2] == ' ')
2023 {
2024 /* if it is a <a> link leave it for the label to parse */
2025 q1 = NULL;
2026 }
2027
2028 if (q1 && q2)
2029 {
2030 char *link;
2031 char *text;
2032 char *name;
2033
2034 if (*q1 == '<')
2035 {
2036 /* email */
2037 char *escaped;
2038
2039 text = g_strstrip (g_strndup (q0, q1 - q0));
2040 name = g_markup_escape_text (text, length: -1);
2041 q1++;
2042 link = g_strndup (str: q1, n: q2 - q1);
2043 q2++;
2044 escaped = g_uri_escape_string (unescaped: link, NULL, FALSE);
2045 g_string_append_printf (string: str,
2046 format: "<a href=\"mailto:%s\">%s</a>",
2047 escaped,
2048 name[0] ? name : link);
2049 g_free (mem: escaped);
2050 g_free (mem: link);
2051 g_free (mem: text);
2052 g_free (mem: name);
2053 }
2054 else
2055 {
2056 /* uri */
2057 text = g_strstrip (g_strndup (q0, q1 - q0));
2058 name = g_markup_escape_text (text, length: -1);
2059 link = g_strndup (str: q1, n: q2 - q1);
2060 g_string_append_printf (string: str,
2061 format: "<a href=\"%s\">%s</a>",
2062 link,
2063 name[0] ? name : link);
2064 g_free (mem: link);
2065 g_free (mem: text);
2066 g_free (mem: name);
2067 }
2068
2069 q0 = q2;
2070 }
2071 else
2072 {
2073 g_string_append (string: str, val: q0);
2074 break;
2075 }
2076 }
2077 g_string_append (string: str, val: "</span>");
2078
2079 label = gtk_label_new (str: str->str);
2080 gtk_label_set_use_markup (GTK_LABEL (label), TRUE);
2081 gtk_label_set_selectable (GTK_LABEL (label), TRUE);
2082 g_signal_connect_swapped (label, "activate-link",
2083 G_CALLBACK (emit_activate_link), about);
2084 g_string_free (string: str, TRUE);
2085 gtk_widget_set_halign (widget: label, align: GTK_ALIGN_START);
2086 gtk_widget_set_valign (widget: label, align: GTK_ALIGN_CENTER);
2087 gtk_grid_attach (grid, child: label, column: 1, row: *row, width: 1, height: 1);
2088 gtk_widget_show (widget: label);
2089 (*row)++;
2090 }
2091
2092 /* skip one at the end */
2093 label = gtk_label_new (str: "");
2094 gtk_grid_attach (grid, child: label, column: 1, row: *row, width: 1, height: 1);
2095 (*row)++;
2096}
2097
2098static void
2099populate_credits_page (GtkAboutDialog *about)
2100{
2101 int row;
2102
2103 row = 0;
2104
2105 if (about->authors != NULL)
2106 add_credits_section (about, GTK_GRID (about->credits_grid), row: &row, _("Created by"), people: about->authors);
2107
2108 if (about->documenters != NULL)
2109 add_credits_section (about, GTK_GRID (about->credits_grid), row: &row, _("Documented by"), people: about->documenters);
2110
2111 /* Don't show an untranslated gettext msgid */
2112 if (about->translator_credits != NULL &&
2113 strcmp (s1: about->translator_credits, s2: "translator_credits") != 0 &&
2114 strcmp (s1: about->translator_credits, s2: "translator-credits") != 0)
2115 {
2116 char **translators;
2117
2118 translators = g_strsplit (string: about->translator_credits, delimiter: "\n", max_tokens: 0);
2119 add_credits_section (about, GTK_GRID (about->credits_grid), row: &row, _("Translated by"), people: translators);
2120 g_strfreev (str_array: translators);
2121 }
2122
2123 if (about->artists != NULL)
2124 add_credits_section (about, GTK_GRID (about->credits_grid), row: &row, _("Design by"), people: about->artists);
2125
2126 if (about->credit_sections != NULL)
2127 {
2128 GSList *cs;
2129 for (cs = about->credit_sections; cs != NULL; cs = cs->next)
2130 {
2131 CreditSection *section = cs->data;
2132 add_credits_section (about, GTK_GRID (about->credits_grid), row: &row, title: section->heading, people: section->people);
2133 }
2134 }
2135}
2136
2137static void
2138populate_license_page (GtkAboutDialog *about)
2139{
2140 GtkTextBuffer *buffer;
2141 char *strings[2];
2142
2143 gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (about->license_view), wrap_mode: about->wrap_license ? GTK_WRAP_WORD : GTK_WRAP_NONE);
2144
2145 strings[0] = about->license;
2146 strings[1] = NULL;
2147 buffer = text_buffer_new (about, strings);
2148 gtk_text_view_set_buffer (GTK_TEXT_VIEW (about->license_view), buffer);
2149 g_object_unref (object: buffer);
2150}
2151
2152static void
2153populate_system_page (GtkAboutDialog *about)
2154{
2155 GtkTextBuffer *buffer;
2156 char *strings[2];
2157
2158 strings[0] = about->system_information;
2159 strings[1] = NULL;
2160 buffer = text_buffer_new (about, strings);
2161 gtk_text_view_set_buffer (GTK_TEXT_VIEW (about->system_view), buffer);
2162 g_object_unref (object: buffer);
2163}
2164
2165/**
2166 * gtk_about_dialog_new:
2167 *
2168 * Creates a new `GtkAboutDialog`.
2169 *
2170 * Returns: a newly created `GtkAboutDialog`
2171 */
2172GtkWidget *
2173gtk_about_dialog_new (void)
2174{
2175 return g_object_new (GTK_TYPE_ABOUT_DIALOG, NULL);
2176}
2177
2178static gboolean
2179close_cb (GtkAboutDialog *about,
2180 gpointer user_data)
2181{
2182 gtk_stack_set_visible_child_name (GTK_STACK (about->stack), name: "main");
2183
2184 gtk_widget_hide (GTK_WIDGET (about));
2185
2186 return TRUE;
2187}
2188
2189/**
2190 * gtk_show_about_dialog:
2191 * @parent: (nullable): the parent top-level window
2192 * @first_property_name: the name of the first property
2193 * @...: value of first property, followed by more pairs of property
2194 * name and value, `NULL`-terminated
2195 *
2196 * A convenience function for showing an application’s about dialog.
2197 *
2198 * The constructed dialog is associated with the parent window and
2199 * reused for future invocations of this function.
2200 */
2201void
2202gtk_show_about_dialog (GtkWindow *parent,
2203 const char *first_property_name,
2204 ...)
2205{
2206 static GtkWidget *global_about_dialog = NULL;
2207 GtkWidget *dialog = NULL;
2208 va_list var_args;
2209
2210 if (parent)
2211 dialog = g_object_get_data (G_OBJECT (parent), key: "gtk-about-dialog");
2212 else
2213 dialog = global_about_dialog;
2214
2215 if (!dialog)
2216 {
2217 dialog = gtk_about_dialog_new ();
2218 gtk_window_set_hide_on_close (GTK_WINDOW (dialog), TRUE);
2219
2220 g_object_ref_sink (dialog);
2221
2222 /* Hide the dialog on close request */
2223 g_signal_connect (dialog, "close-request",
2224 G_CALLBACK (close_cb), NULL);
2225
2226 va_start (var_args, first_property_name);
2227 g_object_set_valist (G_OBJECT (dialog), first_property_name, var_args);
2228 va_end (var_args);
2229
2230 if (parent)
2231 {
2232 gtk_window_set_modal (GTK_WINDOW (dialog), TRUE);
2233 gtk_window_set_transient_for (GTK_WINDOW (dialog), parent);
2234 gtk_window_set_destroy_with_parent (GTK_WINDOW (dialog), TRUE);
2235 g_object_set_data_full (G_OBJECT (parent),
2236 I_("gtk-about-dialog"),
2237 data: dialog, destroy: g_object_unref);
2238 }
2239 else
2240 global_about_dialog = dialog;
2241
2242 }
2243
2244 gtk_window_present (GTK_WINDOW (dialog));
2245}
2246
2247/**
2248 * gtk_about_dialog_set_license_type: (attributes org.gtk.Method.set_property=license-type)
2249 * @about: a `GtkAboutDialog`
2250 * @license_type: the type of license
2251 *
2252 * Sets the license of the application showing the about dialog from a
2253 * list of known licenses.
2254 *
2255 * This function overrides the license set using
2256 * [method@Gtk.AboutDialog.set_license].
2257 */
2258void
2259gtk_about_dialog_set_license_type (GtkAboutDialog *about,
2260 GtkLicense license_type)
2261{
2262 g_return_if_fail (GTK_IS_ABOUT_DIALOG (about));
2263 g_return_if_fail (license_type >= GTK_LICENSE_UNKNOWN &&
2264 license_type < G_N_ELEMENTS (gtk_license_info));
2265
2266 if (about->license_type != license_type)
2267 {
2268 g_object_freeze_notify (G_OBJECT (about));
2269
2270 about->license_type = license_type;
2271
2272 /* custom licenses use the contents of the :license property */
2273 if (about->license_type != GTK_LICENSE_CUSTOM)
2274 {
2275 const char *name;
2276 const char *url;
2277 char *license_string;
2278 GString *str;
2279
2280 name = _(gtk_license_info[about->license_type].name);
2281 url = gtk_license_info[about->license_type].url;
2282 if (url == NULL)
2283 url = about->website_url;
2284
2285 str = g_string_sized_new (dfl_size: 256);
2286 /* Translators: this is the license preamble; the string at the end
2287 * contains the name of the license as link text.
2288 */
2289 g_string_append_printf (string: str, _("This program comes with absolutely no warranty.\nSee the <a href=\"%s\">%s</a> for details."), url, name);
2290
2291 g_free (mem: about->license);
2292 about->license = g_string_free (string: str, FALSE);
2293 about->wrap_license = TRUE;
2294
2295 license_string = g_strdup_printf (format: "<span size=\"small\">%s</span>",
2296 about->license);
2297 gtk_label_set_markup (GTK_LABEL (about->license_label), str: license_string);
2298 g_free (mem: license_string);
2299 gtk_widget_show (widget: about->license_label);
2300
2301 g_object_notify_by_pspec (G_OBJECT (about), pspec: props[PROP_WRAP_LICENSE]);
2302 g_object_notify_by_pspec (G_OBJECT (about), pspec: props[PROP_LICENSE]);
2303 }
2304 else
2305 {
2306 gtk_widget_show (widget: about->license_label);
2307 }
2308
2309 update_license_button_visibility (about);
2310
2311 g_object_notify_by_pspec (G_OBJECT (about), pspec: props[PROP_LICENSE_TYPE]);
2312
2313 g_object_thaw_notify (G_OBJECT (about));
2314 }
2315}
2316
2317/**
2318 * gtk_about_dialog_get_license_type: (attributes org.gtk.Method.get_property=license-type)
2319 * @about: a `GtkAboutDialog`
2320 *
2321 * Retrieves the license type.
2322 *
2323 * Returns: a [enum@Gtk.License] value
2324 */
2325GtkLicense
2326gtk_about_dialog_get_license_type (GtkAboutDialog *about)
2327{
2328 g_return_val_if_fail (GTK_IS_ABOUT_DIALOG (about), GTK_LICENSE_UNKNOWN);
2329
2330 return about->license_type;
2331}
2332
2333/**
2334 * gtk_about_dialog_add_credit_section:
2335 * @about: A `GtkAboutDialog`
2336 * @section_name: The name of the section
2337 * @people: (array zero-terminated=1): The people who belong to that section
2338 *
2339 * Creates a new section in the "Credits" page.
2340 */
2341void
2342gtk_about_dialog_add_credit_section (GtkAboutDialog *about,
2343 const char *section_name,
2344 const char **people)
2345{
2346 CreditSection *new_entry;
2347
2348 g_return_if_fail (GTK_IS_ABOUT_DIALOG (about));
2349 g_return_if_fail (section_name != NULL);
2350 g_return_if_fail (people != NULL);
2351
2352 new_entry = g_slice_new (CreditSection);
2353 new_entry->heading = g_strdup (str: (char *)section_name);
2354 new_entry->people = g_strdupv (str_array: (char **)people);
2355
2356 about->credit_sections = g_slist_append (list: about->credit_sections, data: new_entry);
2357 update_credits_button_visibility (about);
2358}
2359

source code of gtk/gtk/gtkaboutdialog.c