"Fossies" - the Fresh Open Source Software Archive

Member "gimp-2.10.28/app/dialogs/about-dialog.c" (14 Sep 2021, 26570 Bytes) of package /linux/misc/gimp-2.10.28.tar.bz2:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "about-dialog.c" see the Fossies "Dox" file reference documentation and the last Fossies "Diffs" side-by-side code changes report: 2.10.22_vs_2.10.24.

    1 /* GIMP - The GNU Image Manipulation Program
    2  * Copyright (C) 1995 Spencer Kimball and Peter Mattis
    3  *
    4  * This program is free software: you can redistribute it and/or modify
    5  * it under the terms of the GNU General Public License as published by
    6  * the Free Software Foundation; either version 3 of the License, or
    7  * (at your option) any later version.
    8  *
    9  * This program is distributed in the hope that it will be useful,
   10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
   11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   12  * GNU General Public License for more details.
   13  *
   14  * You should have received a copy of the GNU General Public License
   15  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
   16  */
   17 
   18 #include "config.h"
   19 
   20 #include <string.h>
   21 
   22 #include <gegl.h>
   23 #include <gtk/gtk.h>
   24 
   25 #include "libgimpbase/gimpbase.h"
   26 #include "libgimpmath/gimpmath.h"
   27 #include "libgimpwidgets/gimpwidgets.h"
   28 
   29 #include "dialogs-types.h"
   30 
   31 #include "config/gimpcoreconfig.h"
   32 
   33 #include "core/gimp.h"
   34 #include "core/gimpcontext.h"
   35 
   36 #include "pdb/gimppdb.h"
   37 
   38 #include "about.h"
   39 #include "git-version.h"
   40 
   41 #include "about-dialog.h"
   42 #include "authors.h"
   43 #include "gimp-update.h"
   44 #include "gimp-version.h"
   45 
   46 #include "gimp-intl.h"
   47 
   48 
   49 /* The first authors are the creators and maintainers, don't shuffle
   50  * them
   51  */
   52 #define START_INDEX (G_N_ELEMENTS (creators)    - 1 /*NULL*/ + \
   53                      G_N_ELEMENTS (maintainers) - 1 /*NULL*/)
   54 
   55 
   56 typedef struct
   57 {
   58   GtkWidget   *dialog;
   59 
   60   GtkWidget      *update_frame;
   61   GimpCoreConfig *config;
   62 
   63   GtkWidget   *anim_area;
   64   PangoLayout *layout;
   65 
   66   gint         n_authors;
   67   gint         shuffle[G_N_ELEMENTS (authors) - 1];  /* NULL terminated */
   68 
   69   guint        timer;
   70 
   71   gint         index;
   72   gint         animstep;
   73   gint         textrange[2];
   74   gint         state;
   75   gboolean     visible;
   76 } GimpAboutDialog;
   77 
   78 
   79 static void        about_dialog_map           (GtkWidget       *widget,
   80                                                GimpAboutDialog *dialog);
   81 static void        about_dialog_unmap         (GtkWidget       *widget,
   82                                                GimpAboutDialog *dialog);
   83 static GdkPixbuf * about_dialog_load_logo     (void);
   84 static void        about_dialog_add_animation (GtkWidget       *vbox,
   85                                                GimpAboutDialog *dialog);
   86 static gboolean    about_dialog_anim_expose   (GtkWidget       *widget,
   87                                                GdkEventExpose  *event,
   88                                                GimpAboutDialog *dialog);
   89 static void        about_dialog_add_update    (GimpAboutDialog *dialog,
   90                                                GimpCoreConfig  *config);
   91 static void        about_dialog_reshuffle     (GimpAboutDialog *dialog);
   92 static gboolean    about_dialog_timer         (gpointer         data);
   93 
   94 #ifdef GIMP_UNSTABLE
   95 static void        about_dialog_add_unstable_message
   96                                               (GtkWidget       *vbox);
   97 #endif /* GIMP_UNSTABLE */
   98 
   99 static void        about_dialog_last_release_changed
  100                                               (GimpCoreConfig   *config,
  101                                                const GParamSpec *pspec,
  102                                                GimpAboutDialog  *dialog);
  103 static void        about_dialog_download_clicked
  104                                               (GtkButton   *button,
  105                                                const gchar *link);
  106 
  107 GtkWidget *
  108 about_dialog_create (GimpCoreConfig *config)
  109 {
  110   static GimpAboutDialog dialog;
  111 
  112   g_return_val_if_fail (GIMP_IS_CORE_CONFIG (config), NULL);
  113 
  114   if (! dialog.dialog)
  115     {
  116       GtkWidget *widget;
  117       GtkWidget *container;
  118       GdkPixbuf *pixbuf;
  119       GList     *children;
  120       gchar     *copyright;
  121       gchar     *version;
  122 
  123       dialog.n_authors = G_N_ELEMENTS (authors) - 1;
  124       dialog.config    = config;
  125 
  126       pixbuf = about_dialog_load_logo ();
  127 
  128       copyright = g_strdup_printf (GIMP_COPYRIGHT, GIMP_GIT_LAST_COMMIT_YEAR);
  129       if (gimp_version_get_revision () > 0)
  130         /* Translators: the %s is GIMP version, the %d is the
  131          * installer/package revision.
  132          * For instance: "2.10.18 (revision 2)"
  133          */
  134         version = g_strdup_printf (_("%s (revision %d)"), GIMP_VERSION,
  135                                    gimp_version_get_revision ());
  136       else
  137         version = g_strdup (GIMP_VERSION);
  138 
  139       widget = g_object_new (GTK_TYPE_ABOUT_DIALOG,
  140                              "role",               "gimp-about",
  141                              "window-position",    GTK_WIN_POS_CENTER,
  142                              "title",              _("About GIMP"),
  143                              "program-name",       GIMP_ACRONYM,
  144                              "version",            version,
  145                              "copyright",          copyright,
  146                              "comments",           GIMP_NAME,
  147                              "license",            GIMP_LICENSE,
  148                              "wrap-license",       TRUE,
  149                              "logo",               pixbuf,
  150                              "website",            "https://www.gimp.org/",
  151                              "website-label",      _("Visit the GIMP website"),
  152                              "authors",            authors,
  153                              "artists",            artists,
  154                              "documenters",        documenters,
  155                              /* Translators: insert your names here,
  156                                 separated by newline */
  157                              "translator-credits", _("translator-credits"),
  158                              NULL);
  159 
  160       if (pixbuf)
  161         g_object_unref (pixbuf);
  162 
  163       g_free (copyright);
  164       g_free (version);
  165 
  166       dialog.dialog = widget;
  167 
  168       g_object_add_weak_pointer (G_OBJECT (widget), (gpointer) &dialog.dialog);
  169 
  170       g_signal_connect (widget, "response",
  171                         G_CALLBACK (gtk_widget_destroy),
  172                         NULL);
  173 
  174       g_signal_connect (widget, "map",
  175                         G_CALLBACK (about_dialog_map),
  176                         &dialog);
  177       g_signal_connect (widget, "unmap",
  178                         G_CALLBACK (about_dialog_unmap),
  179                         &dialog);
  180 
  181       /*  kids, don't try this at home!  */
  182       container = gtk_dialog_get_content_area (GTK_DIALOG (widget));
  183       children = gtk_container_get_children (GTK_CONTAINER (container));
  184 
  185       if (GTK_IS_BOX (children->data))
  186         {
  187           about_dialog_add_animation (children->data, &dialog);
  188 #ifdef GIMP_UNSTABLE
  189           about_dialog_add_unstable_message (children->data);
  190 #endif /* GIMP_UNSTABLE */
  191           about_dialog_add_update (&dialog, config);
  192         }
  193       else
  194         g_warning ("%s: ooops, no box in this container?", G_STRLOC);
  195 
  196       g_list_free (children);
  197     }
  198 
  199   gtk_window_present (GTK_WINDOW (dialog.dialog));
  200 
  201   return dialog.dialog;
  202 }
  203 
  204 static void
  205 about_dialog_map (GtkWidget       *widget,
  206                   GimpAboutDialog *dialog)
  207 {
  208   gimp_update_refresh (dialog->config);
  209 
  210   if (dialog->layout && dialog->timer == 0)
  211     {
  212       dialog->state    = 0;
  213       dialog->index    = 0;
  214       dialog->animstep = 0;
  215       dialog->visible  = FALSE;
  216 
  217       about_dialog_reshuffle (dialog);
  218 
  219       dialog->timer = g_timeout_add (800, about_dialog_timer, dialog);
  220     }
  221 }
  222 
  223 static void
  224 about_dialog_unmap (GtkWidget       *widget,
  225                     GimpAboutDialog *dialog)
  226 {
  227   if (dialog->timer)
  228     {
  229       g_source_remove (dialog->timer);
  230       dialog->timer = 0;
  231     }
  232 }
  233 
  234 static GdkPixbuf *
  235 about_dialog_load_logo (void)
  236 {
  237   GdkPixbuf    *pixbuf = NULL;
  238   GFile        *file;
  239   GInputStream *input;
  240 
  241   file = gimp_data_directory_file ("images",
  242 #ifdef GIMP_UNSTABLE
  243                                    "gimp-devel-logo.png",
  244 #else
  245                                    "gimp-logo.png",
  246 #endif
  247                                    NULL);
  248 
  249   input = G_INPUT_STREAM (g_file_read (file, NULL, NULL));
  250   g_object_unref (file);
  251 
  252   if (input)
  253     {
  254       pixbuf = gdk_pixbuf_new_from_stream (input, NULL, NULL);
  255       g_object_unref (input);
  256     }
  257 
  258   return pixbuf;
  259 }
  260 
  261 static void
  262 about_dialog_add_animation (GtkWidget       *vbox,
  263                             GimpAboutDialog *dialog)
  264 {
  265   gint  height;
  266 
  267   dialog->anim_area = gtk_drawing_area_new ();
  268   gtk_box_pack_start (GTK_BOX (vbox), dialog->anim_area, FALSE, FALSE, 0);
  269   gtk_box_reorder_child (GTK_BOX (vbox), dialog->anim_area, 5);
  270   gtk_widget_show (dialog->anim_area);
  271 
  272   dialog->layout = gtk_widget_create_pango_layout (dialog->anim_area, NULL);
  273   g_object_weak_ref (G_OBJECT (dialog->anim_area),
  274                      (GWeakNotify) g_object_unref, dialog->layout);
  275 
  276   pango_layout_get_pixel_size (dialog->layout, NULL, &height);
  277 
  278   gtk_widget_set_size_request (dialog->anim_area, -1, 2 * height);
  279 
  280   g_signal_connect (dialog->anim_area, "expose-event",
  281                     G_CALLBACK (about_dialog_anim_expose),
  282                     dialog);
  283 }
  284 
  285 static void
  286 about_dialog_add_update (GimpAboutDialog *dialog,
  287                          GimpCoreConfig  *config)
  288 {
  289   GtkWidget *container;
  290   GList     *children;
  291   GtkWidget *vbox;
  292 
  293   GtkWidget *frame;
  294   GtkWidget *box;
  295   GtkWidget *box2;
  296   GtkWidget *label;
  297   GtkWidget *button;
  298   GtkWidget *button_image;
  299   GtkWidget *button_label;
  300   GDateTime *datetime;
  301   gchar     *date;
  302   gchar     *text;
  303 
  304   if (dialog->update_frame)
  305     {
  306       gtk_widget_destroy (dialog->update_frame);
  307       dialog->update_frame = NULL;
  308     }
  309 
  310   /* Get the dialog vbox. */
  311   container = gtk_dialog_get_content_area (GTK_DIALOG (dialog->dialog));
  312   children = gtk_container_get_children (GTK_CONTAINER (container));
  313   g_return_if_fail (GTK_IS_BOX (children->data));
  314   vbox = children->data;
  315   g_list_free (children);
  316 
  317   /* The preferred localized date representation without the time. */
  318   datetime = g_date_time_new_from_unix_local (config->last_release_timestamp);
  319   date = g_date_time_format (datetime, "%x");
  320   g_date_time_unref (datetime);
  321 
  322   /* The update frame. */
  323   frame = gtk_frame_new (NULL);
  324   gtk_box_pack_start (GTK_BOX (vbox), frame, FALSE, FALSE, 2);
  325 
  326   box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
  327   gtk_container_add (GTK_CONTAINER (frame), box);
  328 
  329   /* Button in the frame. */
  330   button = gtk_button_new ();
  331   gtk_box_pack_start (GTK_BOX (box), button, FALSE, FALSE, 0);
  332   gtk_widget_show (button);
  333 
  334   box2 = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
  335   gtk_container_add (GTK_CONTAINER (button), box2);
  336   gtk_widget_show (box2);
  337 
  338   button_image = gtk_image_new_from_icon_name (NULL, GTK_ICON_SIZE_DIALOG);
  339   gtk_box_pack_start (GTK_BOX (box2), button_image, FALSE, FALSE, 0);
  340   gtk_widget_show (button_image);
  341 
  342   button_label = gtk_label_new (NULL);
  343   gtk_box_pack_start (GTK_BOX (box2), button_label, FALSE, FALSE, 0);
  344   gtk_container_child_set (GTK_CONTAINER (box2), button_label, "expand", TRUE, NULL);
  345   gtk_widget_show (button_label);
  346 
  347   if (config->last_known_release != NULL)
  348     {
  349       /* There is a newer version. */
  350       gchar *comment = NULL;
  351 
  352       /* We want the frame to stand out. */
  353       label = gtk_label_new (NULL);
  354       text = g_strdup_printf ("<tt><b><big>%s</big></b></tt>",
  355                               _("Update available!"));
  356       gtk_label_set_markup (GTK_LABEL (label), text);
  357       g_free (text);
  358       gtk_widget_show (label);
  359       gtk_frame_set_label_widget (GTK_FRAME (frame), label);
  360       gtk_frame_set_label_align (GTK_FRAME (frame), 0.5, 0.5);
  361       gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_ETCHED_OUT);
  362       gtk_box_reorder_child (GTK_BOX (vbox), frame, 3);
  363 
  364       /* Button is an update link. */
  365       gtk_image_set_from_icon_name (GTK_IMAGE (button_image),
  366                                     "software-update-available",
  367                                     GTK_ICON_SIZE_DIALOG);
  368       g_signal_connect (button, "clicked",
  369                         (GCallback) about_dialog_download_clicked,
  370                         "https://www.gimp.org/downloads/");
  371 
  372       if (config->last_revision > 0)
  373         {
  374           /* This is actually a new revision of current version. */
  375           text = g_strdup_printf (_("Download GIMP %s revision %d (released on %s)\n"),
  376                                   config->last_known_release,
  377                                   config->last_revision,
  378                                   date);
  379 
  380           /* Finally an optional release comment. */
  381           if (config->last_release_comment)
  382             {
  383               /* Translators: <> tags are Pango markup. Please keep these
  384                * markups in your translation. */
  385               comment = g_strdup_printf (_("<u>Release comment</u>: <i>%s</i>"), config->last_release_comment);
  386             }
  387         }
  388       else
  389         {
  390           text = g_strdup_printf (_("Download GIMP %s (released on %s)\n"),
  391                                   config->last_known_release, date);
  392         }
  393       gtk_label_set_text (GTK_LABEL (button_label), text);
  394       g_free (text);
  395       g_free (date);
  396 
  397       if (comment)
  398         {
  399           label = gtk_label_new (NULL);
  400           gtk_label_set_max_width_chars (GTK_LABEL (label), 80);
  401           gtk_label_set_markup (GTK_LABEL (label), comment);
  402           gtk_label_set_line_wrap (GTK_LABEL (label), TRUE);
  403           g_free (comment);
  404 
  405           gtk_box_pack_start (GTK_BOX (box), label, FALSE, FALSE, 0);
  406           gtk_widget_show (label);
  407         }
  408     }
  409   else
  410     {
  411       /* Button is a "Check for updates" action. */
  412       gtk_image_set_from_icon_name (GTK_IMAGE (button_image),
  413                                     "view-refresh",
  414                                     GTK_ICON_SIZE_MENU);
  415       gtk_label_set_text (GTK_LABEL (button_label), _("Check for updates"));
  416       g_signal_connect_swapped (button, "clicked",
  417                                 (GCallback) gimp_update_check, config);
  418 
  419     }
  420 
  421   gtk_box_reorder_child (GTK_BOX (vbox), frame, 4);
  422 
  423   /* Last check date box. */
  424   box2 = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
  425   gtk_container_add (GTK_CONTAINER (box), box2);
  426   gtk_widget_show (box2);
  427 
  428   /* Show a small "Check for updates" button only if the big one has
  429    * been replaced by a download button.
  430    */
  431   if (config->last_known_release != NULL)
  432     {
  433       button = gtk_button_new ();
  434       button_image = gtk_image_new_from_icon_name ("view-refresh", GTK_ICON_SIZE_MENU);
  435       gtk_container_add (GTK_CONTAINER (button), button_image);
  436       gtk_widget_set_tooltip_text (button, _("Check for updates"));
  437       gtk_box_pack_start (GTK_BOX (box2), button, FALSE, FALSE, 0);
  438       g_signal_connect_swapped (button, "clicked",
  439                                 (GCallback) gimp_update_check, config);
  440       gtk_widget_show (button);
  441       gtk_widget_show (button_image);
  442     }
  443 
  444   if (config->check_update_timestamp > 0)
  445     {
  446       gchar *subtext;
  447       gchar *time;
  448 
  449       datetime = g_date_time_new_from_unix_local (config->check_update_timestamp);
  450       date = g_date_time_format (datetime, "%x");
  451       time = g_date_time_format (datetime, "%X");
  452       /* Translators: first string is the date in the locale's date
  453        * representation (e.g., 12/31/99), second is the time in the
  454        * locale's time representation (e.g., 23:13:48).
  455        */
  456       subtext = g_strdup_printf (_("Last checked on %s at %s"), date, time);
  457       g_date_time_unref (datetime);
  458       g_free (date);
  459       g_free (time);
  460 
  461       text = g_strdup_printf ("<i>%s</i>", subtext);
  462       label = gtk_label_new (NULL);
  463       gtk_label_set_markup (GTK_LABEL (label), text);
  464       gtk_label_set_justify (GTK_LABEL (label), GTK_JUSTIFY_CENTER);
  465       gtk_box_pack_start (GTK_BOX (box2), label, FALSE, FALSE, 0);
  466       gtk_container_child_set (GTK_CONTAINER (box2), label, "expand", TRUE, NULL);
  467       gtk_widget_show (label);
  468       g_free (text);
  469       g_free (subtext);
  470     }
  471 
  472   gtk_widget_show (box);
  473   gtk_widget_show (frame);
  474 
  475   dialog->update_frame = frame;
  476   g_object_add_weak_pointer (G_OBJECT (frame), (gpointer) &dialog->update_frame);
  477 
  478   /* Reconstruct the dialog when release info changes. */
  479   g_signal_connect (config, "notify::last-known-release",
  480                     (GCallback) about_dialog_last_release_changed,
  481                     dialog);
  482 }
  483 
  484 static void
  485 about_dialog_reshuffle (GimpAboutDialog *dialog)
  486 {
  487   GRand *gr = g_rand_new ();
  488   gint   i;
  489 
  490   for (i = 0; i < dialog->n_authors; i++)
  491     dialog->shuffle[i] = i;
  492 
  493   for (i = START_INDEX; i < dialog->n_authors; i++)
  494     {
  495       gint j = g_rand_int_range (gr, START_INDEX, dialog->n_authors);
  496 
  497       if (i != j)
  498         {
  499           gint t;
  500 
  501           t = dialog->shuffle[j];
  502           dialog->shuffle[j] = dialog->shuffle[i];
  503           dialog->shuffle[i] = t;
  504         }
  505     }
  506 
  507   g_rand_free (gr);
  508 }
  509 
  510 static gboolean
  511 about_dialog_anim_expose (GtkWidget       *widget,
  512                           GdkEventExpose  *event,
  513                           GimpAboutDialog *dialog)
  514 {
  515   GtkStyle      *style = gtk_widget_get_style (widget);
  516   cairo_t       *cr;
  517   GtkAllocation  allocation;
  518   gint           x, y;
  519   gint           width, height;
  520 
  521   if (! dialog->visible)
  522     return FALSE;
  523 
  524   cr = gdk_cairo_create (event->window);
  525 
  526   gdk_cairo_set_source_color (cr, &style->text[GTK_STATE_NORMAL]);
  527 
  528   gtk_widget_get_allocation (widget, &allocation);
  529   pango_layout_get_pixel_size (dialog->layout, &width, &height);
  530 
  531   x = (allocation.width  - width)  / 2;
  532   y = (allocation.height - height) / 2;
  533 
  534   if (dialog->textrange[1] > 0)
  535     {
  536       GdkRegion *covered_region;
  537 
  538       covered_region = gdk_pango_layout_get_clip_region (dialog->layout,
  539                                                          x, y,
  540                                                          dialog->textrange, 1);
  541 
  542       gdk_region_intersect (covered_region, event->region);
  543 
  544       gdk_cairo_region (cr, covered_region);
  545       cairo_clip (cr);
  546 
  547       gdk_region_destroy (covered_region);
  548     }
  549 
  550   cairo_move_to (cr, x, y);
  551 
  552   pango_cairo_show_layout (cr, dialog->layout);
  553 
  554   cairo_destroy (cr);
  555 
  556   return FALSE;
  557 }
  558 
  559 static gchar *
  560 insert_spacers (const gchar *string)
  561 {
  562   GString  *str = g_string_new (NULL);
  563   gchar    *normalized;
  564   gchar    *ptr;
  565   gunichar  unichr;
  566 
  567   normalized = g_utf8_normalize (string, -1, G_NORMALIZE_DEFAULT_COMPOSE);
  568   ptr = normalized;
  569 
  570   while ((unichr = g_utf8_get_char (ptr)))
  571     {
  572       g_string_append_unichar (str, unichr);
  573       g_string_append_unichar (str, 0x200b);  /* ZERO WIDTH SPACE */
  574       ptr = g_utf8_next_char (ptr);
  575     }
  576 
  577   g_free (normalized);
  578 
  579   return g_string_free (str, FALSE);
  580 }
  581 
  582 static inline void
  583 mix_colors (const GdkColor *start,
  584             const GdkColor *end,
  585             GdkColor       *target,
  586             gdouble         pos)
  587 {
  588   target->red   = start->red   * (1.0 - pos) + end->red   * pos;
  589   target->green = start->green * (1.0 - pos) + end->green * pos;
  590   target->blue  = start->blue  * (1.0 - pos) + end->blue  * pos;
  591 }
  592 
  593 static void
  594 decorate_text (GimpAboutDialog *dialog,
  595                gint             anim_type,
  596                gdouble          time)
  597 {
  598   GtkStyle       *style = gtk_widget_get_style (dialog->anim_area);
  599   const gchar    *text;
  600   const gchar    *ptr;
  601   gint            letter_count = 0;
  602   gint            text_length  = 0;
  603   gint            text_bytelen = 0;
  604   gint            cluster_start, cluster_end;
  605   gunichar        unichr;
  606   PangoAttrList  *attrlist = NULL;
  607   PangoAttribute *attr;
  608   PangoRectangle  irect = {0, 0, 0, 0};
  609   PangoRectangle  lrect = {0, 0, 0, 0};
  610   GdkColor        mix;
  611 
  612   mix_colors (style->bg + GTK_STATE_NORMAL,
  613               style->fg + GTK_STATE_NORMAL, &mix, time);
  614 
  615   text = pango_layout_get_text (dialog->layout);
  616   g_return_if_fail (text != NULL);
  617 
  618   text_length = g_utf8_strlen (text, -1);
  619   text_bytelen = strlen (text);
  620 
  621   attrlist = pango_attr_list_new ();
  622 
  623   dialog->textrange[0] = 0;
  624   dialog->textrange[1] = text_bytelen;
  625 
  626   switch (anim_type)
  627     {
  628     case 0: /* Fade in */
  629       attr = pango_attr_foreground_new (mix.red, mix.green, mix.blue);
  630       attr->start_index = 0;
  631       attr->end_index = text_bytelen;
  632       pango_attr_list_insert (attrlist, attr);
  633       break;
  634 
  635     case 1: /* Fade in, spread */
  636       attr = pango_attr_foreground_new (mix.red, mix.green, mix.blue);
  637       attr->start_index = 0;
  638       attr->end_index = text_bytelen;
  639       pango_attr_list_change (attrlist, attr);
  640 
  641       ptr = text;
  642 
  643       cluster_start = 0;
  644       while ((unichr = g_utf8_get_char (ptr)))
  645         {
  646           ptr = g_utf8_next_char (ptr);
  647           cluster_end = (ptr - text);
  648 
  649           if (unichr == 0x200b)
  650             {
  651               lrect.width = (1.0 - time) * 15.0 * PANGO_SCALE + 0.5;
  652               attr = pango_attr_shape_new (&irect, &lrect);
  653               attr->start_index = cluster_start;
  654               attr->end_index = cluster_end;
  655               pango_attr_list_change (attrlist, attr);
  656             }
  657           cluster_start = cluster_end;
  658         }
  659       break;
  660 
  661     case 2: /* Fade in, sinewave */
  662       attr = pango_attr_foreground_new (mix.red, mix.green, mix.blue);
  663       attr->start_index = 0;
  664       attr->end_index = text_bytelen;
  665       pango_attr_list_change (attrlist, attr);
  666 
  667       ptr = text;
  668 
  669       cluster_start = 0;
  670 
  671       while ((unichr = g_utf8_get_char (ptr)))
  672         {
  673           if (unichr == 0x200b)
  674             {
  675               cluster_end = ptr - text;
  676               attr = pango_attr_rise_new ((1.0 -time) * 18000 *
  677                                           sin (4.0 * time +
  678                                                (float) letter_count * 0.7));
  679               attr->start_index = cluster_start;
  680               attr->end_index = cluster_end;
  681               pango_attr_list_change (attrlist, attr);
  682 
  683               letter_count++;
  684               cluster_start = cluster_end;
  685             }
  686 
  687           ptr = g_utf8_next_char (ptr);
  688         }
  689       break;
  690 
  691     case 3: /* letterwise Fade in */
  692       ptr = text;
  693 
  694       letter_count  = 0;
  695       cluster_start = 0;
  696 
  697       while ((unichr = g_utf8_get_char (ptr)))
  698         {
  699           gint    border = (text_length + 15) * time - 15;
  700           gdouble pos;
  701 
  702           if (letter_count < border)
  703             pos = 0;
  704           else if (letter_count > border + 15)
  705             pos = 1;
  706           else
  707             pos = ((gdouble) (letter_count - border)) / 15;
  708 
  709           mix_colors (style->fg + GTK_STATE_NORMAL,
  710                       style->bg + GTK_STATE_NORMAL,
  711                       &mix, pos);
  712 
  713           ptr = g_utf8_next_char (ptr);
  714 
  715           cluster_end = ptr - text;
  716 
  717           attr = pango_attr_foreground_new (mix.red, mix.green, mix.blue);
  718           attr->start_index = cluster_start;
  719           attr->end_index = cluster_end;
  720           pango_attr_list_change (attrlist, attr);
  721 
  722           if (pos < 1.0)
  723             dialog->textrange[1] = cluster_end;
  724 
  725           letter_count++;
  726           cluster_start = cluster_end;
  727         }
  728 
  729       break;
  730 
  731     default:
  732       g_printerr ("Unknown animation type %d\n", anim_type);
  733     }
  734 
  735   pango_layout_set_attributes (dialog->layout, attrlist);
  736   pango_attr_list_unref (attrlist);
  737 }
  738 
  739 static gboolean
  740 about_dialog_timer (gpointer data)
  741 {
  742   GimpAboutDialog *dialog  = data;
  743   gint             timeout = 0;
  744 
  745   if (dialog->animstep == 0)
  746     {
  747       gchar *text = NULL;
  748 
  749       dialog->visible = TRUE;
  750 
  751       switch (dialog->state)
  752         {
  753         case 0:
  754           dialog->timer = g_timeout_add (30, about_dialog_timer, dialog);
  755           dialog->state += 1;
  756           return FALSE;
  757 
  758         case 1:
  759           text = insert_spacers (_("GIMP is brought to you by"));
  760           dialog->state += 1;
  761           break;
  762 
  763         case 2:
  764           if (! (dialog->index < dialog->n_authors))
  765             dialog->index = 0;
  766 
  767           text = insert_spacers (authors[dialog->shuffle[dialog->index]]);
  768           dialog->index += 1;
  769           break;
  770 
  771         default:
  772           g_return_val_if_reached (TRUE);
  773           break;
  774         }
  775 
  776       g_return_val_if_fail (text != NULL, TRUE);
  777 
  778       pango_layout_set_text (dialog->layout, text, -1);
  779       pango_layout_set_attributes (dialog->layout, NULL);
  780 
  781       g_free (text);
  782     }
  783 
  784   if (dialog->animstep < 16)
  785     {
  786       decorate_text (dialog, 2, ((gfloat) dialog->animstep) / 15.0);
  787     }
  788   else if (dialog->animstep == 16)
  789     {
  790       timeout = 800;
  791     }
  792   else if (dialog->animstep == 17)
  793     {
  794       timeout = 30;
  795     }
  796   else if (dialog->animstep < 33)
  797     {
  798       decorate_text (dialog, 1,
  799                      1.0 - ((gfloat) (dialog->animstep - 17)) / 15.0);
  800     }
  801   else if (dialog->animstep == 33)
  802     {
  803       dialog->visible = FALSE;
  804       timeout = 300;
  805     }
  806   else
  807     {
  808       dialog->visible  = FALSE;
  809       dialog->animstep = -1;
  810       timeout = 30;
  811     }
  812 
  813   dialog->animstep++;
  814 
  815   gtk_widget_queue_draw (dialog->anim_area);
  816 
  817   if (timeout > 0)
  818     {
  819       dialog->timer = g_timeout_add (timeout, about_dialog_timer, dialog);
  820       return FALSE;
  821     }
  822 
  823   /* else keep the current timeout */
  824   return TRUE;
  825 }
  826 
  827 #ifdef GIMP_UNSTABLE
  828 
  829 static void
  830 about_dialog_add_unstable_message (GtkWidget *vbox)
  831 {
  832   GtkWidget *label;
  833   gchar     *text;
  834 
  835   text = g_strdup_printf (_("This is an unstable development release\n"
  836                             "commit %s"), GIMP_GIT_VERSION_ABBREV);
  837   label = gtk_label_new (text);
  838   g_free (text);
  839 
  840   gtk_label_set_selectable (GTK_LABEL (label), TRUE);
  841   gtk_label_set_justify (GTK_LABEL (label), GTK_JUSTIFY_CENTER);
  842   gimp_label_set_attributes (GTK_LABEL (label),
  843                              PANGO_ATTR_STYLE, PANGO_STYLE_ITALIC,
  844                              -1);
  845   gtk_box_pack_start (GTK_BOX (vbox), label, FALSE, FALSE, 0);
  846   gtk_box_reorder_child (GTK_BOX (vbox), label, 2);
  847   gtk_widget_show (label);
  848 }
  849 
  850 #endif /* GIMP_UNSTABLE */
  851 
  852 static void
  853 about_dialog_last_release_changed (GimpCoreConfig   *config,
  854                                    const GParamSpec *pspec,
  855                                    GimpAboutDialog  *dialog)
  856 {
  857   g_signal_handlers_disconnect_by_func (config,
  858                                         (GCallback) about_dialog_last_release_changed,
  859                                         dialog);
  860   if (! dialog->dialog)
  861     return;
  862 
  863   about_dialog_add_update (dialog, config);
  864 }
  865 
  866 static void
  867 about_dialog_download_clicked (GtkButton   *button,
  868                                const gchar *link)
  869 {
  870   GtkWidget *window;
  871 
  872   window = gtk_widget_get_ancestor (GTK_WIDGET (button), GTK_TYPE_WINDOW);
  873 
  874   if (window)
  875     gtk_show_uri (gdk_screen_get_default (),
  876                   link,
  877                   gtk_get_current_event_time(),
  878                   NULL);
  879 }