summaryrefslogtreecommitdiffstats
path: root/app/dialogs/about-dialog.c
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 03:13:10 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 03:13:10 +0000
commit3c57dd931145d43f2b0aef96c4d178135956bf91 (patch)
tree3de698981e9f0cc2c4f9569b19a5f3595e741f6b /app/dialogs/about-dialog.c
parentInitial commit. (diff)
downloadgimp-3c57dd931145d43f2b0aef96c4d178135956bf91.tar.xz
gimp-3c57dd931145d43f2b0aef96c4d178135956bf91.zip
Adding upstream version 2.10.36.upstream/2.10.36
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r--app/dialogs/about-dialog.c879
1 files changed, 879 insertions, 0 deletions
diff --git a/app/dialogs/about-dialog.c b/app/dialogs/about-dialog.c
new file mode 100644
index 0000000..5db0fc4
--- /dev/null
+++ b/app/dialogs/about-dialog.c
@@ -0,0 +1,879 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+
+#include "libgimpbase/gimpbase.h"
+#include "libgimpmath/gimpmath.h"
+#include "libgimpwidgets/gimpwidgets.h"
+
+#include "dialogs-types.h"
+
+#include "config/gimpcoreconfig.h"
+
+#include "core/gimp.h"
+#include "core/gimpcontext.h"
+
+#include "pdb/gimppdb.h"
+
+#include "about.h"
+#include "git-version.h"
+
+#include "about-dialog.h"
+#include "authors.h"
+#include "gimp-update.h"
+#include "gimp-version.h"
+
+#include "gimp-intl.h"
+
+
+/* The first authors are the creators and maintainers, don't shuffle
+ * them
+ */
+#define START_INDEX (G_N_ELEMENTS (creators) - 1 /*NULL*/ + \
+ G_N_ELEMENTS (maintainers) - 1 /*NULL*/)
+
+
+typedef struct
+{
+ GtkWidget *dialog;
+
+ GtkWidget *update_frame;
+ GimpCoreConfig *config;
+
+ GtkWidget *anim_area;
+ PangoLayout *layout;
+
+ gint n_authors;
+ gint shuffle[G_N_ELEMENTS (authors) - 1]; /* NULL terminated */
+
+ guint timer;
+
+ gint index;
+ gint animstep;
+ gint textrange[2];
+ gint state;
+ gboolean visible;
+} GimpAboutDialog;
+
+
+static void about_dialog_map (GtkWidget *widget,
+ GimpAboutDialog *dialog);
+static void about_dialog_unmap (GtkWidget *widget,
+ GimpAboutDialog *dialog);
+static GdkPixbuf * about_dialog_load_logo (void);
+static void about_dialog_add_animation (GtkWidget *vbox,
+ GimpAboutDialog *dialog);
+static gboolean about_dialog_anim_expose (GtkWidget *widget,
+ GdkEventExpose *event,
+ GimpAboutDialog *dialog);
+static void about_dialog_add_update (GimpAboutDialog *dialog,
+ GimpCoreConfig *config);
+static void about_dialog_reshuffle (GimpAboutDialog *dialog);
+static gboolean about_dialog_timer (gpointer data);
+
+#ifdef GIMP_UNSTABLE
+static void about_dialog_add_unstable_message
+ (GtkWidget *vbox);
+#endif /* GIMP_UNSTABLE */
+
+static void about_dialog_last_release_changed
+ (GimpCoreConfig *config,
+ const GParamSpec *pspec,
+ GimpAboutDialog *dialog);
+static void about_dialog_download_clicked
+ (GtkButton *button,
+ const gchar *link);
+
+GtkWidget *
+about_dialog_create (GimpCoreConfig *config)
+{
+ static GimpAboutDialog dialog;
+
+ g_return_val_if_fail (GIMP_IS_CORE_CONFIG (config), NULL);
+
+ if (! dialog.dialog)
+ {
+ GtkWidget *widget;
+ GtkWidget *container;
+ GdkPixbuf *pixbuf;
+ GList *children;
+ gchar *copyright;
+ gchar *version;
+
+ dialog.n_authors = G_N_ELEMENTS (authors) - 1;
+ dialog.config = config;
+
+ pixbuf = about_dialog_load_logo ();
+
+ copyright = g_strdup_printf (GIMP_COPYRIGHT, GIMP_GIT_LAST_COMMIT_YEAR);
+ if (gimp_version_get_revision () > 0)
+ /* Translators: the %s is GIMP version, the %d is the
+ * installer/package revision.
+ * For instance: "2.10.18 (revision 2)"
+ */
+ version = g_strdup_printf (_("%s (revision %d)"), GIMP_VERSION,
+ gimp_version_get_revision ());
+ else
+ version = g_strdup (GIMP_VERSION);
+
+ widget = g_object_new (GTK_TYPE_ABOUT_DIALOG,
+ "role", "gimp-about",
+ "window-position", GTK_WIN_POS_CENTER,
+ "title", _("About GIMP"),
+ "program-name", GIMP_ACRONYM,
+ "version", version,
+ "copyright", copyright,
+ "comments", GIMP_NAME,
+ "license", GIMP_LICENSE,
+ "wrap-license", TRUE,
+ "logo", pixbuf,
+ "website", "https://www.gimp.org/",
+ "website-label", _("Visit the GIMP website"),
+ "authors", authors,
+ "artists", artists,
+ "documenters", documenters,
+ /* Translators: insert your names here,
+ separated by newline */
+ "translator-credits", _("translator-credits"),
+ NULL);
+
+ if (pixbuf)
+ g_object_unref (pixbuf);
+
+ g_free (copyright);
+ g_free (version);
+
+ dialog.dialog = widget;
+
+ g_object_add_weak_pointer (G_OBJECT (widget), (gpointer) &dialog.dialog);
+
+ g_signal_connect (widget, "response",
+ G_CALLBACK (gtk_widget_destroy),
+ NULL);
+
+ g_signal_connect (widget, "map",
+ G_CALLBACK (about_dialog_map),
+ &dialog);
+ g_signal_connect (widget, "unmap",
+ G_CALLBACK (about_dialog_unmap),
+ &dialog);
+
+ /* kids, don't try this at home! */
+ container = gtk_dialog_get_content_area (GTK_DIALOG (widget));
+ children = gtk_container_get_children (GTK_CONTAINER (container));
+
+ if (GTK_IS_BOX (children->data))
+ {
+ about_dialog_add_animation (children->data, &dialog);
+#ifdef GIMP_UNSTABLE
+ about_dialog_add_unstable_message (children->data);
+#endif /* GIMP_UNSTABLE */
+ about_dialog_add_update (&dialog, config);
+ }
+ else
+ g_warning ("%s: ooops, no box in this container?", G_STRLOC);
+
+ g_list_free (children);
+ }
+
+ gtk_window_present (GTK_WINDOW (dialog.dialog));
+
+ return dialog.dialog;
+}
+
+static void
+about_dialog_map (GtkWidget *widget,
+ GimpAboutDialog *dialog)
+{
+ gimp_update_refresh (dialog->config);
+
+ if (dialog->layout && dialog->timer == 0)
+ {
+ dialog->state = 0;
+ dialog->index = 0;
+ dialog->animstep = 0;
+ dialog->visible = FALSE;
+
+ about_dialog_reshuffle (dialog);
+
+ dialog->timer = g_timeout_add (800, about_dialog_timer, dialog);
+ }
+}
+
+static void
+about_dialog_unmap (GtkWidget *widget,
+ GimpAboutDialog *dialog)
+{
+ if (dialog->timer)
+ {
+ g_source_remove (dialog->timer);
+ dialog->timer = 0;
+ }
+}
+
+static GdkPixbuf *
+about_dialog_load_logo (void)
+{
+ GdkPixbuf *pixbuf = NULL;
+ GFile *file;
+ GInputStream *input;
+
+ file = gimp_data_directory_file ("images",
+#ifdef GIMP_UNSTABLE
+ "gimp-devel-logo.png",
+#else
+ "gimp-logo.png",
+#endif
+ NULL);
+
+ input = G_INPUT_STREAM (g_file_read (file, NULL, NULL));
+ g_object_unref (file);
+
+ if (input)
+ {
+ pixbuf = gdk_pixbuf_new_from_stream (input, NULL, NULL);
+ g_object_unref (input);
+ }
+
+ return pixbuf;
+}
+
+static void
+about_dialog_add_animation (GtkWidget *vbox,
+ GimpAboutDialog *dialog)
+{
+ gint height;
+
+ dialog->anim_area = gtk_drawing_area_new ();
+ gtk_box_pack_start (GTK_BOX (vbox), dialog->anim_area, FALSE, FALSE, 0);
+ gtk_box_reorder_child (GTK_BOX (vbox), dialog->anim_area, 5);
+ gtk_widget_show (dialog->anim_area);
+
+ dialog->layout = gtk_widget_create_pango_layout (dialog->anim_area, NULL);
+ g_object_weak_ref (G_OBJECT (dialog->anim_area),
+ (GWeakNotify) g_object_unref, dialog->layout);
+
+ pango_layout_get_pixel_size (dialog->layout, NULL, &height);
+
+ gtk_widget_set_size_request (dialog->anim_area, -1, 2 * height);
+
+ g_signal_connect (dialog->anim_area, "expose-event",
+ G_CALLBACK (about_dialog_anim_expose),
+ dialog);
+}
+
+static void
+about_dialog_add_update (GimpAboutDialog *dialog,
+ GimpCoreConfig *config)
+{
+ GtkWidget *container;
+ GList *children;
+ GtkWidget *vbox;
+
+ GtkWidget *frame;
+ GtkWidget *box;
+ GtkWidget *box2;
+ GtkWidget *label;
+ GtkWidget *button;
+ GtkWidget *button_image;
+ GtkWidget *button_label;
+ GDateTime *datetime;
+ gchar *date;
+ gchar *text;
+
+ if (dialog->update_frame)
+ {
+ gtk_widget_destroy (dialog->update_frame);
+ dialog->update_frame = NULL;
+ }
+
+ /* Get the dialog vbox. */
+ container = gtk_dialog_get_content_area (GTK_DIALOG (dialog->dialog));
+ children = gtk_container_get_children (GTK_CONTAINER (container));
+ g_return_if_fail (GTK_IS_BOX (children->data));
+ vbox = children->data;
+ g_list_free (children);
+
+ /* The preferred localized date representation without the time. */
+ datetime = g_date_time_new_from_unix_local (config->last_release_timestamp);
+ date = g_date_time_format (datetime, "%x");
+ g_date_time_unref (datetime);
+
+ /* The update frame. */
+ frame = gtk_frame_new (NULL);
+ gtk_box_pack_start (GTK_BOX (vbox), frame, FALSE, FALSE, 2);
+
+ box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
+ gtk_container_add (GTK_CONTAINER (frame), box);
+
+ /* Button in the frame. */
+ button = gtk_button_new ();
+ gtk_box_pack_start (GTK_BOX (box), button, FALSE, FALSE, 0);
+ gtk_widget_show (button);
+
+ box2 = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
+ gtk_container_add (GTK_CONTAINER (button), box2);
+ gtk_widget_show (box2);
+
+ button_image = gtk_image_new_from_icon_name (NULL, GTK_ICON_SIZE_DIALOG);
+ gtk_box_pack_start (GTK_BOX (box2), button_image, FALSE, FALSE, 0);
+ gtk_widget_show (button_image);
+
+ button_label = gtk_label_new (NULL);
+ gtk_box_pack_start (GTK_BOX (box2), button_label, FALSE, FALSE, 0);
+ gtk_container_child_set (GTK_CONTAINER (box2), button_label, "expand", TRUE, NULL);
+ gtk_widget_show (button_label);
+
+ if (config->last_known_release != NULL)
+ {
+ /* There is a newer version. */
+ gchar *comment = NULL;
+
+ /* We want the frame to stand out. */
+ label = gtk_label_new (NULL);
+ text = g_strdup_printf ("<tt><b><big>%s</big></b></tt>",
+ _("Update available!"));
+ gtk_label_set_markup (GTK_LABEL (label), text);
+ g_free (text);
+ gtk_widget_show (label);
+ gtk_frame_set_label_widget (GTK_FRAME (frame), label);
+ gtk_frame_set_label_align (GTK_FRAME (frame), 0.5, 0.5);
+ gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_ETCHED_OUT);
+ gtk_box_reorder_child (GTK_BOX (vbox), frame, 3);
+
+ /* Button is an update link. */
+ gtk_image_set_from_icon_name (GTK_IMAGE (button_image),
+ "software-update-available",
+ GTK_ICON_SIZE_DIALOG);
+ g_signal_connect (button, "clicked",
+ (GCallback) about_dialog_download_clicked,
+ "https://www.gimp.org/downloads/");
+
+ if (config->last_revision > 0)
+ {
+ /* This is actually a new revision of current version. */
+ text = g_strdup_printf (_("Download GIMP %s revision %d (released on %s)\n"),
+ config->last_known_release,
+ config->last_revision,
+ date);
+
+ /* Finally an optional release comment. */
+ if (config->last_release_comment)
+ {
+ /* Translators: <> tags are Pango markup. Please keep these
+ * markups in your translation. */
+ comment = g_strdup_printf (_("<u>Release comment</u>: <i>%s</i>"), config->last_release_comment);
+ }
+ }
+ else
+ {
+ text = g_strdup_printf (_("Download GIMP %s (released on %s)\n"),
+ config->last_known_release, date);
+ }
+ gtk_label_set_text (GTK_LABEL (button_label), text);
+ g_free (text);
+ g_free (date);
+
+ if (comment)
+ {
+ label = gtk_label_new (NULL);
+ gtk_label_set_max_width_chars (GTK_LABEL (label), 80);
+ gtk_label_set_markup (GTK_LABEL (label), comment);
+ gtk_label_set_line_wrap (GTK_LABEL (label), TRUE);
+ g_free (comment);
+
+ gtk_box_pack_start (GTK_BOX (box), label, FALSE, FALSE, 0);
+ gtk_widget_show (label);
+ }
+ }
+ else
+ {
+ /* Button is a "Check for updates" action. */
+ gtk_image_set_from_icon_name (GTK_IMAGE (button_image),
+ "view-refresh",
+ GTK_ICON_SIZE_MENU);
+ gtk_label_set_text (GTK_LABEL (button_label), _("Check for updates"));
+ g_signal_connect_swapped (button, "clicked",
+ (GCallback) gimp_update_check, config);
+
+ }
+
+ gtk_box_reorder_child (GTK_BOX (vbox), frame, 4);
+
+ /* Last check date box. */
+ box2 = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
+ gtk_container_add (GTK_CONTAINER (box), box2);
+ gtk_widget_show (box2);
+
+ /* Show a small "Check for updates" button only if the big one has
+ * been replaced by a download button.
+ */
+ if (config->last_known_release != NULL)
+ {
+ button = gtk_button_new ();
+ button_image = gtk_image_new_from_icon_name ("view-refresh", GTK_ICON_SIZE_MENU);
+ gtk_container_add (GTK_CONTAINER (button), button_image);
+ gtk_widget_set_tooltip_text (button, _("Check for updates"));
+ gtk_box_pack_start (GTK_BOX (box2), button, FALSE, FALSE, 0);
+ g_signal_connect_swapped (button, "clicked",
+ (GCallback) gimp_update_check, config);
+ gtk_widget_show (button);
+ gtk_widget_show (button_image);
+ }
+
+ if (config->check_update_timestamp > 0)
+ {
+ gchar *subtext;
+ gchar *time;
+
+ datetime = g_date_time_new_from_unix_local (config->check_update_timestamp);
+ date = g_date_time_format (datetime, "%x");
+ time = g_date_time_format (datetime, "%X");
+ /* Translators: first string is the date in the locale's date
+ * representation (e.g., 12/31/99), second is the time in the
+ * locale's time representation (e.g., 23:13:48).
+ */
+ subtext = g_strdup_printf (_("Last checked on %s at %s"), date, time);
+ g_date_time_unref (datetime);
+ g_free (date);
+ g_free (time);
+
+ text = g_strdup_printf ("<i>%s</i>", subtext);
+ label = gtk_label_new (NULL);
+ gtk_label_set_markup (GTK_LABEL (label), text);
+ gtk_label_set_justify (GTK_LABEL (label), GTK_JUSTIFY_CENTER);
+ gtk_box_pack_start (GTK_BOX (box2), label, FALSE, FALSE, 0);
+ gtk_container_child_set (GTK_CONTAINER (box2), label, "expand", TRUE, NULL);
+ gtk_widget_show (label);
+ g_free (text);
+ g_free (subtext);
+ }
+
+ gtk_widget_show (box);
+ gtk_widget_show (frame);
+
+ dialog->update_frame = frame;
+ g_object_add_weak_pointer (G_OBJECT (frame), (gpointer) &dialog->update_frame);
+
+ /* Reconstruct the dialog when release info changes. */
+ g_signal_connect (config, "notify::last-known-release",
+ (GCallback) about_dialog_last_release_changed,
+ dialog);
+}
+
+static void
+about_dialog_reshuffle (GimpAboutDialog *dialog)
+{
+ GRand *gr = g_rand_new ();
+ gint i;
+
+ for (i = 0; i < dialog->n_authors; i++)
+ dialog->shuffle[i] = i;
+
+ for (i = START_INDEX; i < dialog->n_authors; i++)
+ {
+ gint j = g_rand_int_range (gr, START_INDEX, dialog->n_authors);
+
+ if (i != j)
+ {
+ gint t;
+
+ t = dialog->shuffle[j];
+ dialog->shuffle[j] = dialog->shuffle[i];
+ dialog->shuffle[i] = t;
+ }
+ }
+
+ g_rand_free (gr);
+}
+
+static gboolean
+about_dialog_anim_expose (GtkWidget *widget,
+ GdkEventExpose *event,
+ GimpAboutDialog *dialog)
+{
+ GtkStyle *style = gtk_widget_get_style (widget);
+ cairo_t *cr;
+ GtkAllocation allocation;
+ gint x, y;
+ gint width, height;
+
+ if (! dialog->visible)
+ return FALSE;
+
+ cr = gdk_cairo_create (event->window);
+
+ gdk_cairo_set_source_color (cr, &style->text[GTK_STATE_NORMAL]);
+
+ gtk_widget_get_allocation (widget, &allocation);
+ pango_layout_get_pixel_size (dialog->layout, &width, &height);
+
+ x = (allocation.width - width) / 2;
+ y = (allocation.height - height) / 2;
+
+ if (dialog->textrange[1] > 0)
+ {
+ GdkRegion *covered_region;
+
+ covered_region = gdk_pango_layout_get_clip_region (dialog->layout,
+ x, y,
+ dialog->textrange, 1);
+
+ gdk_region_intersect (covered_region, event->region);
+
+ gdk_cairo_region (cr, covered_region);
+ cairo_clip (cr);
+
+ gdk_region_destroy (covered_region);
+ }
+
+ cairo_move_to (cr, x, y);
+
+ pango_cairo_show_layout (cr, dialog->layout);
+
+ cairo_destroy (cr);
+
+ return FALSE;
+}
+
+static gchar *
+insert_spacers (const gchar *string)
+{
+ GString *str = g_string_new (NULL);
+ gchar *normalized;
+ gchar *ptr;
+ gunichar unichr;
+
+ normalized = g_utf8_normalize (string, -1, G_NORMALIZE_DEFAULT_COMPOSE);
+ ptr = normalized;
+
+ while ((unichr = g_utf8_get_char (ptr)))
+ {
+ g_string_append_unichar (str, unichr);
+ g_string_append_unichar (str, 0x200b); /* ZERO WIDTH SPACE */
+ ptr = g_utf8_next_char (ptr);
+ }
+
+ g_free (normalized);
+
+ return g_string_free (str, FALSE);
+}
+
+static inline void
+mix_colors (const GdkColor *start,
+ const GdkColor *end,
+ GdkColor *target,
+ gdouble pos)
+{
+ target->red = start->red * (1.0 - pos) + end->red * pos;
+ target->green = start->green * (1.0 - pos) + end->green * pos;
+ target->blue = start->blue * (1.0 - pos) + end->blue * pos;
+}
+
+static void
+decorate_text (GimpAboutDialog *dialog,
+ gint anim_type,
+ gdouble time)
+{
+ GtkStyle *style = gtk_widget_get_style (dialog->anim_area);
+ const gchar *text;
+ const gchar *ptr;
+ gint letter_count = 0;
+ gint text_length = 0;
+ gint text_bytelen = 0;
+ gint cluster_start, cluster_end;
+ gunichar unichr;
+ PangoAttrList *attrlist = NULL;
+ PangoAttribute *attr;
+ PangoRectangle irect = {0, 0, 0, 0};
+ PangoRectangle lrect = {0, 0, 0, 0};
+ GdkColor mix;
+
+ mix_colors (style->bg + GTK_STATE_NORMAL,
+ style->fg + GTK_STATE_NORMAL, &mix, time);
+
+ text = pango_layout_get_text (dialog->layout);
+ g_return_if_fail (text != NULL);
+
+ text_length = g_utf8_strlen (text, -1);
+ text_bytelen = strlen (text);
+
+ attrlist = pango_attr_list_new ();
+
+ dialog->textrange[0] = 0;
+ dialog->textrange[1] = text_bytelen;
+
+ switch (anim_type)
+ {
+ case 0: /* Fade in */
+ attr = pango_attr_foreground_new (mix.red, mix.green, mix.blue);
+ attr->start_index = 0;
+ attr->end_index = text_bytelen;
+ pango_attr_list_insert (attrlist, attr);
+ break;
+
+ case 1: /* Fade in, spread */
+ attr = pango_attr_foreground_new (mix.red, mix.green, mix.blue);
+ attr->start_index = 0;
+ attr->end_index = text_bytelen;
+ pango_attr_list_change (attrlist, attr);
+
+ ptr = text;
+
+ cluster_start = 0;
+ while ((unichr = g_utf8_get_char (ptr)))
+ {
+ ptr = g_utf8_next_char (ptr);
+ cluster_end = (ptr - text);
+
+ if (unichr == 0x200b)
+ {
+ lrect.width = (1.0 - time) * 15.0 * PANGO_SCALE + 0.5;
+ attr = pango_attr_shape_new (&irect, &lrect);
+ attr->start_index = cluster_start;
+ attr->end_index = cluster_end;
+ pango_attr_list_change (attrlist, attr);
+ }
+ cluster_start = cluster_end;
+ }
+ break;
+
+ case 2: /* Fade in, sinewave */
+ attr = pango_attr_foreground_new (mix.red, mix.green, mix.blue);
+ attr->start_index = 0;
+ attr->end_index = text_bytelen;
+ pango_attr_list_change (attrlist, attr);
+
+ ptr = text;
+
+ cluster_start = 0;
+
+ while ((unichr = g_utf8_get_char (ptr)))
+ {
+ if (unichr == 0x200b)
+ {
+ cluster_end = ptr - text;
+ attr = pango_attr_rise_new ((1.0 -time) * 18000 *
+ sin (4.0 * time +
+ (float) letter_count * 0.7));
+ attr->start_index = cluster_start;
+ attr->end_index = cluster_end;
+ pango_attr_list_change (attrlist, attr);
+
+ letter_count++;
+ cluster_start = cluster_end;
+ }
+
+ ptr = g_utf8_next_char (ptr);
+ }
+ break;
+
+ case 3: /* letterwise Fade in */
+ ptr = text;
+
+ letter_count = 0;
+ cluster_start = 0;
+
+ while ((unichr = g_utf8_get_char (ptr)))
+ {
+ gint border = (text_length + 15) * time - 15;
+ gdouble pos;
+
+ if (letter_count < border)
+ pos = 0;
+ else if (letter_count > border + 15)
+ pos = 1;
+ else
+ pos = ((gdouble) (letter_count - border)) / 15;
+
+ mix_colors (style->fg + GTK_STATE_NORMAL,
+ style->bg + GTK_STATE_NORMAL,
+ &mix, pos);
+
+ ptr = g_utf8_next_char (ptr);
+
+ cluster_end = ptr - text;
+
+ attr = pango_attr_foreground_new (mix.red, mix.green, mix.blue);
+ attr->start_index = cluster_start;
+ attr->end_index = cluster_end;
+ pango_attr_list_change (attrlist, attr);
+
+ if (pos < 1.0)
+ dialog->textrange[1] = cluster_end;
+
+ letter_count++;
+ cluster_start = cluster_end;
+ }
+
+ break;
+
+ default:
+ g_printerr ("Unknown animation type %d\n", anim_type);
+ }
+
+ pango_layout_set_attributes (dialog->layout, attrlist);
+ pango_attr_list_unref (attrlist);
+}
+
+static gboolean
+about_dialog_timer (gpointer data)
+{
+ GimpAboutDialog *dialog = data;
+ gint timeout = 0;
+
+ if (dialog->animstep == 0)
+ {
+ gchar *text = NULL;
+
+ dialog->visible = TRUE;
+
+ switch (dialog->state)
+ {
+ case 0:
+ dialog->timer = g_timeout_add (30, about_dialog_timer, dialog);
+ dialog->state += 1;
+ return FALSE;
+
+ case 1:
+ text = insert_spacers (_("GIMP is brought to you by"));
+ dialog->state += 1;
+ break;
+
+ case 2:
+ if (! (dialog->index < dialog->n_authors))
+ dialog->index = 0;
+
+ text = insert_spacers (authors[dialog->shuffle[dialog->index]]);
+ dialog->index += 1;
+ break;
+
+ default:
+ g_return_val_if_reached (TRUE);
+ break;
+ }
+
+ g_return_val_if_fail (text != NULL, TRUE);
+
+ pango_layout_set_text (dialog->layout, text, -1);
+ pango_layout_set_attributes (dialog->layout, NULL);
+
+ g_free (text);
+ }
+
+ if (dialog->animstep < 16)
+ {
+ decorate_text (dialog, 2, ((gfloat) dialog->animstep) / 15.0);
+ }
+ else if (dialog->animstep == 16)
+ {
+ timeout = 800;
+ }
+ else if (dialog->animstep == 17)
+ {
+ timeout = 30;
+ }
+ else if (dialog->animstep < 33)
+ {
+ decorate_text (dialog, 1,
+ 1.0 - ((gfloat) (dialog->animstep - 17)) / 15.0);
+ }
+ else if (dialog->animstep == 33)
+ {
+ dialog->visible = FALSE;
+ timeout = 300;
+ }
+ else
+ {
+ dialog->visible = FALSE;
+ dialog->animstep = -1;
+ timeout = 30;
+ }
+
+ dialog->animstep++;
+
+ gtk_widget_queue_draw (dialog->anim_area);
+
+ if (timeout > 0)
+ {
+ dialog->timer = g_timeout_add (timeout, about_dialog_timer, dialog);
+ return FALSE;
+ }
+
+ /* else keep the current timeout */
+ return TRUE;
+}
+
+#ifdef GIMP_UNSTABLE
+
+static void
+about_dialog_add_unstable_message (GtkWidget *vbox)
+{
+ GtkWidget *label;
+ gchar *text;
+
+ text = g_strdup_printf (_("This is an unstable development release\n"
+ "commit %s"), GIMP_GIT_VERSION_ABBREV);
+ label = gtk_label_new (text);
+ g_free (text);
+
+ gtk_label_set_selectable (GTK_LABEL (label), TRUE);
+ gtk_label_set_justify (GTK_LABEL (label), GTK_JUSTIFY_CENTER);
+ gimp_label_set_attributes (GTK_LABEL (label),
+ PANGO_ATTR_STYLE, PANGO_STYLE_ITALIC,
+ -1);
+ gtk_box_pack_start (GTK_BOX (vbox), label, FALSE, FALSE, 0);
+ gtk_box_reorder_child (GTK_BOX (vbox), label, 2);
+ gtk_widget_show (label);
+}
+
+#endif /* GIMP_UNSTABLE */
+
+static void
+about_dialog_last_release_changed (GimpCoreConfig *config,
+ const GParamSpec *pspec,
+ GimpAboutDialog *dialog)
+{
+ g_signal_handlers_disconnect_by_func (config,
+ (GCallback) about_dialog_last_release_changed,
+ dialog);
+ if (! dialog->dialog)
+ return;
+
+ about_dialog_add_update (dialog, config);
+}
+
+static void
+about_dialog_download_clicked (GtkButton *button,
+ const gchar *link)
+{
+ GtkWidget *window;
+
+ window = gtk_widget_get_ancestor (GTK_WIDGET (button), GTK_TYPE_WINDOW);
+
+ if (window)
+ gtk_show_uri (gdk_screen_get_default (),
+ link,
+ gtk_get_current_event_time(),
+ NULL);
+}