ssdiff: move comparison engine into its own file.
[gnumeric.git] / src / dialogs / dialog-about.c
blobe4a7ed23db6863e2666a47c2754ad47639e7ab73
1 /* vim: set sw=8: -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3 * about.c: Shows the contributors to Gnumeric.
5 * Author:
6 * Jody Goldberg <jody@gnome.org>
7 * Morten Welinder <terra@gnome.org>
9 * This program is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 2 of the License, or
12 * (at your option) any later version.
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
19 * You should have received a copy of the GNU General Public License
20 * along with this program; if not, see <https://www.gnu.org/licenses/>.
22 #include <gnumeric-config.h>
23 #include <glib/gi18n-lib.h>
24 #include <gnumeric.h>
25 #include "dialogs.h"
26 #include <string.h>
28 #include <gui-util.h>
29 #include <gnm-random.h>
30 #include <wbc-gtk.h>
31 #include <gnm-format.h>
32 #include <goffice/goffice.h>
33 #include <gtk/gtk.h>
35 #define ABOUT_KEY "about-dialog"
37 typedef enum {
38 GNM_CORE = 1 << 0, /* All round hacking */
39 GNM_FEATURE_HACKER = 1 << 1, /* Implement specific feature */
40 GNM_ANALYTICS = 1 << 2,
41 GNM_IMPORT_EXPORT = 1 << 3,
42 GNM_SCRIPTING = 1 << 4,
43 GNM_GUI = 1 << 5,
44 GNM_USABILITY = 1 << 6,
45 GNM_DOCUMENTATION = 1 << 7,
46 GNM_TRANSLATION = 1 << 8,
47 GNM_QA = 1 << 9,
48 GNM_ART = 1 << 10,
49 GNM_PACKAGING = 1 << 11
50 } ContribTypes;
51 #define GNM_ABOUT_NUM_TYPES 12
52 static char const * const about_types[GNM_ABOUT_NUM_TYPES] = {
53 N_("Core"),
54 N_("Features"),
55 N_("Analytics"),
56 N_("Import Export"),
57 N_("Scripting"),
58 N_("UI"),
59 N_("Usability"),
60 N_("Documentation"),
61 N_("Translation"),
62 N_("QA"),
63 N_("Art"),
64 N_("Packaging")
67 static struct {
68 char const *name;
69 unsigned contributions;
70 char const *details; /* optionally NULL */
71 } const contributors[] = {
72 { N_("Harald Ashburner"), GNM_ANALYTICS,
73 N_("Options pricers") },
74 { N_("Sean Atkinson"), GNM_ANALYTICS | GNM_IMPORT_EXPORT,
75 N_("Functions and X-Base importing.") },
76 { N_("Michel Berkelaar"), GNM_ANALYTICS,
77 N_("Simplex algorithm for Solver (LP Solve).") },
78 { N_("Jean Br\303\251fort"), GNM_CORE | GNM_FEATURE_HACKER,
79 N_("Core charting engine.") },
80 { N_("Grandma Chema Celorio"), GNM_FEATURE_HACKER|GNM_USABILITY|GNM_QA,
81 N_("Quality Assurance and sheet copy.") },
82 { N_("Frank Chiulli"), GNM_IMPORT_EXPORT,
83 N_("OLE2 support.") },
84 { N_("Kenneth Christiansen"), GNM_TRANSLATION,
85 N_("Localization.") },
86 { N_("Zbigniew Chyla"), GNM_CORE,
87 N_("Plugin system, localization.") },
88 { N_("J.H.M. Dassen (Ray)"), GNM_PACKAGING,
89 N_("Debian packaging.") },
90 { N_("Jeroen Dirks"), GNM_ANALYTICS,
91 N_("Simplex algorithm for Solver (LP Solve).") },
92 { N_("Tom Dyas"), GNM_FEATURE_HACKER,
93 N_("Original plugin engine.") },
94 { N_("Kjell Eikland"), GNM_ANALYTICS,
95 N_("LP-solve") },
96 { N_("Gergo Erdi"), GNM_GUI,
97 N_("Custom UI tools") },
98 { N_("Jody Goldberg"), GNM_CORE, NULL },
99 { N_("John Gotts"), GNM_PACKAGING,
100 N_("RPM packaging") },
101 { N_("Andreas J. G\xc3\xbclzow"), GNM_CORE|GNM_FEATURE_HACKER|GNM_ANALYTICS|GNM_IMPORT_EXPORT|GNM_GUI|GNM_USABILITY|GNM_DOCUMENTATION|GNM_TRANSLATION|GNM_QA,
102 N_("Statistics and GUI master") },
103 { N_("Jon K\xc3\xa5re Hellan"), GNM_CORE|GNM_FEATURE_HACKER|GNM_ANALYTICS|GNM_IMPORT_EXPORT|GNM_SCRIPTING|GNM_GUI|GNM_USABILITY|GNM_DOCUMENTATION|GNM_TRANSLATION|GNM_QA,
104 N_("UI polish and all round bug fixer") },
105 { N_("Miguel de Icaza"), GNM_CORE, NULL },
106 { N_("Ross Ihaka"), GNM_ANALYTICS,
107 N_("Special functions") },
108 { N_("Jukka-Pekka Iivonen"), GNM_ANALYTICS|GNM_GUI|GNM_FEATURE_HACKER,
109 N_("Solver, lots of worksheet functions, and general trailblazer") },
110 { N_("Jakub Jel\xc3\xadnek"), GNM_CORE,
111 N_("One of the original core contributors") },
112 { N_("Chris Lahey"), GNM_FEATURE_HACKER,
113 N_("The original value format engine and libgoffice work") },
114 { N_("Takashi Matsuda"), GNM_FEATURE_HACKER,
115 N_("The original text plugin") },
116 { N_("Michael Meeks"), GNM_CORE|GNM_IMPORT_EXPORT,
117 N_("Started the MS Excel import/export engine, and 'GnmStyle'") },
118 { N_("Lutz Muller"), GNM_FEATURE_HACKER,
119 N_("SheetObject improvement") },
120 { N_("Yukihiro Nakai"), GNM_FEATURE_HACKER | GNM_TRANSLATION | GNM_QA,
121 N_("Support for non-Latin languages") },
122 { N_("Peter Notebaert"), GNM_ANALYTICS,
123 N_("LP-solve") },
124 { N_("Emmanuel Pacaud"), GNM_CORE | GNM_FEATURE_HACKER,
125 N_("Many plot types for charting engine.") },
126 { N_("Federico M. Quintero"), GNM_CORE,
127 N_("canvas support") },
128 { N_("Mark Probst"), GNM_SCRIPTING,
129 N_("Guile support") },
130 { N_("Rasca"), GNM_IMPORT_EXPORT,
131 N_("HTML, troff, LaTeX exporters") },
132 { N_("Vincent Renardias"), GNM_IMPORT_EXPORT|GNM_TRANSLATION,
133 N_("original CSV support, French localization") },
134 { N_("Ariel Rios"), GNM_SCRIPTING,
135 N_("Guile support") },
136 { N_("Jakub Steiner"), GNM_ART,
137 N_("Icons and Images") },
138 { N_("Uwe Steinmann"), GNM_FEATURE_HACKER|GNM_IMPORT_EXPORT,
139 N_("Paradox Importer") },
140 { N_("Arturo Tena"), GNM_IMPORT_EXPORT,
141 N_("Initial work on OLE2 for libgsf") },
142 { N_("Almer S. Tigelaar"), GNM_FEATURE_HACKER|GNM_IMPORT_EXPORT,
143 N_("Consolidation and Structured Text importer") },
144 { N_("Bruno Unna"), GNM_IMPORT_EXPORT,
145 N_("Pieces of MS Excel import") },
146 { N_("Arief Mulya Utama"), GNM_ANALYTICS,
147 N_("Telecommunications functions") },
148 { N_("Daniel Veillard"), GNM_IMPORT_EXPORT,
149 N_("Initial XML support") },
150 { N_("Vladimir Vuksan"), GNM_ANALYTICS,
151 N_("Some financial functions") },
152 { N_("Morten Welinder"), GNM_CORE|GNM_FEATURE_HACKER|GNM_ANALYTICS|GNM_IMPORT_EXPORT|GNM_SCRIPTING|GNM_GUI|GNM_USABILITY|GNM_TRANSLATION|GNM_QA,
153 N_("All round powerhouse") },
154 { N_("Kevin Breit"), GNM_DOCUMENTATION, NULL },
155 { N_("Thomas Canty"), GNM_DOCUMENTATION, NULL },
156 { N_("Adrian Custer"), GNM_DOCUMENTATION, NULL },
157 { N_("Adrian Likins"), GNM_DOCUMENTATION, NULL },
158 { N_("Aaron Weber"), GNM_DOCUMENTATION, NULL },
159 { N_("Alexander Kirillov"), GNM_DOCUMENTATION, NULL },
162 typedef struct AboutRenderer_ AboutRenderer;
163 typedef struct AboutState_ AboutState;
165 struct AboutRenderer_ {
166 int start_time, duration;
168 gboolean (*renderer) (AboutRenderer *, AboutState *);
170 PangoLayout *layout;
171 int natural_width;
173 gboolean fade_in, fade_out;
175 struct {
176 double x, y;
177 } start, end;
179 struct {
180 double rate;
181 int count;
182 } expansion;
183 cairo_t *cr;
186 struct AboutState_ {
187 GtkWidget *dialog;
188 guint timer;
190 GtkWidget *anim_area;
191 GList *active, *waiting;
193 int now;
196 /* ---------------------------------------- */
198 static void
199 free_renderer (AboutRenderer *r)
201 if (r->layout)
202 g_object_unref (r->layout);
203 g_free (r);
206 static gboolean
207 text_item_renderer (AboutRenderer *r, AboutState *state)
209 PangoLayout *layout = r->layout;
210 int age = state->now - r->start_time;
211 double rage = CLAMP (age / (double)r->duration, 0.0, 1.0);
212 GtkWidget *widget = state->anim_area;
213 GtkStyleContext *ctxt;
214 const int fade = 500;
215 int x, y, width, height;
216 cairo_t *cr;
217 GtkAllocation wa;
218 GdkRGBA color;
219 double alpha = 1;
221 if (age >= r->duration)
222 return FALSE;
224 if (r->fade_in && age < fade)
225 alpha = age / (double)fade;
226 else if (r->fade_out && r->duration - age < fade)
227 alpha = (r->duration - age) / (double)fade;
229 ctxt = gtk_widget_get_style_context (widget);
231 gtk_widget_get_allocation (widget, &wa);
232 x = (int)(PANGO_SCALE * wa.width *
233 (r->start.x + rage * (r->end.x - r->start.x)));
234 y = (int)(PANGO_SCALE * wa.height *
235 (r->start.y + rage * (r->end.y - r->start.y)));
237 if (r->expansion.count) {
238 PangoAttrList *attrlist = pango_layout_get_attributes (layout);
239 const char *p, *text = pango_layout_get_text (layout);
240 PangoRectangle ink, logical;
242 memset (&ink, 0, sizeof (ink));
243 logical = ink;
245 logical.width = (int)(rage * r->expansion.rate * r->natural_width / r->expansion.count);
247 p = text;
248 while (*p) {
249 const char *next = g_utf8_next_char (p);
250 gunichar uc = g_utf8_get_char (p);
251 PangoAttribute *attr;
253 if (uc == UNICODE_ZERO_WIDTH_SPACE_C) {
254 attr = pango_attr_shape_new (&ink, &logical);
255 attr->start_index = p - text;
256 attr->end_index = next - text;
257 pango_attr_list_change (attrlist, attr);
259 p = next;
261 pango_layout_set_attributes (layout, attrlist);
264 pango_layout_get_size (layout, &width, &height);
265 x -= width / 2;
266 y -= height / 2;
268 cr = r->cr;
269 gnm_style_context_get_color (ctxt, GTK_STATE_FLAG_NORMAL, &color);
270 color.alpha = alpha;
271 gdk_cairo_set_source_rgba (cr, &color);
272 cairo_move_to (cr, x / (double)PANGO_SCALE, y / (double)PANGO_SCALE);
273 pango_cairo_show_layout (cr, layout);
275 return TRUE;
278 static void
279 set_text_motion (AboutRenderer *r, double sx, double sy, double ex, double ey)
281 r->start.x = sx;
282 r->start.y = sy;
284 r->end.x = ex;
285 r->end.y = ey;
288 static void
289 set_text_expansion (AboutRenderer *r, double er)
291 const char *text = pango_layout_get_text (r->layout);
292 GString *str = g_string_new (NULL);
293 char *ntext;
294 const char *p;
296 r->expansion.rate = er;
297 r->expansion.count = 0;
299 /* Normalize to make sure diacriticals are combined. */
300 ntext = g_utf8_normalize (text, -1, G_NORMALIZE_DEFAULT_COMPOSE);
302 /* Insert inter-letter spaces we can stretch. */
303 for (p = ntext; *p; p = g_utf8_next_char (p)) {
304 gunichar uc = g_utf8_get_char (p);
306 if (uc == UNICODE_ZERO_WIDTH_SPACE_C)
307 continue;
309 if (str->len) {
310 g_string_append_unichar (str, UNICODE_ZERO_WIDTH_SPACE_C);
311 r->expansion.count++;
313 g_string_append_unichar (str, uc);
316 g_free (ntext);
317 pango_layout_set_text (r->layout, str->str, -1);
318 g_string_free (str, TRUE);
321 static AboutRenderer *
322 make_text_item (AboutState *state, const char *text, int duration)
324 AboutRenderer *r = g_new0 (AboutRenderer, 1);
325 PangoAttrList *attrlist;
326 PangoAttribute *attr;
328 r->start_time = state->now;
329 r->duration = duration;
330 r->layout = gtk_widget_create_pango_layout (state->anim_area, NULL);
331 r->renderer = text_item_renderer;
332 r->fade_in = r->fade_out = TRUE;
333 set_text_motion (r, 0.5, 0.5, 0.5, 0.5);
335 pango_layout_set_text (r->layout, text, -1);
336 pango_layout_get_size (r->layout, &r->natural_width, NULL);
338 attrlist = pango_attr_list_new ();
339 attr = pango_attr_weight_new (PANGO_WEIGHT_BOLD);
340 pango_attr_list_change (attrlist, attr);
341 pango_layout_set_attributes (r->layout, attrlist);
342 pango_attr_list_unref (attrlist);
344 state->now += duration;
346 return r;
349 /* ---------------------------------------- */
352 #define TIME_SLICE 20 /* ms */
355 static void
356 free_state (AboutState *state)
358 if (state->timer) {
359 g_source_remove (state->timer);
360 state->timer = 0;
363 g_list_free_full (state->active, (GDestroyNotify)free_renderer);
364 state->active = NULL;
366 g_list_free_full (state->waiting, (GDestroyNotify)free_renderer);
367 state->waiting = NULL;
369 g_free (state);
372 static gboolean
373 about_dialog_timer (gpointer state_)
375 AboutState *state = state_;
377 while (state->waiting) {
378 AboutRenderer *r = state->waiting->data;
379 if (r->start_time > state->now)
380 break;
381 state->active = g_list_append (state->active, r);
382 state->waiting = g_list_remove (state->waiting, r);
385 if (state->active)
386 gtk_widget_queue_draw (state->anim_area);
388 state->now += TIME_SLICE;
390 return TRUE;
393 static gboolean
394 about_dialog_anim_draw (G_GNUC_UNUSED GtkWidget *widget,
395 cairo_t *cr,
396 AboutState *state)
398 GList *l;
400 l = state->active;
401 while (l) {
402 GList *next = l->next;
403 AboutRenderer *r = l->data;
404 gboolean keep;
405 r->cr = cr;
406 keep = r->renderer (r, state);
407 if (!keep) {
408 free_renderer (r);
409 state->active = g_list_remove_link (state->active, l);
411 l = next;
414 return FALSE;
417 #define APPENDR(r_) do { \
418 tail->next = g_list_prepend (NULL, (r_)); \
419 tail->next->prev = tail; \
420 tail = tail->next; \
421 } while (0)
423 static void
424 create_animation (AboutState *state)
426 AboutRenderer *r;
427 GList *tail;
428 unsigned ui;
429 unsigned N = G_N_ELEMENTS (contributors);
430 unsigned *permutation;
432 state->now += 500;
434 r = make_text_item (state, _("Gnumeric is the result of"), 3000);
435 set_text_motion (r, 0.5, 0.9, 0.5, 0.1);
436 tail = state->waiting = g_list_prepend (NULL, r);
438 state->now -= 2000; /* Overlap. */
440 r = make_text_item (state, _("the efforts of many people."), 3000);
441 set_text_motion (r, 0.5, 0.9, 0.5, 0.1);
442 APPENDR (r);
444 state->now -= 2000; /* Overlap. */
446 r = make_text_item (state, _("Your help is much appreciated!"), 3000);
447 set_text_motion (r, 0.5, 0.9, 0.5, 0.1);
448 APPENDR (r);
450 permutation = g_new (unsigned, N);
451 for (ui = 0; ui < N; ui++)
452 permutation[ui] = ui;
454 for (ui = 0; ui < N; ui++) {
455 unsigned pui = (int)(random_01 () * N);
456 unsigned A = permutation[ui];
457 permutation[ui] = permutation[pui];
458 permutation[pui] = A;
461 for (ui = 0; ui < N; ui++) {
462 unsigned pui = permutation[ui];
463 const char *name = contributors[pui].name;
464 int style = ui % 2;
466 if (ui != 0)
467 state->now -= 1900; /* Overlap. */
469 r = make_text_item (state, name, 3000);
470 switch (style) {
471 case 0:
472 set_text_motion (r, 0.5, 0.1, 0.1, 0.9);
473 if (0) set_text_expansion (r, 1);
474 break;
475 case 1:
476 set_text_motion (r, 0.5, 0.1, 0.9, 0.9);
477 if (0) set_text_expansion (r, 1);
478 break;
479 #if 0
480 case 2:
481 set_text_motion (r, 0.5, 0.1, 0.5, 0.9);
482 set_text_expansion (r, 3);
483 break;
484 #endif
487 APPENDR (r);
490 g_free (permutation);
492 state->now += 1000;
494 r = make_text_item (state, _("We apologize if anyone was left out."),
495 3000);
496 set_text_motion (r, 0.5, 0.9, 0.5, 0.1);
497 APPENDR (r);
499 state->now -= 2000; /* Overlap. */
501 r = make_text_item (state, _("Please contact us to correct mistakes."),
502 3000);
503 set_text_motion (r, 0.5, 0.9, 0.5, 0.1);
504 APPENDR (r);
506 state->now -= 2000; /* Overlap. */
508 r = make_text_item (state, _("Report problems at http://bugzilla.gnome.org"), 3000);
509 set_text_motion (r, 0.5, 0.9, 0.5, 0.1);
510 APPENDR (r);
512 r = make_text_item (state, _("We aim to please!"), 3000);
513 r->fade_out = FALSE;
514 APPENDR (r);
516 state->now -= 100; /* Overlap. */
518 r = make_text_item (state, _("We aim to please!"), 1000);
519 r->fade_in = FALSE;
520 set_text_expansion (r, 4);
521 APPENDR (r);
523 state->now = 0;
526 #undef APPENDR
528 void
529 dialog_about (WBCGtk *wbcg)
531 GtkWidget *w, *c;
532 GList *children;
533 AboutState *state;
535 if (gnm_dialog_raise_if_exists (wbcg, ABOUT_KEY))
536 return;
538 state = g_new0 (AboutState, 1);
540 w = g_object_new (GTK_TYPE_ABOUT_DIALOG,
541 "title", _("About Gnumeric"),
542 "version", GNM_VERSION_FULL,
543 "website", PACKAGE_URL,
544 "website-label", _("Visit the Gnumeric website"),
545 "logo-icon-name", "gnumeric",
546 "copyright", _("Copyright \xc2\xa9 1998-2018"),
547 "comments", _("Free, Fast, Accurate - Pick Any Three!"),
548 NULL);
549 state->dialog = w;
551 g_signal_connect (w, "response",
552 G_CALLBACK (gtk_widget_destroy), NULL);
553 g_signal_connect_swapped (w, "destroy",
554 G_CALLBACK (free_state), state);
556 c = gtk_dialog_get_content_area (GTK_DIALOG (w));
557 children = gtk_container_get_children (GTK_CONTAINER (c));
559 if (children && GTK_IS_BOX (children->data)) {
560 GtkWidget *vbox = children->data;
561 int height;
562 PangoLayout *layout;
564 state->anim_area = gtk_drawing_area_new ();
565 layout = gtk_widget_create_pango_layout (state->anim_area, "x");
566 pango_layout_get_pixel_size (layout, NULL, &height);
567 gtk_widget_set_size_request (state->anim_area, -1, 8 * height);
568 g_object_unref (layout);
570 g_signal_connect (state->anim_area, "draw",
571 G_CALLBACK (about_dialog_anim_draw),
572 state);
574 gtk_box_pack_end (GTK_BOX (vbox), state->anim_area, TRUE, TRUE, 0);
576 create_animation (state);
578 state->timer = g_timeout_add (TIME_SLICE, about_dialog_timer, state);
580 g_list_free (children);
582 gnm_keyed_dialog (wbcg, GTK_WINDOW (w), ABOUT_KEY);
583 gtk_widget_show_all (w);