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 *
about_dialog_create(GimpCoreConfig * config)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
about_dialog_map(GtkWidget * widget,GimpAboutDialog * dialog)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
about_dialog_unmap(GtkWidget * widget,GimpAboutDialog * dialog)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 *
about_dialog_load_logo(void)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
about_dialog_add_animation(GtkWidget * vbox,GimpAboutDialog * dialog)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
about_dialog_add_update(GimpAboutDialog * dialog,GimpCoreConfig * config)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
about_dialog_reshuffle(GimpAboutDialog * dialog)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
about_dialog_anim_expose(GtkWidget * widget,GdkEventExpose * event,GimpAboutDialog * dialog)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 *
insert_spacers(const gchar * string)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
mix_colors(const GdkColor * start,const GdkColor * end,GdkColor * target,gdouble pos)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
decorate_text(GimpAboutDialog * dialog,gint anim_type,gdouble time)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
about_dialog_timer(gpointer data)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
about_dialog_add_unstable_message(GtkWidget * vbox)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
about_dialog_last_release_changed(GimpCoreConfig * config,const GParamSpec * pspec,GimpAboutDialog * dialog)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
about_dialog_download_clicked(GtkButton * button,const gchar * link)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 }
880