]> Pileus Git - ~andy/gtk/blob - gtk/gtkaboutdialog.c
Update about dialog design to not use a swarm of dialogs
[~andy/gtk] / gtk / gtkaboutdialog.c
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, write to the
18  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
19  * Boston, MA 02111-1307, USA.
20  */
21
22 /*
23  * Author: Anders Carlsson <andersca@gnome.org>
24  *
25  * Modified by the GTK+ Team and others 1997-2004.  See the AUTHORS
26  * file for a list of people on the GTK+ Team.  See the ChangeLog
27  * files for a list of changes.  These files are distributed with
28  * GTK+ at ftp://ftp.gtk.org/pub/gtk/.
29  */
30
31 #include "config.h"
32
33 #include <string.h>
34
35 #include <gdk/gdkkeysyms.h>
36
37 #include "gtkaboutdialog.h"
38 #include "gtkalignment.h"
39 #include "gtkbutton.h"
40 #include "gtkbbox.h"
41 #include "gtkdialog.h"
42 #include "gtkgrid.h"
43 #include "gtkhbox.h"
44 #include "gtkimage.h"
45 #include "gtklabel.h"
46 #include "gtklinkbutton.h"
47 #include "gtkmarshalers.h"
48 #include "gtknotebook.h"
49 #include "gtkorientable.h"
50 #include "gtkscrolledwindow.h"
51 #include "gtkstock.h"
52 #include "gtktextview.h"
53 #include "gtkvbox.h"
54 #include "gtkiconfactory.h"
55 #include "gtkshow.h"
56 #include "gtkmain.h"
57 #include "gtkmessagedialog.h"
58 #include "gtktogglebutton.h"
59 #include "gtkprivate.h"
60 #include "gtkintl.h"
61
62
63 /**
64  * SECTION:gtkaboutdialog
65  * @Short_description: Display information about an application
66  * @Title: GtkAboutDialog
67  * @See_also: #GTK_STOCK_ABOUT
68  *
69  * The GtkAboutDialog offers a simple way to display information about
70  * a program like its logo, name, copyright, website and license. It is
71  * also possible to give credits to the authors, documenters, translators
72  * and artists who have worked on the program. An about dialog is typically
73  * opened when the user selects the <literal>About</literal> option from
74  * the <literal>Help</literal> menu. All parts of the dialog are optional.
75  *
76  * About dialog often contain links and email addresses. GtkAboutDialog
77  * displays these as clickable links. By default, it calls gtk_show_uri()
78  * when a user clicks one. The behaviour can be overridden with the
79  * #GtkAboutDialog::activate-link signal.
80  *
81  * To make constructing a GtkAboutDialog as convenient as possible, you can
82  * use the function gtk_show_about_dialog() which constructs and shows a dialog
83  * and keeps it around so that it can be shown again.
84  *
85  * Note that GTK+ sets a default title of <literal>_("About &percnt;s")</literal>
86  * on the dialog window (where &percnt;s is replaced by the name of the
87  * application, but in order to ensure proper translation of the title,
88  * applications should set the title property explicitly when constructing
89  * a GtkAboutDialog, as shown in the following example:
90  * <informalexample><programlisting>
91  * gtk_show_about_dialog (NULL,
92  *                        "program-name", "ExampleCode",
93  *                        "logo", example_logo,
94  *                        "title" _("About ExampleCode"),
95  *                        NULL);
96  * </programlisting></informalexample>
97  */
98
99 static GdkColor default_link_color = { 0, 0, 0, 0xeeee };
100 static GdkColor default_visited_link_color = { 0, 0x5555, 0x1a1a, 0x8b8b };
101
102 /* Translators: this is the license preamble; the string at the end
103  * contains the URL of the license.
104  */
105 static const gchar *gtk_license_preamble = N_("This program comes with ABSOLUTELY NO WARRANTY; for details, visit <a href=\"%s\">%s</a>");
106
107 /* URLs for each GtkLicense type; keep in the same order as the enumeration */
108 static const gchar *gtk_license_urls[] = {
109   NULL,
110   NULL,
111
112   "http://www.gnu.org/licenses/old-licenses/gpl-2.0.html",
113   "http://www.gnu.org/licenses/gpl.html",
114
115   "http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html",
116   "http://www.gnu.org/licenses/lgpl.html",
117
118   "http://opensource.org/licenses/bsd-license.php",
119   "http://opensource.org/licenses/mit-license.php",
120
121   "http://opensource.org/licenses/artistic-license-2.0.php"
122 };
123
124 struct _GtkAboutDialogPrivate 
125 {
126   gchar *name;
127   gchar *version;
128   gchar *copyright;
129   gchar *comments;
130   gchar *website_url;
131   gchar *website_text;
132   gchar *translator_credits;
133   gchar *license;
134
135   gchar **authors;
136   gchar **documenters;
137   gchar **artists;
138
139   gint credits_page;
140   gint license_page;
141
142   GtkWidget *notebook;
143   GtkWidget *logo_image;
144   GtkWidget *name_label;
145   GtkWidget *version_label;
146   GtkWidget *comments_label;
147   GtkWidget *copyright_label;
148   GtkWidget *license_label;
149   GtkWidget *website_label;
150
151   GtkWidget *credits_button;
152   GtkWidget *license_button;
153
154   GdkCursor *hand_cursor;
155   GdkCursor *regular_cursor;
156
157   GSList *visited_links;
158
159   GtkLicense license_type;
160
161   guint hovering_over_link : 1;
162   guint wrap_license : 1;
163 };
164
165 #define GTK_ABOUT_DIALOG_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GTK_TYPE_ABOUT_DIALOG, GtkAboutDialogPrivate))
166
167
168 enum
169 {
170   PROP_0,
171   PROP_NAME,
172   PROP_VERSION,
173   PROP_COPYRIGHT,
174   PROP_COMMENTS,
175   PROP_WEBSITE,
176   PROP_WEBSITE_LABEL,
177   PROP_LICENSE,
178   PROP_AUTHORS,
179   PROP_DOCUMENTERS,
180   PROP_TRANSLATOR_CREDITS,
181   PROP_ARTISTS,
182   PROP_LOGO,
183   PROP_LOGO_ICON_NAME,
184   PROP_WRAP_LICENSE,
185   PROP_LICENSE_TYPE
186 };
187
188 static void                 gtk_about_dialog_finalize       (GObject            *object);
189 static void                 gtk_about_dialog_get_property   (GObject            *object,
190                                                              guint               prop_id,
191                                                              GValue             *value,
192                                                              GParamSpec         *pspec);
193 static void                 gtk_about_dialog_set_property   (GObject            *object,
194                                                              guint               prop_id,
195                                                              const GValue       *value,
196                                                              GParamSpec         *pspec);
197 static void                 gtk_about_dialog_show           (GtkWidget          *widge);
198 static void                 update_name_version             (GtkAboutDialog     *about);
199 static GtkIconSet *         icon_set_new_from_pixbufs       (GList              *pixbufs);
200 static void                 follow_if_link                  (GtkAboutDialog     *about,
201                                                              GtkTextView        *text_view,
202                                                              GtkTextIter        *iter);
203 static void                 set_cursor_if_appropriate       (GtkAboutDialog     *about,
204                                                              GtkTextView        *text_view,
205                                                              GdkDevice          *device,
206                                                              gint                x,
207                                                              gint                y);
208 static void                 display_credits_page            (GtkWidget          *button,
209                                                              gpointer            data);
210 static void                 display_license_page            (GtkWidget          *button,
211                                                              gpointer            data);
212 static void                 close_cb                        (GtkAboutDialog     *about);
213 static gboolean             gtk_about_dialog_activate_link  (GtkAboutDialog     *about,
214                                                              const gchar        *uri);
215
216 enum {
217   ACTIVATE_LINK,
218   LAST_SIGNAL
219 };
220
221 static guint signals[LAST_SIGNAL] = { 0 };
222
223 G_DEFINE_TYPE (GtkAboutDialog, gtk_about_dialog, GTK_TYPE_DIALOG)
224
225
226 static void
227 gtk_about_dialog_class_init (GtkAboutDialogClass *klass)
228 {
229   GObjectClass *object_class;
230   GtkWidgetClass *widget_class;
231
232   object_class = (GObjectClass *)klass;
233   widget_class = (GtkWidgetClass *)klass;
234
235   object_class->set_property = gtk_about_dialog_set_property;
236   object_class->get_property = gtk_about_dialog_get_property;
237
238   object_class->finalize = gtk_about_dialog_finalize;
239
240   widget_class->show = gtk_about_dialog_show;
241
242   klass->activate_link = gtk_about_dialog_activate_link;
243
244   /**
245    * GtkAboutDialog::activate-link:
246    * @label: The object on which the signal was emitted
247    * @uri: the URI that is activated
248    *
249    * The signal which gets emitted to activate a URI.
250    * Applications may connect to it to override the default behaviour,
251    * which is to call gtk_show_uri().
252    *
253    * Returns: %TRUE if the link has been activated
254    *
255    * Since: 2.24
256    */
257   signals[ACTIVATE_LINK] =
258     g_signal_new ("activate-link",
259                   G_TYPE_FROM_CLASS (object_class),
260                   G_SIGNAL_RUN_LAST,
261                   G_STRUCT_OFFSET (GtkAboutDialogClass, activate_link),
262                   _gtk_boolean_handled_accumulator, NULL,
263                   _gtk_marshal_BOOLEAN__STRING,
264                   G_TYPE_BOOLEAN, 1, G_TYPE_STRING);
265
266   /**
267    * GtkAboutDialog:program-name:
268    *
269    * The name of the program.
270    * If this is not set, it defaults to g_get_application_name().
271    *
272    * Since: 2.12
273    */
274   g_object_class_install_property (object_class,
275                                    PROP_NAME,
276                                    g_param_spec_string ("program-name",
277                                                         P_("Program name"),
278                                                         P_("The name of the program. If this is not set, it defaults to g_get_application_name()"),
279                                                         NULL,
280                                                         GTK_PARAM_READWRITE));
281
282   /**
283    * GtkAboutDialog:version:
284    *
285    * The version of the program.
286    *
287    * Since: 2.6
288    */
289   g_object_class_install_property (object_class,
290                                    PROP_VERSION,
291                                    g_param_spec_string ("version",
292                                                         P_("Program version"),
293                                                         P_("The version of the program"),
294                                                         NULL,
295                                                         GTK_PARAM_READWRITE));
296
297   /**
298    * GtkAboutDialog:copyright:
299    *
300    * Copyright information for the program.
301    *
302    * Since: 2.6
303    */
304   g_object_class_install_property (object_class,
305                                    PROP_COPYRIGHT,
306                                    g_param_spec_string ("copyright",
307                                                         P_("Copyright string"),
308                                                         P_("Copyright information for the program"),
309                                                         NULL,
310                                                         GTK_PARAM_READWRITE));
311         
312
313   /**
314    * GtkAboutDialog:comments:
315    *
316    * Comments about the program. This string is displayed in a label
317    * in the main dialog, thus it should be a short explanation of
318    * the main purpose of the program, not a detailed list of features.
319    *
320    * Since: 2.6
321    */
322   g_object_class_install_property (object_class,
323                                    PROP_COMMENTS,
324                                    g_param_spec_string ("comments",
325                                                         P_("Comments string"),
326                                                         P_("Comments about the program"),
327                                                         NULL,
328                                                         GTK_PARAM_READWRITE));
329
330   /**
331    * GtkAboutDialog:license:
332    *
333    * The license of the program. This string is displayed in a
334    * text view in a secondary dialog, therefore it is fine to use
335    * a long multi-paragraph text. Note that the text is only wrapped
336    * in the text view if the "wrap-license" property is set to %TRUE;
337    * otherwise the text itself must contain the intended linebreaks.
338    * When setting this property to a non-%NULL value, the
339    * #GtkAboutDialog:license-type property is set to %GTK_LICENSE_CUSTOM
340    * as a side effect.
341    *
342    * Since: 2.6
343    */
344   g_object_class_install_property (object_class,
345                                    PROP_LICENSE,
346                                    g_param_spec_string ("license",
347                                                         _("License"),
348                                                         _("The license of the program"),
349                                                         NULL,
350                                                         GTK_PARAM_READWRITE));
351
352   /**
353    * GtkAboutDialog:license-type:
354    *
355    * The license of the program, as a value of the %GtkLicense enumeration.
356    *
357    * The #GtkAboutDialog will automatically fill out a standard disclaimer
358    * and link the user to the appropriate online resource for the license
359    * text.
360    *
361    * If %GTK_LICENSE_UNKNOWN is used, the link used will be the same
362    * specified in the #GtkAboutDialog:website property.
363    *
364    * If %GTK_LICENSE_CUSTOM is used, the current contents of the
365    * #GtkAboutDialog:license property are used.
366    *
367    * For any other #GtkLicense value, the contents of the
368    * #GtkAboutDialog:license property are also set by this property as
369    * a side effect.
370    *
371    * Since: 3.0
372    */
373   g_object_class_install_property (object_class,
374                                    PROP_LICENSE_TYPE,
375                                    g_param_spec_enum ("license-type",
376                                                       P_("License Type"),
377                                                       P_("The license type of the program"),
378                                                       GTK_TYPE_LICENSE,
379                                                       GTK_LICENSE_UNKNOWN,
380                                                       GTK_PARAM_READWRITE));
381
382   /**
383    * GtkAboutDialog:website:
384    *
385    * The URL for the link to the website of the program.
386    * This should be a string starting with "http://.
387    *
388    * Since: 2.6
389    */
390   g_object_class_install_property (object_class,
391                                    PROP_WEBSITE,
392                                    g_param_spec_string ("website",
393                                                         P_("Website URL"),
394                                                         P_("The URL for the link to the website of the program"),
395                                                         NULL,
396                                                         GTK_PARAM_READWRITE));
397
398   /**
399    * GtkAboutDialog:website-label:
400    *
401    * The label for the link to the website of the program. If this is not set,
402    * it defaults to the URL specified in the #GtkAboutDialog:website property.
403    *
404    * Since: 2.6
405    */
406   g_object_class_install_property (object_class,
407                                    PROP_WEBSITE_LABEL,
408                                    g_param_spec_string ("website-label",
409                                                         P_("Website label"),
410                                                         P_("The label for the link to the website of the program. If this is not set, it defaults to the URL"),
411                                                         NULL,
412                                                         GTK_PARAM_READWRITE));
413
414   /**
415    * GtkAboutDialog:authors:
416    *
417    * The authors of the program, as a %NULL-terminated array of strings.
418    * Each string may contain email addresses and URLs, which will be displayed
419    * as links, see the introduction for more details.
420    *
421    * Since: 2.6
422    */
423   g_object_class_install_property (object_class,
424                                    PROP_AUTHORS,
425                                    g_param_spec_boxed ("authors",
426                                                        P_("Authors"),
427                                                        P_("List of authors of the program"),
428                                                        G_TYPE_STRV,
429                                                        GTK_PARAM_READWRITE));
430
431   /**
432    * GtkAboutDialog:documenters:
433    *
434    * The people documenting the program, as a %NULL-terminated array of strings.
435    * Each string may contain email addresses and URLs, which will be displayed
436    * as links, see the introduction for more details.
437    *
438    * Since: 2.6
439    */
440   g_object_class_install_property (object_class,
441                                    PROP_DOCUMENTERS,
442                                    g_param_spec_boxed ("documenters",
443                                                        P_("Documenters"),
444                                                        P_("List of people documenting the program"),
445                                                        G_TYPE_STRV,
446                                                        GTK_PARAM_READWRITE));
447
448   /**
449    * GtkAboutDialog:artists:
450    *
451    * The people who contributed artwork to the program, as a %NULL-terminated
452    * array of strings. Each string may contain email addresses and URLs, which
453    * will be displayed as links, see the introduction for more details.
454    *
455    * Since: 2.6
456    */
457   g_object_class_install_property (object_class,
458                                    PROP_ARTISTS,
459                                    g_param_spec_boxed ("artists",
460                                                        P_("Artists"),
461                                                        P_("List of people who have contributed artwork to the program"),
462                                                        G_TYPE_STRV,
463                                                        GTK_PARAM_READWRITE));
464
465
466   /**
467    * GtkAboutDialog:translator-credits:
468    *
469    * Credits to the translators. This string should be marked as translatable.
470    * The string may contain email addresses and URLs, which will be displayed
471    * as links, see the introduction for more details.
472    *
473    * Since: 2.6
474    */
475   g_object_class_install_property (object_class,
476                                    PROP_TRANSLATOR_CREDITS,
477                                    g_param_spec_string ("translator-credits",
478                                                         P_("Translator credits"),
479                                                         P_("Credits to the translators. This string should be marked as translatable"),
480                                                         NULL,
481                                                         GTK_PARAM_READWRITE));
482         
483   /**
484    * GtkAboutDialog:logo:
485    *
486    * A logo for the about box. If this is not set, it defaults to
487    * gtk_window_get_default_icon_list().
488    *
489    * Since: 2.6
490    */
491   g_object_class_install_property (object_class,
492                                    PROP_LOGO,
493                                    g_param_spec_object ("logo",
494                                                         P_("Logo"),
495                                                         P_("A logo for the about box. If this is not set, it defaults to gtk_window_get_default_icon_list()"),
496                                                         GDK_TYPE_PIXBUF,
497                                                         GTK_PARAM_READWRITE));
498
499   /**
500    * GtkAboutDialog:logo-icon-name:
501    *
502    * A named icon to use as the logo for the about box. This property
503    * overrides the #GtkAboutDialog:logo property.
504    *
505    * Since: 2.6
506    */
507   g_object_class_install_property (object_class,
508                                    PROP_LOGO_ICON_NAME,
509                                    g_param_spec_string ("logo-icon-name",
510                                                         P_("Logo Icon Name"),
511                                                         P_("A named icon to use as the logo for the about box."),
512                                                         NULL,
513                                                         GTK_PARAM_READWRITE));
514   /**
515    * GtkAboutDialog:wrap-license:
516    *
517    * Whether to wrap the text in the license dialog.
518    *
519    * Since: 2.8
520    */
521   g_object_class_install_property (object_class,
522                                    PROP_WRAP_LICENSE,
523                                    g_param_spec_boolean ("wrap-license",
524                                                          P_("Wrap license"),
525                                                          P_("Whether to wrap the license text."),
526                                                          FALSE,
527                                                          GTK_PARAM_READWRITE));
528
529
530   g_type_class_add_private (object_class, sizeof (GtkAboutDialogPrivate));
531 }
532
533 static gboolean
534 emit_activate_link (GtkAboutDialog *about,
535                     const gchar    *uri)
536 {
537   gboolean handled = FALSE;
538
539   g_signal_emit (about, signals[ACTIVATE_LINK], 0, uri, &handled);
540
541   return TRUE;
542 }
543
544 static void
545 update_license_button_visibility (GtkAboutDialog *about)
546 {
547   GtkAboutDialogPrivate *priv = about->priv;
548
549   if (priv->license_type == GTK_LICENSE_CUSTOM && priv->license != NULL)
550     gtk_widget_show (priv->license_button);
551   else
552     gtk_widget_hide (priv->license_button);
553 }
554
555 static void
556 update_credits_button_visibility (GtkAboutDialog *about)
557 {
558   GtkAboutDialogPrivate *priv = about->priv;
559   gboolean show;
560
561   show = (priv->authors != NULL ||
562           priv->documenters != NULL ||
563           priv->artists != NULL ||
564          (priv->translator_credits != NULL &&
565           strcmp (priv->translator_credits, "translator_credits") &&
566           strcmp (priv->translator_credits, "translator-credits")));
567   if (show)
568     gtk_widget_show (priv->credits_button);
569   else
570     gtk_widget_hide (priv->credits_button);
571 }
572
573 static void
574 switch_page (GtkAboutDialog *about,
575              gint            page)
576 {
577   GtkAboutDialogPrivate *priv = about->priv;
578
579   gtk_notebook_set_current_page (GTK_NOTEBOOK (priv->notebook), page);
580 }
581
582 static void
583 display_main_page (GtkButton *button,
584                    gpointer   data)
585 {
586   GtkAboutDialog *about = (GtkAboutDialog *)data;
587
588   switch_page (about, 0);
589 }
590
591 static void
592 credits_button_clicked (GtkButton *button,
593                         gpointer   data)
594 {
595   GtkAboutDialog *about = (GtkAboutDialog *)data;
596   GtkAboutDialogPrivate *priv = about->priv;
597   gboolean active;
598
599   active = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button));
600
601   if (active)
602     {
603       gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (priv->license_button), FALSE);
604       display_credits_page (NULL, data);
605     }
606   else
607     {
608       display_main_page (NULL, data);
609     }
610 }
611
612 static void
613 license_button_clicked (GtkButton *button,
614                         gpointer   data)
615 {
616   GtkAboutDialog *about = (GtkAboutDialog *)data;
617   GtkAboutDialogPrivate *priv = about->priv;
618   gboolean active;
619
620   active = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button));
621
622   if (active)
623     {
624       gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (priv->credits_button), FALSE);
625       display_license_page (NULL, data);
626     }
627   else
628     {
629       display_main_page (NULL, data);
630     }
631 }
632 static void
633 gtk_about_dialog_init (GtkAboutDialog *about)
634 {
635   GtkDialog *dialog = GTK_DIALOG (about);
636   GtkAboutDialogPrivate *priv;
637   GtkWidget *vbox, *page_vbox, *hbox, *button, *close_button, *image;
638   GtkWidget *content_area, *action_area;
639
640   /* Data */
641   priv = GTK_ABOUT_DIALOG_GET_PRIVATE (about);
642   about->priv = priv;
643
644   priv->name = NULL;
645   priv->version = NULL;
646   priv->copyright = NULL;
647   priv->comments = NULL;
648   priv->website_url = NULL;
649   priv->website_text = NULL;
650   priv->translator_credits = NULL;
651   priv->license = NULL;
652   priv->authors = NULL;
653   priv->documenters = NULL;
654   priv->artists = NULL;
655
656   priv->hand_cursor = gdk_cursor_new (GDK_HAND2);
657   priv->regular_cursor = gdk_cursor_new (GDK_XTERM);
658   priv->hovering_over_link = FALSE;
659   priv->wrap_license = FALSE;
660
661   priv->license_type = GTK_LICENSE_UNKNOWN;
662
663   content_area = gtk_dialog_get_content_area (dialog);
664   action_area = gtk_dialog_get_action_area (dialog);
665
666   gtk_container_set_border_width (GTK_CONTAINER (dialog), 5);
667   gtk_box_set_spacing (GTK_BOX (content_area), 2); /* 2 * 5 + 2 = 12 */
668   gtk_container_set_border_width (GTK_CONTAINER (action_area), 5);
669
670   /* Widgets */
671   gtk_widget_push_composite_child ();
672
673   vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12);
674   gtk_container_set_border_width (GTK_CONTAINER (vbox), 5);
675   gtk_box_pack_start (GTK_BOX (content_area), vbox, TRUE, TRUE, 0);
676
677   priv->logo_image = gtk_image_new ();
678   gtk_box_pack_start (GTK_BOX (vbox), priv->logo_image, FALSE, FALSE, 0);
679
680   priv->name_label = gtk_label_new (NULL);
681   gtk_label_set_selectable (GTK_LABEL (priv->name_label), TRUE);
682   gtk_label_set_justify (GTK_LABEL (priv->name_label), GTK_JUSTIFY_CENTER);
683   gtk_box_pack_start (GTK_BOX (vbox), priv->name_label, FALSE, FALSE, 0);
684
685   priv->notebook = gtk_notebook_new ();
686   gtk_box_pack_start (GTK_BOX (vbox), priv->notebook, TRUE, TRUE, 0);
687   gtk_widget_set_size_request (priv->notebook, 400, 100);
688
689   page_vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 8);
690   gtk_widget_show (page_vbox);
691   gtk_notebook_set_show_tabs (GTK_NOTEBOOK (priv->notebook), FALSE);
692   gtk_notebook_set_show_border (GTK_NOTEBOOK (priv->notebook), FALSE);
693   gtk_notebook_append_page (GTK_NOTEBOOK (priv->notebook), page_vbox, NULL);
694
695   priv->version_label = gtk_label_new (NULL);
696   gtk_label_set_selectable (GTK_LABEL (priv->version_label), TRUE);
697   gtk_label_set_justify (GTK_LABEL (priv->version_label), GTK_JUSTIFY_CENTER);
698   gtk_box_pack_start (GTK_BOX (page_vbox), priv->version_label, FALSE, FALSE, 0);
699
700   priv->comments_label = gtk_label_new (NULL);
701   gtk_label_set_selectable (GTK_LABEL (priv->comments_label), TRUE);
702   gtk_label_set_justify (GTK_LABEL (priv->comments_label), GTK_JUSTIFY_CENTER);
703   gtk_label_set_line_wrap (GTK_LABEL (priv->comments_label), TRUE);
704   gtk_box_pack_start (GTK_BOX (page_vbox), priv->comments_label, FALSE, FALSE, 0);
705
706   hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
707   gtk_box_set_homogeneous (GTK_BOX (hbox), TRUE);
708   gtk_box_pack_start (GTK_BOX (page_vbox), hbox, FALSE, FALSE, 0);
709
710   priv->website_label = button = gtk_label_new ("");
711   gtk_widget_set_no_show_all (button, TRUE);
712   gtk_label_set_selectable (GTK_LABEL (button), TRUE);
713   gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, FALSE, 0);
714   g_signal_connect_swapped (button, "activate-link",
715                             G_CALLBACK (emit_activate_link), about);
716
717   priv->license_label = gtk_label_new (NULL);
718   gtk_label_set_use_markup (GTK_LABEL (priv->license_label), TRUE);
719   gtk_label_set_selectable (GTK_LABEL (priv->license_label), TRUE);
720   gtk_label_set_justify (GTK_LABEL (priv->license_label), GTK_JUSTIFY_CENTER);
721   gtk_box_pack_end (GTK_BOX (page_vbox), priv->license_label, FALSE, FALSE, 0);
722   gtk_label_set_line_wrap (GTK_LABEL (priv->license_label), TRUE);
723
724   priv->copyright_label = gtk_label_new (NULL);
725   gtk_label_set_selectable (GTK_LABEL (priv->copyright_label), TRUE);
726   gtk_label_set_justify (GTK_LABEL (priv->copyright_label), GTK_JUSTIFY_CENTER);
727   gtk_box_pack_end (GTK_BOX (page_vbox), priv->copyright_label, FALSE, FALSE, 0);
728
729   gtk_widget_show (vbox);
730   gtk_widget_show (priv->notebook);
731   gtk_widget_show (priv->logo_image);
732   gtk_widget_show (priv->name_label);
733   gtk_widget_show (hbox);
734
735   /* Add the close button */
736   close_button = gtk_dialog_add_button (GTK_DIALOG (about), GTK_STOCK_CLOSE,
737                                         GTK_RESPONSE_CANCEL);
738   gtk_dialog_set_default_response (GTK_DIALOG (about), GTK_RESPONSE_CANCEL);
739
740   /* Add the credits button */
741   button = gtk_toggle_button_new_with_mnemonic (_("C_redits"));
742   gtk_widget_set_can_default (button, TRUE);
743   image = gtk_image_new_from_stock (GTK_STOCK_ABOUT, GTK_ICON_SIZE_BUTTON);
744   gtk_button_set_image (GTK_BUTTON (button), image);
745   gtk_widget_set_no_show_all (button, TRUE);
746   gtk_box_pack_end (GTK_BOX (action_area), button, FALSE, TRUE, 0);
747   gtk_button_box_set_child_secondary (GTK_BUTTON_BOX (action_area), button, TRUE);
748   g_signal_connect (button, "clicked",
749                     G_CALLBACK (credits_button_clicked), about);
750   priv->credits_button = button;
751   priv->credits_page = 0;
752
753   /* Add the license button */
754   button = gtk_toggle_button_new_with_mnemonic (_("_License"));
755   gtk_widget_set_can_default (button, TRUE);
756   gtk_widget_set_no_show_all (button, TRUE);
757   gtk_box_pack_end (GTK_BOX (action_area), button, FALSE, TRUE, 0);
758   gtk_button_box_set_child_secondary (GTK_BUTTON_BOX (action_area), button, TRUE);
759   g_signal_connect (button, "clicked",
760                     G_CALLBACK (license_button_clicked), about);
761   priv->license_button = button;
762   priv->license_page = 0;
763
764   switch_page (about, 0);
765
766   gtk_window_set_resizable (GTK_WINDOW (about), FALSE);
767
768   gtk_widget_pop_composite_child ();
769
770   gtk_widget_grab_default (close_button);
771   gtk_widget_grab_focus (close_button);
772
773   /* force defaults */
774   gtk_about_dialog_set_program_name (about, NULL);
775   gtk_about_dialog_set_logo (about, NULL);
776 }
777
778 static void
779 gtk_about_dialog_finalize (GObject *object)
780 {
781   GtkAboutDialog *about = GTK_ABOUT_DIALOG (object);
782   GtkAboutDialogPrivate *priv = about->priv;
783
784   g_free (priv->name);
785   g_free (priv->version);
786   g_free (priv->copyright);
787   g_free (priv->comments);
788   g_free (priv->license);
789   g_free (priv->website_url);
790   g_free (priv->website_text);
791   g_free (priv->translator_credits);
792
793   g_strfreev (priv->authors);
794   g_strfreev (priv->documenters);
795   g_strfreev (priv->artists);
796
797   g_slist_foreach (priv->visited_links, (GFunc)g_free, NULL);
798   g_slist_free (priv->visited_links);
799
800   gdk_cursor_unref (priv->hand_cursor);
801   gdk_cursor_unref (priv->regular_cursor);
802
803   G_OBJECT_CLASS (gtk_about_dialog_parent_class)->finalize (object);
804 }
805
806 static void
807 gtk_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   GtkAboutDialogPrivate *priv = about->priv;
814
815   switch (prop_id)
816     {
817     case PROP_NAME:
818       gtk_about_dialog_set_program_name (about, g_value_get_string (value));
819       break;
820     case PROP_VERSION:
821       gtk_about_dialog_set_version (about, g_value_get_string (value));
822       break;
823     case PROP_COMMENTS:
824       gtk_about_dialog_set_comments (about, g_value_get_string (value));
825       break;
826     case PROP_WEBSITE:
827       gtk_about_dialog_set_website (about, g_value_get_string (value));
828       break;
829     case PROP_WEBSITE_LABEL:
830       gtk_about_dialog_set_website_label (about, g_value_get_string (value));
831       break;
832     case PROP_LICENSE:
833       gtk_about_dialog_set_license (about, g_value_get_string (value));
834       break;
835     case PROP_LICENSE_TYPE:
836       gtk_about_dialog_set_license_type (about, g_value_get_enum (value));
837       break;
838     case PROP_COPYRIGHT:
839       gtk_about_dialog_set_copyright (about, g_value_get_string (value));
840       break;
841     case PROP_LOGO:
842       gtk_about_dialog_set_logo (about, g_value_get_object (value));
843       break;
844     case PROP_AUTHORS:
845       gtk_about_dialog_set_authors (about, (const gchar**)g_value_get_boxed (value));
846       break;
847     case PROP_DOCUMENTERS:
848       gtk_about_dialog_set_documenters (about, (const gchar**)g_value_get_boxed (value));
849       break;
850     case PROP_ARTISTS:
851       gtk_about_dialog_set_artists (about, (const gchar**)g_value_get_boxed (value));
852       break;
853     case PROP_TRANSLATOR_CREDITS:
854       gtk_about_dialog_set_translator_credits (about, g_value_get_string (value));
855       break;
856     case PROP_LOGO_ICON_NAME:
857       gtk_about_dialog_set_logo_icon_name (about, g_value_get_string (value));
858       break;
859     case PROP_WRAP_LICENSE:
860       priv->wrap_license = g_value_get_boolean (value);
861       break;
862     default:
863       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
864       break;
865     }
866 }
867
868 static void
869 gtk_about_dialog_get_property (GObject    *object,
870                                guint       prop_id,
871                                GValue     *value,
872                                GParamSpec *pspec)
873 {
874   GtkAboutDialog *about = GTK_ABOUT_DIALOG (object);
875   GtkAboutDialogPrivate *priv = about->priv;
876         
877   switch (prop_id) 
878     {
879     case PROP_NAME:
880       g_value_set_string (value, priv->name);
881       break;
882     case PROP_VERSION:
883       g_value_set_string (value, priv->version);
884       break;
885     case PROP_COPYRIGHT:
886       g_value_set_string (value, priv->copyright);
887       break;
888     case PROP_COMMENTS:
889       g_value_set_string (value, priv->comments);
890       break;
891     case PROP_WEBSITE:
892       g_value_set_string (value, priv->website_url);
893       break;
894     case PROP_WEBSITE_LABEL:
895       g_value_set_string (value, priv->website_text);
896       break;
897     case PROP_LICENSE:
898       g_value_set_string (value, priv->license);
899       break;
900     case PROP_LICENSE_TYPE:
901       g_value_set_enum (value, priv->license_type);
902       break;
903     case PROP_TRANSLATOR_CREDITS:
904       g_value_set_string (value, priv->translator_credits);
905       break;
906     case PROP_AUTHORS:
907       g_value_set_boxed (value, priv->authors);
908       break;
909     case PROP_DOCUMENTERS:
910       g_value_set_boxed (value, priv->documenters);
911       break;
912     case PROP_ARTISTS:
913       g_value_set_boxed (value, priv->artists);
914       break;
915     case PROP_LOGO:
916       if (gtk_image_get_storage_type (GTK_IMAGE (priv->logo_image)) == GTK_IMAGE_PIXBUF)
917         g_value_set_object (value, gtk_image_get_pixbuf (GTK_IMAGE (priv->logo_image)));
918       else
919         g_value_set_object (value, NULL);
920       break;
921     case PROP_LOGO_ICON_NAME:
922       if (gtk_image_get_storage_type (GTK_IMAGE (priv->logo_image)) == GTK_IMAGE_ICON_NAME)
923         {
924           const gchar *icon_name;
925
926           gtk_image_get_icon_name (GTK_IMAGE (priv->logo_image), &icon_name, NULL);
927           g_value_set_string (value, icon_name);
928         }
929       else
930         g_value_set_string (value, NULL);
931       break;
932     case PROP_WRAP_LICENSE:
933       g_value_set_boolean (value, priv->wrap_license);
934       break;
935     default:
936       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
937       break;
938     }
939 }
940
941 static gboolean
942 gtk_about_dialog_activate_link (GtkAboutDialog *about,
943                                 const gchar    *uri)
944 {
945   GdkScreen *screen;
946   GError *error = NULL;
947
948   screen = gtk_widget_get_screen (GTK_WIDGET (about));
949
950   if (!gtk_show_uri (screen, uri, gtk_get_current_event_time (), &error))
951     {
952       GtkWidget *dialog;
953
954       dialog = gtk_message_dialog_new (GTK_WINDOW (about),
955                                        GTK_DIALOG_DESTROY_WITH_PARENT |
956                                        GTK_DIALOG_MODAL,
957                                        GTK_MESSAGE_ERROR,
958                                        GTK_BUTTONS_CLOSE,
959                                        "%s", _("Could not show link"));
960       gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
961                                                 "%s", error->message);
962       g_error_free (error);
963
964       g_signal_connect (dialog, "response",
965                         G_CALLBACK (gtk_widget_destroy), NULL);
966
967       gtk_window_present (GTK_WINDOW (dialog));
968     }
969
970   return TRUE;
971 }
972
973 static void
974 update_website (GtkAboutDialog *about)
975 {
976   GtkAboutDialogPrivate *priv = about->priv;
977
978   gtk_widget_show (priv->website_label);
979
980   if (priv->website_url)
981     {
982       gchar *markup;
983
984       if (priv->website_text)
985         {
986           gchar *escaped;
987
988           escaped = g_markup_escape_text (priv->website_text, -1);
989           markup = g_strdup_printf ("<a href=\"%s\">%s</a>",
990                                     priv->website_url, escaped);
991           g_free (escaped);
992         }
993       else
994         {
995           markup = g_strdup_printf ("<a href=\"%s\">%s</a>",
996                                     priv->website_url, _("Homepage"));
997         }
998
999       gtk_label_set_markup (GTK_LABEL (priv->website_label), markup);
1000       g_free (markup);
1001     }
1002   else
1003     {
1004       if (priv->website_url)
1005         gtk_label_set_text (GTK_LABEL (priv->website_label), priv->website_url);
1006       else if (priv->website_text)
1007         gtk_label_set_text (GTK_LABEL (priv->website_label), priv->website_text);
1008       else
1009         gtk_widget_hide (priv->website_label);
1010     }
1011 }
1012
1013 static void
1014 gtk_about_dialog_show (GtkWidget *widget)
1015 {
1016   update_website (GTK_ABOUT_DIALOG (widget));
1017
1018   GTK_WIDGET_CLASS (gtk_about_dialog_parent_class)->show (widget);
1019 }
1020
1021 /**
1022  * gtk_about_dialog_get_program_name:
1023  * @about: a #GtkAboutDialog
1024  *
1025  * Returns the program name displayed in the about dialog.
1026  *
1027  * Return value: The program name. The string is owned by the about
1028  *  dialog and must not be modified.
1029  *
1030  * Since: 2.12
1031  */
1032 G_CONST_RETURN gchar *
1033 gtk_about_dialog_get_program_name (GtkAboutDialog *about)
1034 {
1035   GtkAboutDialogPrivate *priv;
1036
1037   g_return_val_if_fail (GTK_IS_ABOUT_DIALOG (about), NULL);
1038
1039   priv = about->priv;
1040
1041   return priv->name;
1042 }
1043
1044 static void
1045 update_name_version (GtkAboutDialog *about)
1046 {
1047   GtkAboutDialogPrivate *priv;
1048   gchar *title_string, *name_string;
1049
1050   priv = about->priv;
1051
1052   title_string = g_strdup_printf (_("About %s"), priv->name);
1053   gtk_window_set_title (GTK_WINDOW (about), title_string);
1054   g_free (title_string);
1055
1056   if (priv->version != NULL)
1057     {
1058       gtk_label_set_markup (GTK_LABEL (priv->version_label), priv->version);
1059       gtk_widget_show (priv->version_label);
1060     }
1061   else
1062     gtk_widget_hide (priv->version_label);
1063
1064   name_string = g_markup_printf_escaped ("<span weight=\"bold\">%s</span>",
1065                                          priv->name);
1066   gtk_label_set_markup (GTK_LABEL (priv->name_label), name_string);
1067   g_free (name_string);
1068 }
1069
1070 /**
1071  * gtk_about_dialog_set_program_name:
1072  * @about: a #GtkAboutDialog
1073  * @name: the program name
1074  *
1075  * Sets the name to display in the about dialog.
1076  * If this is not set, it defaults to g_get_application_name().
1077  *
1078  * Since: 2.12
1079  */
1080 void
1081 gtk_about_dialog_set_program_name (GtkAboutDialog *about,
1082                                    const gchar    *name)
1083 {
1084   GtkAboutDialogPrivate *priv;
1085   gchar *tmp;
1086
1087   g_return_if_fail (GTK_IS_ABOUT_DIALOG (about));
1088
1089   priv = about->priv;
1090
1091   tmp = priv->name;
1092   priv->name = g_strdup (name ? name : g_get_application_name ());
1093   g_free (tmp);
1094
1095   update_name_version (about);
1096
1097   g_object_notify (G_OBJECT (about), "program-name");
1098 }
1099
1100
1101 /**
1102  * gtk_about_dialog_get_version:
1103  * @about: a #GtkAboutDialog
1104  *
1105  * Returns the version string.
1106  *
1107  * Return value: The version string. The string is owned by the about
1108  *  dialog and must not be modified.
1109  *
1110  * Since: 2.6
1111  */
1112 G_CONST_RETURN gchar *
1113 gtk_about_dialog_get_version (GtkAboutDialog *about)
1114 {
1115   g_return_val_if_fail (GTK_IS_ABOUT_DIALOG (about), NULL);
1116
1117   return about->priv->version;
1118 }
1119
1120 /**
1121  * gtk_about_dialog_set_version:
1122  * @about: a #GtkAboutDialog
1123  * @version: (allow-none): the version string
1124  *
1125  * Sets the version string to display in the about dialog.
1126  *
1127  * Since: 2.6
1128  */
1129 void
1130 gtk_about_dialog_set_version (GtkAboutDialog *about,
1131                               const gchar    *version)
1132 {
1133   GtkAboutDialogPrivate *priv;
1134   gchar *tmp;
1135
1136   g_return_if_fail (GTK_IS_ABOUT_DIALOG (about));
1137
1138   priv = about->priv;
1139
1140   tmp = priv->version;
1141   priv->version = g_strdup (version);
1142   g_free (tmp);
1143
1144   update_name_version (about);
1145
1146   g_object_notify (G_OBJECT (about), "version");
1147 }
1148
1149 /**
1150  * gtk_about_dialog_get_copyright:
1151  * @about: a #GtkAboutDialog
1152  *
1153  * Returns the copyright string.
1154  *
1155  * Return value: The copyright string. The string is owned by the about
1156  *  dialog and must not be modified.
1157  *
1158  * Since: 2.6
1159  */
1160 G_CONST_RETURN gchar *
1161 gtk_about_dialog_get_copyright (GtkAboutDialog *about)
1162 {
1163   g_return_val_if_fail (GTK_IS_ABOUT_DIALOG (about), NULL);
1164
1165   return about->priv->copyright;
1166 }
1167
1168 /**
1169  * gtk_about_dialog_set_copyright:
1170  * @about: a #GtkAboutDialog
1171  * @copyright: (allow-none) the copyright string
1172  *
1173  * Sets the copyright string to display in the about dialog.
1174  * This should be a short string of one or two lines.
1175  *
1176  * Since: 2.6
1177  */
1178 void
1179 gtk_about_dialog_set_copyright (GtkAboutDialog *about,
1180                                 const gchar    *copyright)
1181 {
1182   GtkAboutDialogPrivate *priv;
1183   gchar *copyright_string, *tmp;
1184
1185   g_return_if_fail (GTK_IS_ABOUT_DIALOG (about));
1186
1187   priv = about->priv;
1188
1189   tmp = priv->copyright;
1190   priv->copyright = g_strdup (copyright);
1191   g_free (tmp);
1192
1193   if (priv->copyright != NULL)
1194     {
1195       copyright_string = g_markup_printf_escaped ("<span size=\"small\">%s</span>",
1196                                                   priv->copyright);
1197       gtk_label_set_markup (GTK_LABEL (priv->copyright_label), copyright_string);
1198       g_free (copyright_string);
1199
1200       gtk_widget_show (priv->copyright_label);
1201     }
1202   else
1203     gtk_widget_hide (priv->copyright_label);
1204
1205   g_object_notify (G_OBJECT (about), "copyright");
1206 }
1207
1208 /**
1209  * gtk_about_dialog_get_comments:
1210  * @about: a #GtkAboutDialog
1211  *
1212  * Returns the comments string.
1213  *
1214  * Return value: The comments. The string is owned by the about
1215  *  dialog and must not be modified.
1216  *
1217  * Since: 2.6
1218  */
1219 G_CONST_RETURN gchar *
1220 gtk_about_dialog_get_comments (GtkAboutDialog *about)
1221 {
1222   g_return_val_if_fail (GTK_IS_ABOUT_DIALOG (about), NULL);
1223
1224   return about->priv->comments;
1225 }
1226
1227 /**
1228  * gtk_about_dialog_set_comments:
1229  * @about: a #GtkAboutDialog
1230  * @comments: (allow-none): a comments string
1231  *
1232  * Sets the comments string to display in the about dialog.
1233  * This should be a short string of one or two lines.
1234  *
1235  * Since: 2.6
1236  */
1237 void
1238 gtk_about_dialog_set_comments (GtkAboutDialog *about,
1239                                const gchar    *comments)
1240 {
1241   GtkAboutDialogPrivate *priv;
1242   gchar *tmp;
1243
1244   g_return_if_fail (GTK_IS_ABOUT_DIALOG (about));
1245
1246   priv = about->priv;
1247
1248   tmp = priv->comments;
1249   if (comments)
1250     {
1251       priv->comments = g_strdup (comments);
1252       gtk_label_set_text (GTK_LABEL (priv->comments_label), priv->comments);
1253       gtk_widget_show (priv->comments_label);
1254     }
1255   else
1256     {
1257       priv->comments = NULL;
1258       gtk_widget_hide (priv->comments_label);
1259     }
1260   g_free (tmp);
1261
1262   g_object_notify (G_OBJECT (about), "comments");
1263 }
1264
1265 /**
1266  * gtk_about_dialog_get_license:
1267  * @about: a #GtkAboutDialog
1268  *
1269  * Returns the license information.
1270  *
1271  * Return value: The license information. The string is owned by the about
1272  *  dialog and must not be modified.
1273  *
1274  * Since: 2.6
1275  */
1276 G_CONST_RETURN gchar *
1277 gtk_about_dialog_get_license (GtkAboutDialog *about)
1278 {
1279   g_return_val_if_fail (GTK_IS_ABOUT_DIALOG (about), NULL);
1280
1281   return about->priv->license;
1282 }
1283
1284 /**
1285  * gtk_about_dialog_set_license:
1286  * @about: a #GtkAboutDialog
1287  * @license: (allow-none): the license information or %NULL
1288  *
1289  * Sets the license information to be displayed in the secondary
1290  * license dialog. If @license is %NULL, the license button is
1291  * hidden.
1292  *
1293  * Since: 2.6
1294  */
1295 void
1296 gtk_about_dialog_set_license (GtkAboutDialog *about,
1297                               const gchar    *license)
1298 {
1299   GtkAboutDialogPrivate *priv;
1300   gchar *tmp;
1301
1302   g_return_if_fail (GTK_IS_ABOUT_DIALOG (about));
1303
1304   priv = about->priv;
1305
1306   tmp = priv->license;
1307   if (license)
1308     {
1309       priv->license = g_strdup (license);
1310       priv->license_type = GTK_LICENSE_CUSTOM;
1311     }
1312   else
1313     {
1314       priv->license = NULL;
1315       priv->license_type = GTK_LICENSE_UNKNOWN;
1316     }
1317   g_free (tmp);
1318
1319   gtk_widget_hide (priv->license_label);
1320
1321   update_license_button_visibility (about);
1322
1323   g_object_notify (G_OBJECT (about), "license");
1324   g_object_notify (G_OBJECT (about), "license-type");
1325 }
1326
1327 /**
1328  * gtk_about_dialog_get_wrap_license:
1329  * @about: a #GtkAboutDialog
1330  *
1331  * Returns whether the license text in @about is
1332  * automatically wrapped.
1333  *
1334  * Returns: %TRUE if the license text is wrapped
1335  *
1336  * Since: 2.8
1337  */
1338 gboolean
1339 gtk_about_dialog_get_wrap_license (GtkAboutDialog *about)
1340 {
1341   g_return_val_if_fail (GTK_IS_ABOUT_DIALOG (about), FALSE);
1342
1343   return about->priv->wrap_license;
1344 }
1345
1346 /**
1347  * gtk_about_dialog_set_wrap_license:
1348  * @about: a #GtkAboutDialog
1349  * @wrap_license: whether to wrap the license
1350  *
1351  * Sets whether the license text in @about is
1352  * automatically wrapped.
1353  *
1354  * Since: 2.8
1355  */
1356 void
1357 gtk_about_dialog_set_wrap_license (GtkAboutDialog *about,
1358                                    gboolean        wrap_license)
1359 {
1360   GtkAboutDialogPrivate *priv;
1361
1362   g_return_if_fail (GTK_IS_ABOUT_DIALOG (about));
1363
1364   priv = about->priv;
1365
1366   wrap_license = wrap_license != FALSE;
1367
1368   if (priv->wrap_license != wrap_license)
1369     {
1370        priv->wrap_license = wrap_license;
1371
1372        g_object_notify (G_OBJECT (about), "wrap-license");
1373     }
1374 }
1375
1376 /**
1377  * gtk_about_dialog_get_website:
1378  * @about: a #GtkAboutDialog
1379  *
1380  * Returns the website URL.
1381  *
1382  * Return value: The website URL. The string is owned by the about
1383  *  dialog and must not be modified.
1384  *
1385  * Since: 2.6
1386  */
1387 G_CONST_RETURN gchar *
1388 gtk_about_dialog_get_website (GtkAboutDialog *about)
1389 {
1390   GtkAboutDialogPrivate *priv;
1391
1392   g_return_val_if_fail (GTK_IS_ABOUT_DIALOG (about), NULL);
1393
1394   priv = about->priv;
1395
1396   return priv->website_url;
1397 }
1398
1399 /**
1400  * gtk_about_dialog_set_website:
1401  * @about: a #GtkAboutDialog
1402  * @website: (allow-none): a URL string starting with "http://"
1403  *
1404  * Sets the URL to use for the website link.
1405  *
1406  * Since: 2.6
1407  */
1408 void
1409 gtk_about_dialog_set_website (GtkAboutDialog *about,
1410                               const gchar    *website)
1411 {
1412   GtkAboutDialogPrivate *priv;
1413   gchar *tmp;
1414
1415   g_return_if_fail (GTK_IS_ABOUT_DIALOG (about));
1416
1417   priv = about->priv;
1418
1419   tmp = priv->website_url;
1420   priv->website_url = g_strdup (website);
1421   g_free (tmp);
1422
1423   update_website (about);
1424
1425   g_object_notify (G_OBJECT (about), "website");
1426 }
1427
1428 /**
1429  * gtk_about_dialog_get_website_label:
1430  * @about: a #GtkAboutDialog
1431  *
1432  * Returns the label used for the website link.
1433  *
1434  * Return value: The label used for the website link. The string is
1435  *     owned by the about dialog and must not be modified.
1436  *
1437  * Since: 2.6
1438  */
1439 G_CONST_RETURN gchar *
1440 gtk_about_dialog_get_website_label (GtkAboutDialog *about)
1441 {
1442   GtkAboutDialogPrivate *priv;
1443
1444   g_return_val_if_fail (GTK_IS_ABOUT_DIALOG (about), NULL);
1445
1446   priv = about->priv;
1447
1448   return priv->website_text;
1449 }
1450
1451 /**
1452  * gtk_about_dialog_set_website_label:
1453  * @about: a #GtkAboutDialog
1454  * @website_label: the label used for the website link
1455  *
1456  * Sets the label to be used for the website link.
1457  * It defaults to the website URL.
1458  *
1459  * Since: 2.6
1460  */
1461 void
1462 gtk_about_dialog_set_website_label (GtkAboutDialog *about,
1463                                     const gchar    *website_label)
1464 {
1465   GtkAboutDialogPrivate *priv;
1466   gchar *tmp;
1467
1468   g_return_if_fail (GTK_IS_ABOUT_DIALOG (about));
1469
1470   priv = about->priv;
1471
1472   tmp = priv->website_text;
1473   priv->website_text = g_strdup (website_label);
1474   g_free (tmp);
1475
1476   update_website (about);
1477
1478   g_object_notify (G_OBJECT (about), "website-label");
1479 }
1480
1481 /**
1482  * gtk_about_dialog_get_authors:
1483  * @about: a #GtkAboutDialog
1484  *
1485  * Returns the string which are displayed in the authors tab
1486  * of the secondary credits dialog.
1487  *
1488  * Return value: A %NULL-terminated string array containing
1489  *  the authors. The array is owned by the about dialog
1490  *  and must not be modified.
1491  *
1492  * Since: 2.6
1493  */
1494 G_CONST_RETURN gchar * G_CONST_RETURN *
1495 gtk_about_dialog_get_authors (GtkAboutDialog *about)
1496 {
1497   GtkAboutDialogPrivate *priv;
1498
1499   g_return_val_if_fail (GTK_IS_ABOUT_DIALOG (about), NULL);
1500
1501   priv = about->priv;
1502
1503   return (const gchar * const *) priv->authors;
1504 }
1505
1506 /**
1507  * gtk_about_dialog_set_authors:
1508  * @about: a #GtkAboutDialog
1509  * @authors: (array zero-terminated=1): a %NULL-terminated array of strings
1510  *
1511  * Sets the strings which are displayed in the authors tab
1512  * of the secondary credits dialog.
1513  *
1514  * Since: 2.6
1515  */
1516 void
1517 gtk_about_dialog_set_authors (GtkAboutDialog  *about,
1518                               const gchar    **authors)
1519 {
1520   GtkAboutDialogPrivate *priv;
1521   gchar **tmp;
1522
1523   g_return_if_fail (GTK_IS_ABOUT_DIALOG (about));
1524
1525   priv = about->priv;
1526
1527   tmp = priv->authors;
1528   priv->authors = g_strdupv ((gchar **)authors);
1529   g_strfreev (tmp);
1530
1531   update_credits_button_visibility (about);
1532
1533   g_object_notify (G_OBJECT (about), "authors");
1534 }
1535
1536 /**
1537  * gtk_about_dialog_get_documenters:
1538  * @about: a #GtkAboutDialog
1539  *
1540  * Returns the string which are displayed in the documenters
1541  * tab of the secondary credits dialog.
1542  *
1543  * Return value: A %NULL-terminated string array containing
1544  *  the documenters. The array is owned by the about dialog
1545  *  and must not be modified.
1546  *
1547  * Since: 2.6
1548  */
1549 G_CONST_RETURN gchar * G_CONST_RETURN *
1550 gtk_about_dialog_get_documenters (GtkAboutDialog *about)
1551 {
1552   GtkAboutDialogPrivate *priv;
1553
1554   g_return_val_if_fail (GTK_IS_ABOUT_DIALOG (about), NULL);
1555
1556   priv = about->priv;
1557
1558   return (const gchar * const *)priv->documenters;
1559 }
1560
1561 /**
1562  * gtk_about_dialog_set_documenters:
1563  * @about: a #GtkAboutDialog
1564  * @documenters: (array zero-terminated=1): a %NULL-terminated array of strings
1565  *
1566  * Sets the strings which are displayed in the documenters tab
1567  * of the secondary credits dialog.
1568  *
1569  * Since: 2.6
1570  */
1571 void
1572 gtk_about_dialog_set_documenters (GtkAboutDialog *about,
1573                                   const gchar   **documenters)
1574 {
1575   GtkAboutDialogPrivate *priv;
1576   gchar **tmp;
1577
1578   g_return_if_fail (GTK_IS_ABOUT_DIALOG (about));
1579
1580   priv = about->priv;
1581
1582   tmp = priv->documenters;
1583   priv->documenters = g_strdupv ((gchar **)documenters);
1584   g_strfreev (tmp);
1585
1586   update_credits_button_visibility (about);
1587
1588   g_object_notify (G_OBJECT (about), "documenters");
1589 }
1590
1591 /**
1592  * gtk_about_dialog_get_artists:
1593  * @about: a #GtkAboutDialog
1594  *
1595  * Returns the string which are displayed in the artists tab
1596  * of the secondary credits dialog.
1597  *
1598  * Return value: A %NULL-terminated string array containing
1599  *  the artists. The array is owned by the about dialog
1600  *  and must not be modified.
1601  *
1602  * Since: 2.6
1603  */
1604 G_CONST_RETURN gchar * G_CONST_RETURN *
1605 gtk_about_dialog_get_artists (GtkAboutDialog *about)
1606 {
1607   GtkAboutDialogPrivate *priv;
1608
1609   g_return_val_if_fail (GTK_IS_ABOUT_DIALOG (about), NULL);
1610
1611   priv = about->priv;
1612
1613   return (const gchar * const *)priv->artists;
1614 }
1615
1616 /**
1617  * gtk_about_dialog_set_artists:
1618  * @about: a #GtkAboutDialog
1619  * @artists: (array zero-terminated=1): a %NULL-terminated array of strings
1620  *
1621  * Sets the strings which are displayed in the artists tab
1622  * of the secondary credits dialog.
1623  *
1624  * Since: 2.6
1625  */
1626 void
1627 gtk_about_dialog_set_artists (GtkAboutDialog *about,
1628                               const gchar   **artists)
1629 {
1630   GtkAboutDialogPrivate *priv;
1631   gchar **tmp;
1632
1633   g_return_if_fail (GTK_IS_ABOUT_DIALOG (about));
1634
1635   priv = about->priv;
1636
1637   tmp = priv->artists;
1638   priv->artists = g_strdupv ((gchar **)artists);
1639   g_strfreev (tmp);
1640
1641   update_credits_button_visibility (about);
1642
1643   g_object_notify (G_OBJECT (about), "artists");
1644 }
1645
1646 /**
1647  * gtk_about_dialog_get_translator_credits:
1648  * @about: a #GtkAboutDialog
1649  *
1650  * Returns the translator credits string which is displayed
1651  * in the translators tab of the secondary credits dialog.
1652  *
1653  * Return value: The translator credits string. The string is
1654  *   owned by the about dialog and must not be modified.
1655  *
1656  * Since: 2.6
1657  */
1658 G_CONST_RETURN gchar *
1659 gtk_about_dialog_get_translator_credits (GtkAboutDialog *about)
1660 {
1661   g_return_val_if_fail (GTK_IS_ABOUT_DIALOG (about), NULL);
1662
1663   return about->priv->translator_credits;
1664 }
1665
1666 /**
1667  * gtk_about_dialog_set_translator_credits:
1668  * @about: a #GtkAboutDialog
1669  * @translator_credits: (allow-none): the translator credits
1670  *
1671  * Sets the translator credits string which is displayed in
1672  * the translators tab of the secondary credits dialog.
1673  *
1674  * The intended use for this string is to display the translator
1675  * of the language which is currently used in the user interface.
1676  * Using gettext(), a simple way to achieve that is to mark the
1677  * string for translation:
1678  * |[
1679  *  gtk_about_dialog_set_translator_credits (about, _("translator-credits"));
1680  * ]|
1681  * It is a good idea to use the customary msgid "translator-credits" for this
1682  * purpose, since translators will already know the purpose of that msgid, and
1683  * since #GtkAboutDialog will detect if "translator-credits" is untranslated
1684  * and hide the tab.
1685  *
1686  * Since: 2.6
1687  */
1688 void
1689 gtk_about_dialog_set_translator_credits (GtkAboutDialog *about,
1690                                          const gchar    *translator_credits)
1691 {
1692   GtkAboutDialogPrivate *priv;
1693   gchar *tmp;
1694
1695   g_return_if_fail (GTK_IS_ABOUT_DIALOG (about));
1696
1697   priv = about->priv;
1698
1699   tmp = priv->translator_credits;
1700   priv->translator_credits = g_strdup (translator_credits);
1701   g_free (tmp);
1702
1703   update_credits_button_visibility (about);
1704
1705   g_object_notify (G_OBJECT (about), "translator-credits");
1706 }
1707
1708 /**
1709  * gtk_about_dialog_get_logo:
1710  * @about: a #GtkAboutDialog
1711  *
1712  * Returns the pixbuf displayed as logo in the about dialog.
1713  *
1714  * Return value: the pixbuf displayed as logo. The pixbuf is
1715  *   owned by the about dialog. If you want to keep a reference
1716  *   to it, you have to call g_object_ref() on it.
1717  *
1718  * Since: 2.6
1719  */
1720 GdkPixbuf *
1721 gtk_about_dialog_get_logo (GtkAboutDialog *about)
1722 {
1723   GtkAboutDialogPrivate *priv;
1724
1725   g_return_val_if_fail (GTK_IS_ABOUT_DIALOG (about), NULL);
1726
1727   priv = about->priv;
1728
1729   if (gtk_image_get_storage_type (GTK_IMAGE (priv->logo_image)) == GTK_IMAGE_PIXBUF)
1730     return gtk_image_get_pixbuf (GTK_IMAGE (priv->logo_image));
1731   else
1732     return NULL;
1733 }
1734
1735 static GtkIconSet *
1736 icon_set_new_from_pixbufs (GList *pixbufs)
1737 {
1738   GtkIconSet *icon_set = gtk_icon_set_new ();
1739
1740   for (; pixbufs; pixbufs = pixbufs->next)
1741     {
1742       GdkPixbuf *pixbuf = GDK_PIXBUF (pixbufs->data);
1743
1744       GtkIconSource *icon_source = gtk_icon_source_new ();
1745       gtk_icon_source_set_pixbuf (icon_source, pixbuf);
1746       gtk_icon_set_add_source (icon_set, icon_source);
1747       gtk_icon_source_free (icon_source);
1748     }
1749
1750   return icon_set;
1751 }
1752
1753 /**
1754  * gtk_about_dialog_set_logo:
1755  * @about: a #GtkAboutDialog
1756  * @logo: (allow-none): a #GdkPixbuf, or %NULL
1757  *
1758  * Sets the pixbuf to be displayed as logo in the about dialog.
1759  * If it is %NULL, the default window icon set with
1760  * gtk_window_set_default_icon() will be used.
1761  *
1762  * Since: 2.6
1763  */
1764 void
1765 gtk_about_dialog_set_logo (GtkAboutDialog *about,
1766                            GdkPixbuf      *logo)
1767 {
1768   GtkAboutDialogPrivate *priv;
1769
1770   g_return_if_fail (GTK_IS_ABOUT_DIALOG (about));
1771
1772   priv = about->priv;
1773
1774   g_object_freeze_notify (G_OBJECT (about));
1775
1776   if (gtk_image_get_storage_type (GTK_IMAGE (priv->logo_image)) == GTK_IMAGE_ICON_NAME)
1777     g_object_notify (G_OBJECT (about), "logo-icon-name");
1778
1779   if (logo != NULL)
1780     gtk_image_set_from_pixbuf (GTK_IMAGE (priv->logo_image), logo);
1781   else
1782     {
1783       GList *pixbufs = gtk_window_get_default_icon_list ();
1784
1785       if (pixbufs != NULL)
1786         {
1787           GtkIconSet *icon_set = icon_set_new_from_pixbufs (pixbufs);
1788
1789           gtk_image_set_from_icon_set (GTK_IMAGE (priv->logo_image),
1790                                        icon_set, GTK_ICON_SIZE_DIALOG);
1791
1792           gtk_icon_set_unref (icon_set);
1793           g_list_free (pixbufs);
1794         }
1795     }
1796
1797   g_object_notify (G_OBJECT (about), "logo");
1798
1799   g_object_thaw_notify (G_OBJECT (about));
1800 }
1801
1802 /**
1803  * gtk_about_dialog_get_logo_icon_name:
1804  * @about: a #GtkAboutDialog
1805  *
1806  * Returns the icon name displayed as logo in the about dialog.
1807  *
1808  * Return value: the icon name displayed as logo. The string is
1809  *   owned by the dialog. If you want to keep a reference
1810  *   to it, you have to call g_strdup() on it.
1811  *
1812  * Since: 2.6
1813  */
1814 G_CONST_RETURN gchar *
1815 gtk_about_dialog_get_logo_icon_name (GtkAboutDialog *about)
1816 {
1817   GtkAboutDialogPrivate *priv;
1818   const gchar *icon_name = NULL;
1819
1820   g_return_val_if_fail (GTK_IS_ABOUT_DIALOG (about), NULL);
1821
1822   priv = about->priv;
1823
1824   if (gtk_image_get_storage_type (GTK_IMAGE (priv->logo_image)) == GTK_IMAGE_ICON_NAME)
1825     gtk_image_get_icon_name (GTK_IMAGE (priv->logo_image), &icon_name, NULL);
1826
1827   return icon_name;
1828 }
1829
1830 /**
1831  * gtk_about_dialog_set_logo_icon_name:
1832  * @about: a #GtkAboutDialog
1833  * @icon_name: (allow-none): an icon name, or %NULL
1834  *
1835  * Sets the pixbuf to be displayed as logo in the about dialog.
1836  * If it is %NULL, the default window icon set with
1837  * gtk_window_set_default_icon() will be used.
1838  *
1839  * Since: 2.6
1840  */
1841 void
1842 gtk_about_dialog_set_logo_icon_name (GtkAboutDialog *about,
1843                                      const gchar    *icon_name)
1844 {
1845   GtkAboutDialogPrivate *priv;
1846
1847   g_return_if_fail (GTK_IS_ABOUT_DIALOG (about));
1848
1849   priv = about->priv;
1850
1851   g_object_freeze_notify (G_OBJECT (about));
1852
1853   if (gtk_image_get_storage_type (GTK_IMAGE (priv->logo_image)) == GTK_IMAGE_PIXBUF)
1854     g_object_notify (G_OBJECT (about), "logo");
1855
1856   gtk_image_set_from_icon_name (GTK_IMAGE (priv->logo_image), icon_name,
1857                                 GTK_ICON_SIZE_DIALOG);
1858   g_object_notify (G_OBJECT (about), "logo-icon-name");
1859
1860   g_object_thaw_notify (G_OBJECT (about));
1861 }
1862
1863 static void
1864 follow_if_link (GtkAboutDialog *about,
1865                 GtkTextView    *text_view,
1866                 GtkTextIter    *iter)
1867 {
1868   GSList *tags = NULL, *tagp = NULL;
1869   GtkAboutDialogPrivate *priv = about->priv;
1870   gchar *uri = NULL;
1871
1872   tags = gtk_text_iter_get_tags (iter);
1873   for (tagp = tags; tagp != NULL && !uri; tagp = tagp->next)
1874     {
1875       GtkTextTag *tag = tagp->data;
1876
1877       uri = g_object_get_data (G_OBJECT (tag), "uri");
1878       if (uri)
1879         emit_activate_link (about, uri);
1880
1881       if (uri && !g_slist_find_custom (priv->visited_links, uri, (GCompareFunc)strcmp))
1882         {
1883           GdkColor *style_visited_link_color;
1884           GdkColor color;
1885
1886           gtk_widget_ensure_style (GTK_WIDGET (about));
1887           gtk_widget_style_get (GTK_WIDGET (about),
1888                                 "visited-link-color", &style_visited_link_color,
1889                                 NULL);
1890           if (style_visited_link_color)
1891             {
1892               color = *style_visited_link_color;
1893               gdk_color_free (style_visited_link_color);
1894             }
1895           else
1896             color = default_visited_link_color;
1897
1898           g_object_set (G_OBJECT (tag), "foreground-gdk", &color, NULL);
1899
1900           priv->visited_links = g_slist_prepend (priv->visited_links, g_strdup (uri));
1901         }
1902     }
1903
1904   if (tags)
1905     g_slist_free (tags);
1906 }
1907
1908 static gboolean
1909 text_view_key_press_event (GtkWidget      *text_view,
1910                            GdkEventKey    *event,
1911                            GtkAboutDialog *about)
1912 {
1913   GtkTextIter iter;
1914   GtkTextBuffer *buffer;
1915
1916   switch (event->keyval)
1917     {
1918       case GDK_KEY_Return:
1919       case GDK_KEY_ISO_Enter:
1920       case GDK_KEY_KP_Enter:
1921         buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (text_view));
1922         gtk_text_buffer_get_iter_at_mark (buffer, &iter,
1923                                           gtk_text_buffer_get_insert (buffer));
1924         follow_if_link (about, GTK_TEXT_VIEW (text_view), &iter);
1925         break;
1926
1927       default:
1928         break;
1929     }
1930
1931   return FALSE;
1932 }
1933
1934 static gboolean
1935 text_view_event_after (GtkWidget      *text_view,
1936                        GdkEvent       *event,
1937                        GtkAboutDialog *about)
1938 {
1939   GtkTextIter start, end, iter;
1940   GtkTextBuffer *buffer;
1941   GdkEventButton *button_event;
1942   gint x, y;
1943
1944   if (event->type != GDK_BUTTON_RELEASE)
1945     return FALSE;
1946
1947   button_event = (GdkEventButton *)event;
1948
1949   if (button_event->button != 1)
1950     return FALSE;
1951
1952   buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (text_view));
1953
1954   /* we shouldn't follow a link if the user has selected something */
1955   gtk_text_buffer_get_selection_bounds (buffer, &start, &end);
1956   if (gtk_text_iter_get_offset (&start) != gtk_text_iter_get_offset (&end))
1957     return FALSE;
1958
1959   gtk_text_view_window_to_buffer_coords (GTK_TEXT_VIEW (text_view),
1960                                          GTK_TEXT_WINDOW_WIDGET,
1961                                          button_event->x, button_event->y, &x, &y);
1962
1963   gtk_text_view_get_iter_at_location (GTK_TEXT_VIEW (text_view), &iter, x, y);
1964
1965   follow_if_link (about, GTK_TEXT_VIEW (text_view), &iter);
1966
1967   return FALSE;
1968 }
1969
1970 static void
1971 set_cursor_if_appropriate (GtkAboutDialog *about,
1972                            GtkTextView    *text_view,
1973                            GdkDevice      *device,
1974                            gint            x,
1975                            gint            y)
1976 {
1977   GtkAboutDialogPrivate *priv = about->priv;
1978   GSList *tags = NULL, *tagp = NULL;
1979   GtkTextIter iter;
1980   gboolean hovering_over_link = FALSE;
1981
1982   gtk_text_view_get_iter_at_location (text_view, &iter, x, y);
1983
1984   tags = gtk_text_iter_get_tags (&iter);
1985   for (tagp = tags;  tagp != NULL;  tagp = tagp->next)
1986     {
1987       GtkTextTag *tag = tagp->data;
1988       gchar *uri = g_object_get_data (G_OBJECT (tag), "uri");
1989
1990       if (uri != NULL)
1991         {
1992           hovering_over_link = TRUE;
1993           break;
1994         }
1995     }
1996
1997   if (hovering_over_link != priv->hovering_over_link)
1998     {
1999       priv->hovering_over_link = hovering_over_link;
2000
2001       if (hovering_over_link)
2002         gdk_window_set_device_cursor (gtk_text_view_get_window (text_view, GTK_TEXT_WINDOW_TEXT), device, priv->hand_cursor);
2003       else
2004         gdk_window_set_device_cursor (gtk_text_view_get_window (text_view, GTK_TEXT_WINDOW_TEXT), device, priv->regular_cursor);
2005     }
2006
2007   if (tags)
2008     g_slist_free (tags);
2009 }
2010
2011 static gboolean
2012 text_view_motion_notify_event (GtkWidget *text_view,
2013                                GdkEventMotion *event,
2014                                GtkAboutDialog *about)
2015 {
2016   gint x, y;
2017
2018   gtk_text_view_window_to_buffer_coords (GTK_TEXT_VIEW (text_view),
2019                                          GTK_TEXT_WINDOW_WIDGET,
2020                                          event->x, event->y, &x, &y);
2021
2022   set_cursor_if_appropriate (about, GTK_TEXT_VIEW (text_view), event->device, x, y);
2023
2024   gdk_event_request_motions (event);
2025
2026   return FALSE;
2027 }
2028
2029
2030 static gboolean
2031 text_view_visibility_notify_event (GtkWidget          *text_view,
2032                                    GdkEventVisibility *event,
2033                                    GtkAboutDialog     *about)
2034 {
2035   GdkDeviceManager *device_manager;
2036   GdkDisplay *display;
2037   GList *devices, *d;
2038   gint wx, wy, bx, by;
2039
2040   display = gdk_window_get_display (event->window);
2041   device_manager = gdk_display_get_device_manager (display);
2042   devices = gdk_device_manager_list_devices (device_manager, GDK_DEVICE_TYPE_MASTER);
2043
2044   for (d = devices; d; d = d->next)
2045     {
2046       GdkDevice *dev = d->data;
2047
2048       gdk_window_get_device_position (gtk_widget_get_window (text_view), dev,
2049                                       &wx, &wy, NULL);
2050
2051       gtk_text_view_window_to_buffer_coords (GTK_TEXT_VIEW (text_view),
2052                                              GTK_TEXT_WINDOW_WIDGET,
2053                                              wx, wy, &bx, &by);
2054
2055       set_cursor_if_appropriate (about, GTK_TEXT_VIEW (text_view), dev, bx, by);
2056     }
2057
2058   return FALSE;
2059 }
2060
2061 static GtkWidget *
2062 text_view_new (GtkAboutDialog  *about,
2063                gchar          **strings,
2064                GtkWrapMode      wrap_mode)
2065 {
2066   gchar **p;
2067   gchar *q0, *q1, *q2, *r1, *r2;
2068   GtkWidget *view;
2069   GtkTextView *text_view;
2070   GtkTextBuffer *buffer;
2071   GdkColor *style_link_color;
2072   GdkColor *style_visited_link_color;
2073   GdkColor color;
2074   GdkColor link_color;
2075   GdkColor visited_link_color;
2076   gint size;
2077   PangoFontDescription *font_desc;
2078
2079   GtkAboutDialogPrivate *priv = about->priv;
2080
2081   gtk_widget_ensure_style (GTK_WIDGET (about));
2082   gtk_widget_style_get (GTK_WIDGET (about),
2083                         "link-color", &style_link_color,
2084                         "visited-link-color", &style_visited_link_color,
2085                         NULL);
2086   if (style_link_color)
2087     {
2088       link_color = *style_link_color;
2089       gdk_color_free (style_link_color);
2090     }
2091   else
2092     link_color = default_link_color;
2093
2094   if (style_visited_link_color)
2095     {
2096       visited_link_color = *style_visited_link_color;
2097       gdk_color_free (style_visited_link_color);
2098     }
2099   else
2100     visited_link_color = default_visited_link_color;
2101
2102   view = gtk_text_view_new ();
2103   text_view = GTK_TEXT_VIEW (view);
2104   buffer = gtk_text_view_get_buffer (text_view);
2105   gtk_text_view_set_cursor_visible (text_view, FALSE);
2106   gtk_text_view_set_editable (text_view, FALSE);
2107   gtk_text_view_set_wrap_mode (text_view, wrap_mode);
2108
2109   size = pango_font_description_get_size (gtk_widget_get_style (view)->font_desc);
2110   font_desc = pango_font_description_new ();
2111   pango_font_description_set_size (font_desc, size * PANGO_SCALE_SMALL);
2112   gtk_widget_modify_font (view, font_desc);
2113   pango_font_description_free (font_desc);
2114
2115   gtk_text_view_set_left_margin (text_view, 8);
2116   gtk_text_view_set_right_margin (text_view, 8);
2117
2118   g_signal_connect (view, "key-press-event",
2119                     G_CALLBACK (text_view_key_press_event), about);
2120   g_signal_connect (view, "event-after",
2121                     G_CALLBACK (text_view_event_after), about);
2122   g_signal_connect (view, "motion-notify-event",
2123                     G_CALLBACK (text_view_motion_notify_event), about);
2124   g_signal_connect (view, "visibility-notify-event",
2125                     G_CALLBACK (text_view_visibility_notify_event), about);
2126
2127   if (strings == NULL)
2128     {
2129       gtk_widget_hide (view);
2130       return view;
2131     }
2132
2133   for (p = strings; *p; p++)
2134     {
2135       q0  = *p;
2136       while (*q0)
2137         {
2138           q1 = strchr (q0, '<');
2139           q2 = q1 ? strchr (q1, '>') : NULL;
2140           r1 = strstr (q0, "http://");
2141           if (r1)
2142             {
2143               r2 = strpbrk (r1, " \n\t");
2144               if (!r2)
2145                 r2 = strchr (r1, '\0');
2146             }
2147           else
2148             r2 = NULL;
2149
2150           if (r1 && r2 && (!q1 || !q2 || (r1 < q1)))
2151             {
2152               q1 = r1;
2153               q2 = r2;
2154             }
2155
2156           if (q1 && q2)
2157             {
2158               GtkTextIter end;
2159               gchar *link;
2160               gchar *uri;
2161               const gchar *link_type;
2162               GtkTextTag *tag;
2163
2164               if (*q1 == '<')
2165                 {
2166                   gtk_text_buffer_insert_at_cursor (buffer, q0, q1 - q0 + 1);
2167                   gtk_text_buffer_get_end_iter (buffer, &end);
2168                   q1++;
2169                   link_type = "email";
2170                 }
2171               else
2172                 {
2173                   gtk_text_buffer_insert_at_cursor (buffer, q0, q1 - q0);
2174                   gtk_text_buffer_get_end_iter (buffer, &end);
2175                   link_type = "uri";
2176                 }
2177
2178               q0 = q2;
2179
2180               link = g_strndup (q1, q2 - q1);
2181
2182               if (g_slist_find_custom (priv->visited_links, link, (GCompareFunc)strcmp))
2183                 color = visited_link_color;
2184               else
2185                 color = link_color;
2186
2187               tag = gtk_text_buffer_create_tag (buffer, NULL,
2188                                                 "foreground-gdk", &color,
2189                                                 "underline", PANGO_UNDERLINE_SINGLE,
2190                                                 NULL);
2191               if (strcmp (link_type, "email") == 0)
2192                 {
2193                   gchar *escaped;
2194
2195                   escaped = g_uri_escape_string (link, NULL, FALSE);
2196                   uri = g_strconcat ("mailto:", escaped, NULL);
2197                   g_free (escaped);
2198                 }
2199               else
2200                 {
2201                   uri = g_strdup (link);
2202                 }
2203               g_object_set_data_full (G_OBJECT (tag), I_("uri"), uri, g_free);
2204               gtk_text_buffer_insert_with_tags (buffer, &end, link, -1, tag, NULL);
2205
2206               g_free (link);
2207             }
2208           else
2209             {
2210               gtk_text_buffer_insert_at_cursor (buffer, q0, -1);
2211               break;
2212             }
2213         }
2214
2215       if (p[1])
2216         gtk_text_buffer_insert_at_cursor (buffer, "\n", 1);
2217     }
2218
2219   gtk_widget_show (view);
2220   return view;
2221 }
2222
2223 static void
2224 add_credits_section (GtkAboutDialog *about,
2225                      GtkGrid        *grid,
2226                      gint           *row,
2227                      gchar          *title,
2228                      gchar         **people)
2229 {
2230   GtkWidget *label;
2231   gchar *markup;
2232   gchar **p;
2233   gchar *q0, *q1, *q2, *r1, *r2;
2234
2235   if (people == NULL)
2236     return;
2237
2238   markup = g_strdup_printf ("<span size=\"small\">%s</span>", title);
2239   label = gtk_label_new (markup);
2240   gtk_label_set_use_markup (GTK_LABEL (label), TRUE);
2241   g_free (markup);
2242   gtk_misc_set_alignment (GTK_MISC (label), 1.0, 0.5);
2243   gtk_grid_attach (grid, label, 0, *row, 1, 1);
2244
2245   for (p = people; *p; p++)
2246     {
2247       GString *str;
2248
2249       str = g_string_new ("<span size=\"small\">");
2250
2251       q0 = *p;
2252       while (*q0)
2253         {
2254           q1 = strchr (q0, '<');
2255           q2 = q1 ? strchr (q1, '>') : NULL;
2256           r1 = strstr (q0, "http://");
2257           if (r1)
2258             {
2259               r2 = strpbrk (r1, " \n\t");
2260               if (!r2)
2261                 r2 = strchr (r1, '\0');
2262             }
2263           else
2264             r2 = NULL;
2265
2266           if (r1 && r2 && (!q1 || !q2 || (r1 < q1)))
2267             {
2268               q1 = r1;
2269               q2 = r2;
2270             }
2271           else if (q1 && (q1[1] == 'a' || q1[1] == 'A') && q1[2] == ' ')
2272             {
2273               /* if it is a <a> link leave it for the label to parse */
2274               q1 = NULL;
2275             }
2276
2277           if (q1 && q2)
2278             {
2279               gchar *link;
2280               gchar *text;
2281
2282               if (*q1 == '<')
2283                 {
2284                   /* email */
2285                   gchar *escaped;
2286
2287                   text = g_strstrip (g_strndup (q0, q1 - q0));
2288                   q1++;
2289                   link = g_strndup (q1, q2 - q1);
2290                   q2++;
2291                   escaped = g_uri_escape_string (link, NULL, FALSE);
2292                   g_string_append_printf (str,
2293                                           "<a href=\"mailto:%s\">%s</a>",
2294                                           escaped,
2295                                           text);
2296                   g_free (escaped);
2297                   g_free (link);
2298                   g_free (text);
2299                 }
2300               else
2301                 {
2302                   /* uri */
2303                   text = g_strstrip (g_strndup (q0, q1 - q0));
2304                   link = g_strndup (q1, q2 - q1);
2305                   g_string_append_printf (str,
2306                                           "<a href=\"%s\">%s</a>",
2307                                           link,
2308                                           text);
2309                   g_free (link);
2310                   g_free (text);
2311                 }
2312
2313               q0 = q2;
2314             }
2315           else
2316             {
2317               g_string_append (str, q0);
2318               break;
2319             }
2320         }
2321       g_string_append (str, "</span>");
2322
2323       label = gtk_label_new (str->str);
2324       gtk_label_set_use_markup (GTK_LABEL (label), TRUE);
2325       g_string_free (str, TRUE);
2326       gtk_misc_set_alignment (GTK_MISC (label), 0, 0.5);
2327       gtk_grid_attach (grid, label, 1, *row, 1, 1);
2328       (*row)++;
2329     }
2330
2331   /* skip one at the end */
2332   (*row)++;
2333 }
2334
2335 static void
2336 create_credits_page (GtkAboutDialog *about)
2337 {
2338   GtkAboutDialogPrivate *priv = about->priv;
2339   GtkWidget *page_vbox;
2340   GtkWidget *sw;
2341   GtkWidget *grid;
2342   GtkWidget *alignment;
2343   gint row;
2344
2345   page_vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 8);
2346   gtk_widget_show (page_vbox);
2347   priv->credits_page = gtk_notebook_append_page (GTK_NOTEBOOK (priv->notebook), page_vbox, NULL);
2348
2349   sw = gtk_scrolled_window_new (NULL, NULL);
2350   gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (sw), GTK_SHADOW_NONE);
2351   gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw),
2352                                   GTK_POLICY_NEVER,
2353                                   GTK_POLICY_AUTOMATIC);
2354   gtk_box_pack_start (GTK_BOX (page_vbox), sw, TRUE, TRUE, 0);
2355
2356   alignment = gtk_alignment_new (0.5, 0, 0, 0);
2357   gtk_scrolled_window_add_with_viewport (GTK_SCROLLED_WINDOW (sw), alignment);
2358
2359   grid = gtk_grid_new ();
2360   gtk_container_set_border_width (GTK_CONTAINER (grid), 5);
2361   gtk_orientable_set_orientation (GTK_ORIENTABLE (grid), GTK_ORIENTATION_VERTICAL);
2362   gtk_grid_set_column_spacing (GTK_GRID (grid), 2);
2363   gtk_grid_set_row_spacing (GTK_GRID (grid), 12);
2364   gtk_container_add (GTK_CONTAINER (alignment), grid);
2365
2366   if (priv->authors != NULL)
2367     add_credits_section (about, GTK_GRID (grid), &row, _("Created by"), priv->authors);
2368
2369   if (priv->documenters != NULL)
2370     add_credits_section (about, GTK_GRID (grid), &row, _("Documented by"), priv->documenters);
2371
2372   /* Don't show an untranslated gettext msgid */
2373   if (priv->translator_credits != NULL &&
2374       strcmp (priv->translator_credits, "translator_credits") != 0 &&
2375       strcmp (priv->translator_credits, "translator-credits") != 0)
2376     {
2377       gchar *translators[2];
2378
2379       translators[0] = priv->translator_credits;
2380       translators[1] = NULL;
2381
2382       add_credits_section (about, GTK_GRID (grid), &row, _("Translated by"), translators);
2383     }
2384
2385   if (priv->artists != NULL)
2386     add_credits_section (about, GTK_GRID (grid), &row, _("Artwork by"), priv->artists);
2387
2388   gtk_widget_show_all (sw);
2389 }
2390
2391 static void
2392 display_credits_page (GtkWidget *button,
2393                       gpointer   data)
2394 {
2395   GtkAboutDialog *about = (GtkAboutDialog *)data;
2396   GtkAboutDialogPrivate *priv = about->priv;
2397
2398   if (priv->credits_page == 0)
2399     create_credits_page (about);
2400
2401   switch_page (about, priv->credits_page);
2402 }
2403
2404 static void
2405 create_license_page (GtkAboutDialog *about)
2406 {
2407   GtkAboutDialogPrivate *priv = about->priv;
2408   GtkWidget *page_vbox;
2409   GtkWidget *sw;
2410   GtkWidget *view;
2411   gchar *strings[2];
2412
2413   page_vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 8);
2414   priv->license_page = gtk_notebook_append_page (GTK_NOTEBOOK (priv->notebook), page_vbox, NULL);
2415
2416   sw = gtk_scrolled_window_new (NULL, NULL);
2417   gtk_container_set_border_width (GTK_CONTAINER (sw), 5);
2418   gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (sw), GTK_SHADOW_IN);
2419   gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw),
2420                                   GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
2421   gtk_box_pack_start (GTK_BOX (page_vbox), sw, TRUE, TRUE, 0);
2422
2423   strings[0] = priv->license;
2424   strings[1] = NULL;
2425   view = text_view_new (about, strings,
2426                         priv->wrap_license ? GTK_WRAP_WORD : GTK_WRAP_NONE);
2427
2428   gtk_container_add (GTK_CONTAINER (sw), view);
2429
2430   gtk_widget_show_all (page_vbox);
2431 }
2432
2433 static void
2434 display_license_page (GtkWidget *button,
2435                       gpointer   data)
2436 {
2437   GtkAboutDialog *about = (GtkAboutDialog *)data;
2438   GtkAboutDialogPrivate *priv = about->priv;
2439
2440   if (priv->license_page == 0)
2441     create_license_page (about);
2442
2443   switch_page (about, priv->license_page);
2444 }
2445
2446 /**
2447  * gtk_about_dialog_new:
2448  *
2449  * Creates a new #GtkAboutDialog.
2450  *
2451  * Returns: a newly created #GtkAboutDialog
2452  *
2453  * Since: 2.6
2454  */
2455 GtkWidget *
2456 gtk_about_dialog_new (void)
2457 {
2458   GtkAboutDialog *dialog = g_object_new (GTK_TYPE_ABOUT_DIALOG, NULL);
2459
2460   return GTK_WIDGET (dialog);
2461 }
2462
2463 static void
2464 close_cb (GtkAboutDialog *about)
2465 {
2466   switch_page (about, 0);
2467   gtk_widget_hide (GTK_WIDGET (about));
2468 }
2469
2470 /**
2471  * gtk_show_about_dialog:
2472  * @parent: (allow-none): transient parent, or %NULL for none
2473  * @first_property_name: the name of the first property
2474  * @Varargs: value of first property, followed by more properties, %NULL-terminated
2475  *
2476  * This is a convenience function for showing an application's about box.
2477  * The constructed dialog is associated with the parent window and
2478  * reused for future invocations of this function.
2479  *
2480  * Since: 2.6
2481  */
2482 void
2483 gtk_show_about_dialog (GtkWindow   *parent,
2484                        const gchar *first_property_name,
2485                        ...)
2486 {
2487   static GtkWidget *global_about_dialog = NULL;
2488   GtkWidget *dialog = NULL;
2489   va_list var_args;
2490
2491   if (parent)
2492     dialog = g_object_get_data (G_OBJECT (parent), "gtk-about-dialog");
2493   else
2494     dialog = global_about_dialog;
2495
2496   if (!dialog)
2497     {
2498       dialog = gtk_about_dialog_new ();
2499
2500       g_object_ref_sink (dialog);
2501
2502       g_signal_connect (dialog, "delete-event",
2503                         G_CALLBACK (gtk_widget_hide_on_delete), NULL);
2504
2505       /* Close dialog on user response */
2506       g_signal_connect (dialog, "response",
2507                         G_CALLBACK (close_cb), NULL);
2508
2509       va_start (var_args, first_property_name);
2510       g_object_set_valist (G_OBJECT (dialog), first_property_name, var_args);
2511       va_end (var_args);
2512
2513       if (parent)
2514         {
2515           gtk_window_set_modal (GTK_WINDOW (dialog), TRUE);
2516           gtk_window_set_transient_for (GTK_WINDOW (dialog), parent);
2517           gtk_window_set_destroy_with_parent (GTK_WINDOW (dialog), TRUE);
2518           g_object_set_data_full (G_OBJECT (parent),
2519                                   I_("gtk-about-dialog"),
2520                                   dialog, g_object_unref);
2521         }
2522       else
2523         global_about_dialog = dialog;
2524
2525     }
2526
2527   gtk_window_present (GTK_WINDOW (dialog));
2528 }
2529
2530 /**
2531  * gtk_about_dialog_set_license_type:
2532  * @about: a #GtkAboutDialog
2533  * @license_type: the type of license
2534  *
2535  * Sets the license of the application showing the @about dialog from a
2536  * list of known licenses.
2537  *
2538  * This function overrides the license set using
2539  * gtk_about_dialog_set_license().
2540  *
2541  * Since: 3.0
2542  */
2543 void
2544 gtk_about_dialog_set_license_type (GtkAboutDialog *about,
2545                                    GtkLicense      license_type)
2546 {
2547   GtkAboutDialogPrivate *priv;
2548
2549   g_return_if_fail (GTK_IS_ABOUT_DIALOG (about));
2550   g_return_if_fail (license_type >= GTK_LICENSE_UNKNOWN &&
2551                     license_type <= GTK_LICENSE_ARTISTIC);
2552
2553   priv = about->priv;
2554
2555   if (priv->license_type != license_type)
2556     {
2557       g_object_freeze_notify (G_OBJECT (about));
2558
2559       priv->license_type = license_type;
2560
2561       /* custom licenses use the contents of the :license property */
2562       if (priv->license_type != GTK_LICENSE_CUSTOM)
2563         {
2564           const gchar *url;
2565           gchar *license_string;
2566           GString *str;
2567
2568           url = gtk_license_urls[priv->license_type];
2569           if (url == NULL)
2570             url = priv->website_url;
2571
2572           str = g_string_sized_new (256);
2573           g_string_append_printf (str, _(gtk_license_preamble), url, url);
2574
2575           g_free (priv->license);
2576           priv->license = g_string_free (str, FALSE);
2577           priv->wrap_license = TRUE;
2578
2579           license_string = g_strdup_printf ("<span size=\"small\">%s</span>",
2580                                             priv->license);
2581           gtk_label_set_markup (GTK_LABEL (priv->license_label), license_string);
2582           g_free (license_string);
2583           gtk_widget_show (priv->license_label);
2584
2585           update_license_button_visibility (about);
2586
2587           g_object_notify (G_OBJECT (about), "wrap-license");
2588           g_object_notify (G_OBJECT (about), "license");
2589         }
2590       else
2591         {
2592           gtk_widget_show (priv->license_label);
2593         }
2594
2595       g_object_notify (G_OBJECT (about), "license-type");
2596
2597       g_object_thaw_notify (G_OBJECT (about));
2598     }
2599 }
2600
2601 /**
2602  * gtk_about_dialog_get_license_type:
2603  * @about: a #GtkAboutDialog
2604  *
2605  * Retrieves the license set using gtk_about_dialog_set_license_type()
2606  *
2607  * Return value: a #GtkLicense value
2608  *
2609  * Since: 3.0
2610  */
2611 GtkLicense
2612 gtk_about_dialog_get_license_type (GtkAboutDialog *about)
2613 {
2614   g_return_val_if_fail (GTK_IS_ABOUT_DIALOG (about), GTK_LICENSE_UNKNOWN);
2615
2616   return about->priv->license_type;
2617 }