1 /*
2 * Animation Playback plug-in version 0.99.1
3 *
4 * (c) Adam D. Moss : 1997-2000 : adam@gimp.org : adam@foxbox.org
5 * (c) Mircea Purdea : 2009 : someone_else@exhalus.net
6 * (c) Jehan : 2012 : jehan at girinstud.io
7 *
8 * GIMP - The GNU Image Manipulation Program
9 * Copyright (C) 1995 Spencer Kimball and Peter Mattis
10 *
11 * This program is free software: you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License as published by
13 * the Free Software Foundation; either version 3 of the License, or
14 * (at your option) any later version.
15 *
16 * This program is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 * GNU General Public License for more details.
20 *
21 * You should have received a copy of the GNU General Public License
22 * along with this program. If not, see <https://www.gnu.org/licenses/>.
23 */
24
25 /*
26 * TODO:
27 * pdb interface - should we bother?
28 *
29 * speedups (caching? most bottlenecks seem to be in pixelrgns)
30 * -> do pixelrgns properly!
31 */
32
33 #include "config.h"
34
35 #include <string.h>
36
37 #include <libgimp/gimp.h>
38 #undef GDK_DISABLE_DEPRECATED
39 #include <libgimp/gimpui.h>
40
41 #include "libgimp/stdplugins-intl.h"
42
43
44 #define PLUG_IN_PROC "plug-in-animationplay"
45 #define PLUG_IN_BINARY "animation-play"
46 #define PLUG_IN_ROLE "gimp-animation-play"
47 #define DITHERTYPE GDK_RGB_DITHER_NORMAL
48
49
50 typedef enum
51 {
52 DISPOSE_COMBINE = 0x00,
53 DISPOSE_REPLACE = 0x01
54 } DisposeType;
55
56 typedef struct
57 {
58 gint duration_index;
59 DisposeType default_frame_disposal;
60 guint32 default_frame_duration;
61 }
62 AnimationSettings;
63
64 /* for shaping */
65 typedef struct
66 {
67 gint x, y;
68 } CursorOffset;
69
70 /* Declare local functions. */
71 static void query (void);
72 static void run (const gchar *name,
73 gint nparams,
74 const GimpParam *param,
75 gint *nreturn_vals,
76 GimpParam **return_vals);
77
78 static void initialize (void);
79 static void build_dialog (gchar *imagename);
80 static void refresh_dialog (gchar *imagename);
81
82 static void da_size_callback (GtkWidget *widget,
83 GtkAllocation *allocation, void *data);
84 static void sda_size_callback (GtkWidget *widget,
85 GtkAllocation *allocation, void *data);
86
87 static void window_destroy (GtkWidget *widget);
88 static void play_callback (GtkToggleAction *action);
89 static void step_back_callback (GtkAction *action);
90 static void step_callback (GtkAction *action);
91 static void refresh_callback (GtkAction *action);
92 static void rewind_callback (GtkAction *action);
93 static void speed_up_callback (GtkAction *action);
94 static void speed_down_callback (GtkAction *action);
95 static void speed_reset_callback (GtkAction *action);
96 static void framecombo_changed (GtkWidget *combo,
97 gpointer data);
98 static void speedcombo_changed (GtkWidget *combo,
99 gpointer data);
100 static void fpscombo_changed (GtkWidget *combo,
101 gpointer data);
102 static void zoomcombo_activated (GtkEntry *combo,
103 gpointer data);
104 static void zoomcombo_changed (GtkWidget *combo,
105 gpointer data);
106 static gboolean repaint_sda (GtkWidget *darea,
107 GdkEventExpose *event,
108 gpointer data);
109 static gboolean repaint_da (GtkWidget *darea,
110 GdkEventExpose *event,
111 gpointer data);
112
113 static void init_frames (void);
114 static void render_frame (gint32 whichframe);
115 static void show_frame (void);
116 static void total_alpha_preview (void);
117 static void update_alpha_preview (void);
118 static void update_combobox (void);
119 static gdouble get_duration_factor (gint index);
120 static gint get_fps (gint index);
121 static gdouble get_scale (gint index);
122 static void update_scale (gdouble scale);
123
124
125 /* tag util functions*/
126 static gint parse_ms_tag (const gchar *str);
127 static DisposeType parse_disposal_tag (const gchar *str);
128 static gboolean is_disposal_tag (const gchar *str,
129 DisposeType *disposal,
130 gint *taglength);
131 static gboolean is_ms_tag (const gchar *str,
132 gint *duration,
133 gint *taglength);
134
135
136 const GimpPlugInInfo PLUG_IN_INFO =
137 {
138 NULL, /* init_proc */
139 NULL, /* quit_proc */
140 query, /* query_proc */
141 run, /* run_proc */
142 };
143
144
145 /* Global widgets'n'stuff */
146 static GtkWidget *window = NULL;
147 static GdkWindow *root_win = NULL;
148 static GtkUIManager *ui_manager = NULL;
149 static GtkWidget *progress;
150 static GtkWidget *speedcombo = NULL;
151 static GtkWidget *fpscombo = NULL;
152 static GtkWidget *zoomcombo = NULL;
153 static GtkWidget *frame_disposal_combo = NULL;
154
155 static gint32 image_id;
156 static guint width = -1,
157 height = -1;
158 static gint32 *layers = NULL;
159 static gint32 total_layers = 0;
160
161 static GtkWidget *drawing_area = NULL;
162 static guchar *drawing_area_data = NULL;
163 static guint drawing_area_width = -1,
164 drawing_area_height = -1;
165 static guchar *preview_alpha1_data = NULL;
166 static guchar *preview_alpha2_data = NULL;
167
168 static GtkWidget *shape_window = NULL;
169 static GtkWidget *shape_drawing_area = NULL;
170 static guchar *shape_drawing_area_data = NULL;
171 static guint shape_drawing_area_width = -1,
172 shape_drawing_area_height = -1;
173 static gchar *shape_preview_mask = NULL;
174
175
176 static gint32 total_frames = 0;
177 static gint32 *frames = NULL;
178 static guchar *rawframe = NULL;
179 static guint32 *frame_durations = NULL;
180 static guint frame_number = 0;
181
182 static gboolean playing = FALSE;
183 static guint timer = 0;
184 static gboolean detached = FALSE;
185 static gdouble scale, shape_scale;
186
187 /* Default settings. */
188 static AnimationSettings settings =
189 {
190 3,
191 DISPOSE_COMBINE,
192 100 /* ms */
193 };
194
195 static gint32 frames_image_id = 0;
196
MAIN()197 MAIN ()
198
199 static void
200 query (void)
201 {
202 static const GimpParamDef args[] =
203 {
204 { GIMP_PDB_INT32, "run-mode", "The run mode { RUN-INTERACTIVE (0), RUN-NONINTERACTIVE (1) }" },
205 { GIMP_PDB_IMAGE, "image", "Input image" },
206 { GIMP_PDB_DRAWABLE, "drawable", "Input drawable (unused)" }
207 };
208
209 gimp_install_procedure (PLUG_IN_PROC,
210 N_("Preview a GIMP layer-based animation"),
211 "",
212 "Adam D. Moss <adam@gimp.org>",
213 "Adam D. Moss <adam@gimp.org>",
214 "1997, 1998...",
215 N_("_Playback..."),
216 "RGB*, INDEXED*, GRAY*",
217 GIMP_PLUGIN,
218 G_N_ELEMENTS (args), 0,
219 args, NULL);
220
221 gimp_plugin_menu_register (PLUG_IN_PROC, "<Image>/Filters/Animation");
222 gimp_plugin_icon_register (PLUG_IN_PROC, GIMP_ICON_TYPE_ICON_NAME,
223 (const guint8 *) "media-playback-start");
224 }
225
226 static void
run(const gchar * name,gint n_params,const GimpParam * param,gint * nreturn_vals,GimpParam ** return_vals)227 run (const gchar *name,
228 gint n_params,
229 const GimpParam *param,
230 gint *nreturn_vals,
231 GimpParam **return_vals)
232 {
233 static GimpParam values[1];
234 GimpRunMode run_mode;
235 GimpPDBStatusType status = GIMP_PDB_SUCCESS;
236
237 INIT_I18N ();
238 gegl_init (NULL, NULL);
239
240 *nreturn_vals = 1;
241 *return_vals = values;
242
243 run_mode = param[0].data.d_int32;
244
245 if (run_mode == GIMP_RUN_NONINTERACTIVE && n_params != 3)
246 {
247 status = GIMP_PDB_CALLING_ERROR;
248 }
249
250 if (status == GIMP_PDB_SUCCESS)
251 {
252 gimp_get_data (PLUG_IN_PROC, &settings);
253 image_id = param[1].data.d_image;
254
255 initialize ();
256 gtk_main ();
257 gimp_set_data (PLUG_IN_PROC, &settings, sizeof (settings));
258
259 if (run_mode != GIMP_RUN_NONINTERACTIVE)
260 gimp_displays_flush ();
261 }
262
263 values[0].type = GIMP_PDB_STATUS;
264 values[0].data.d_status = status;
265
266 gimp_image_delete (frames_image_id);
267 gegl_exit ();
268 }
269
270 static void
reshape_from_bitmap(const gchar * bitmap)271 reshape_from_bitmap (const gchar *bitmap)
272 {
273 static gchar *prev_bitmap = NULL;
274 static guint prev_width = -1;
275 static guint prev_height = -1;
276
277 if ((!prev_bitmap) ||
278 prev_width != shape_drawing_area_width || prev_height != shape_drawing_area_height ||
279 (memcmp (prev_bitmap, bitmap, (shape_drawing_area_width * shape_drawing_area_height) / 8 + shape_drawing_area_height)))
280 {
281 GdkBitmap *shape_mask;
282
283 shape_mask = gdk_bitmap_create_from_data (gtk_widget_get_window (shape_window),
284 bitmap,
285 shape_drawing_area_width, shape_drawing_area_height);
286 gtk_widget_shape_combine_mask (shape_window, shape_mask, 0, 0);
287 g_object_unref (shape_mask);
288
289 if (!prev_bitmap || prev_width != shape_drawing_area_width || prev_height != shape_drawing_area_height)
290 {
291 g_free(prev_bitmap);
292 prev_bitmap = g_malloc ((shape_drawing_area_width * shape_drawing_area_height) / 8 + shape_drawing_area_height);
293 prev_width = shape_drawing_area_width;
294 prev_height = shape_drawing_area_height;
295 }
296
297 memcpy (prev_bitmap, bitmap, (shape_drawing_area_width * shape_drawing_area_height) / 8 + shape_drawing_area_height);
298 }
299 }
300
301 static gboolean
popup_menu(GtkWidget * widget,GdkEventButton * event)302 popup_menu (GtkWidget *widget,
303 GdkEventButton *event)
304 {
305 GtkWidget *menu = gtk_ui_manager_get_widget (ui_manager, "/anim-play-popup");
306
307 gtk_menu_set_screen (GTK_MENU (menu), gtk_widget_get_screen (widget));
308 gtk_menu_popup (GTK_MENU (menu),
309 NULL, NULL, NULL, NULL,
310 event ? event->button : 0,
311 event ? event->time : gtk_get_current_event_time ());
312
313 return TRUE;
314 }
315
316 static gboolean
button_press(GtkWidget * widget,GdkEventButton * event)317 button_press (GtkWidget *widget,
318 GdkEventButton *event)
319 {
320 if (gdk_event_triggers_context_menu ((GdkEvent *) event))
321 return popup_menu (widget, event);
322
323 return FALSE;
324 }
325
326 /*
327 * Update the actual drawing area metrics, which may be different as requested,
328 * because there is no full control of the WM.
329 * data is always NULL. */
330 static void
da_size_callback(GtkWidget * widget,GtkAllocation * allocation,void * data)331 da_size_callback (GtkWidget *widget,
332 GtkAllocation *allocation, void *data)
333 {
334 if (allocation->width == drawing_area_width && allocation->height == drawing_area_height)
335 return;
336
337 drawing_area_width = allocation->width;
338 drawing_area_height = allocation->height;
339 scale = MIN ((gdouble) drawing_area_width / (gdouble) width, (gdouble) drawing_area_height / (gdouble) height);
340
341 g_free (drawing_area_data);
342 drawing_area_data = g_malloc (drawing_area_width * drawing_area_height * 3);
343
344 update_alpha_preview ();
345
346 if (! detached)
347 {
348 /* Update the zoom information. */
349 GtkEntry *zoomcombo_text_child;
350
351 zoomcombo_text_child = GTK_ENTRY (gtk_bin_get_child (GTK_BIN (zoomcombo)));
352 if (zoomcombo_text_child)
353 {
354 char* new_entry_text = g_strdup_printf (_("%.1f %%"), scale * 100.0);
355
356 gtk_entry_set_text (zoomcombo_text_child, new_entry_text);
357 g_free (new_entry_text);
358 }
359
360 /* Update the rawframe. */
361 g_free (rawframe);
362 rawframe = g_malloc ((unsigned long) drawing_area_width * drawing_area_height * 4);
363
364 /* As we re-allocated the drawn data, let's render it again. */
365 if (frame_number < total_frames)
366 render_frame (frame_number);
367 }
368 else
369 {
370 /* Set "alpha grid" background. */
371 total_alpha_preview ();
372 repaint_da(drawing_area, NULL, NULL);
373 }
374 }
375
376 /*
377 * Update the actual shape drawing area metrics, which may be different as requested,
378 * They *should* be the same as the drawing area, but the safe way is to make sure
379 * and process it separately.
380 * data is always NULL. */
381 static void
sda_size_callback(GtkWidget * widget,GtkAllocation * allocation,void * data)382 sda_size_callback (GtkWidget *widget,
383 GtkAllocation *allocation, void *data)
384 {
385 if (allocation->width == shape_drawing_area_width && allocation->height == shape_drawing_area_height)
386 return;
387
388 shape_drawing_area_width = allocation->width;
389 shape_drawing_area_height = allocation->height;
390 shape_scale = MIN ((gdouble) shape_drawing_area_width / (gdouble) width, (gdouble) shape_drawing_area_height / (gdouble) height);
391
392 g_free (shape_drawing_area_data);
393 g_free (shape_preview_mask);
394
395 shape_drawing_area_data = g_malloc (shape_drawing_area_width * shape_drawing_area_height * 3);
396 shape_preview_mask = g_malloc ((shape_drawing_area_width * shape_drawing_area_height) / 8 + 1 + shape_drawing_area_height);
397
398 if (detached)
399 {
400 /* Update the zoom information. */
401 GtkEntry *zoomcombo_text_child;
402
403 zoomcombo_text_child = GTK_ENTRY (gtk_bin_get_child (GTK_BIN (zoomcombo)));
404 if (zoomcombo_text_child)
405 {
406 char* new_entry_text = g_strdup_printf (_("%.1f %%"), shape_scale * 100.0);
407
408 gtk_entry_set_text (zoomcombo_text_child, new_entry_text);
409 g_free (new_entry_text);
410 }
411
412 /* Update the rawframe. */
413 g_free (rawframe);
414 rawframe = g_malloc ((unsigned long) shape_drawing_area_width * shape_drawing_area_height * 4);
415
416 if (frame_number < total_frames)
417 render_frame (frame_number);
418 }
419 }
420
421 static gboolean
shape_pressed(GtkWidget * widget,GdkEventButton * event)422 shape_pressed (GtkWidget *widget,
423 GdkEventButton *event)
424 {
425 if (button_press (widget, event))
426 return TRUE;
427
428 /* ignore double and triple click */
429 if (event->type == GDK_BUTTON_PRESS)
430 {
431 CursorOffset *p = g_object_get_data (G_OBJECT(widget), "cursor-offset");
432
433 if (!p)
434 return FALSE;
435
436 p->x = (gint) event->x;
437 p->y = (gint) event->y;
438
439 gtk_grab_add (widget);
440 gdk_pointer_grab (gtk_widget_get_window (widget), TRUE,
441 GDK_BUTTON_RELEASE_MASK |
442 GDK_BUTTON_MOTION_MASK |
443 GDK_POINTER_MOTION_HINT_MASK,
444 NULL, NULL, 0);
445 gdk_window_raise (gtk_widget_get_window (widget));
446 }
447
448 return FALSE;
449 }
450
451 static gboolean
shape_released(GtkWidget * widget)452 shape_released (GtkWidget *widget)
453 {
454 gtk_grab_remove (widget);
455 gdk_display_pointer_ungrab (gtk_widget_get_display (widget), 0);
456 gdk_flush ();
457
458 return FALSE;
459 }
460
461 static gboolean
shape_motion(GtkWidget * widget,GdkEventMotion * event)462 shape_motion (GtkWidget *widget,
463 GdkEventMotion *event)
464 {
465 GdkModifierType mask;
466 gint xp, yp;
467
468 gdk_window_get_pointer (root_win, &xp, &yp, &mask);
469
470 /* if a button is still held by the time we process this event... */
471 if (mask & GDK_BUTTON1_MASK)
472 {
473 CursorOffset *p = g_object_get_data (G_OBJECT (widget), "cursor-offset");
474
475 if (!p)
476 return FALSE;
477
478 gtk_window_move (GTK_WINDOW (widget), xp - p->x, yp - p->y);
479 }
480 else /* the user has released all buttons */
481 {
482 shape_released (widget);
483 }
484
485 return FALSE;
486 }
487
488 static gboolean
repaint_da(GtkWidget * darea,GdkEventExpose * event,gpointer data)489 repaint_da (GtkWidget *darea,
490 GdkEventExpose *event,
491 gpointer data)
492 {
493 GtkStyle *style = gtk_widget_get_style (darea);
494
495 gdk_draw_rgb_image (gtk_widget_get_window (darea),
496 style->white_gc,
497 (gint) ((drawing_area_width - scale * width) / 2),
498 (gint) ((drawing_area_height - scale * height) / 2),
499 drawing_area_width, drawing_area_height,
500 (total_frames == 1) ? GDK_RGB_DITHER_MAX : DITHERTYPE,
501 drawing_area_data, drawing_area_width * 3);
502
503 return TRUE;
504 }
505
506 static gboolean
repaint_sda(GtkWidget * darea,GdkEventExpose * event,gpointer data)507 repaint_sda (GtkWidget *darea,
508 GdkEventExpose *event,
509 gpointer data)
510 {
511 GtkStyle *style = gtk_widget_get_style (darea);
512
513 gdk_draw_rgb_image (gtk_widget_get_window (darea),
514 style->white_gc,
515 (gint) ((shape_drawing_area_width - shape_scale * width) / 2),
516 (gint) ((shape_drawing_area_height - shape_scale * height) / 2),
517 shape_drawing_area_width, shape_drawing_area_height,
518 (total_frames == 1) ? GDK_RGB_DITHER_MAX : DITHERTYPE,
519 shape_drawing_area_data, shape_drawing_area_width * 3);
520
521 return TRUE;
522 }
523
524 static void
close_callback(GtkAction * action,gpointer data)525 close_callback (GtkAction *action,
526 gpointer data)
527 {
528 gtk_widget_destroy (GTK_WIDGET (data));
529 }
530
531 static void
help_callback(GtkAction * action,gpointer data)532 help_callback (GtkAction *action,
533 gpointer data)
534 {
535 gimp_standard_help_func (PLUG_IN_PROC, data);
536 }
537
538
539 static void
detach_callback(GtkToggleAction * action)540 detach_callback (GtkToggleAction *action)
541 {
542 gboolean active = gtk_toggle_action_get_active (action);
543
544 if (active == detached)
545 {
546 g_warning ("detached state and toggle action got out of sync");
547 return;
548 }
549
550 detached = active;
551
552 if (detached)
553 {
554 gint x, y;
555
556 /* Create a total-alpha buffer merely for the not-shaped
557 drawing area to now display. */
558
559 gtk_window_set_screen (GTK_WINDOW (shape_window),
560 gtk_widget_get_screen (drawing_area));
561
562 gtk_widget_show (shape_window);
563
564 if (!gtk_widget_get_realized (drawing_area))
565 gtk_widget_realize (drawing_area);
566 if (!gtk_widget_get_realized (shape_drawing_area))
567 gtk_widget_realize (shape_drawing_area);
568
569 gdk_window_get_origin (gtk_widget_get_window (drawing_area), &x, &y);
570
571 gtk_window_move (GTK_WINDOW (shape_window), x + 6, y + 6);
572
573 gdk_window_set_back_pixmap (gtk_widget_get_window (shape_drawing_area), NULL, TRUE);
574
575
576 /* Set "alpha grid" background. */
577 total_alpha_preview ();
578 repaint_da(drawing_area, NULL, NULL);
579 }
580 else
581 gtk_widget_hide (shape_window);
582
583 render_frame (frame_number);
584 }
585
586 static GtkUIManager *
ui_manager_new(GtkWidget * window)587 ui_manager_new (GtkWidget *window)
588 {
589 static GtkActionEntry actions[] =
590 {
591 { "step-back", "media-skip-backward",
592 N_("Step _back"), "d", N_("Step back to previous frame"),
593 G_CALLBACK (step_back_callback) },
594
595 { "step", "media-skip-forward",
596 N_("_Step"), "f", N_("Step to next frame"),
597 G_CALLBACK (step_callback) },
598
599 { "rewind", "media-seek-backward",
600 NULL, NULL, N_("Rewind the animation"),
601 G_CALLBACK (rewind_callback) },
602
603 { "refresh", GIMP_ICON_VIEW_REFRESH,
604 NULL, "<control>R", N_("Reload the image"),
605 G_CALLBACK (refresh_callback) },
606
607 { "help", "help-browser",
608 NULL, NULL, NULL,
609 G_CALLBACK (help_callback) },
610
611 { "close", "window-close",
612 NULL, "<control>W", NULL,
613 G_CALLBACK (close_callback)
614 },
615 {
616 "quit", "application-quit",
617 NULL, "<control>Q", NULL,
618 G_CALLBACK (close_callback)
619 },
620 {
621 "speed-up", NULL,
622 N_("Faster"), "<control>L", N_("Increase the speed of the animation"),
623 G_CALLBACK (speed_up_callback)
624 },
625 {
626 "speed-down", NULL,
627 N_("Slower"), "<control>J", N_("Decrease the speed of the animation"),
628 G_CALLBACK (speed_down_callback)
629 },
630 {
631 "speed-reset", NULL,
632 N_("Reset speed"), "<control>K", N_("Reset the speed of the animation"),
633 G_CALLBACK (speed_reset_callback)
634 }
635 };
636
637 static GtkToggleActionEntry toggle_actions[] =
638 {
639 { "play", "media-playback-start",
640 NULL, "space", N_("Start playback"),
641 G_CALLBACK (play_callback), FALSE },
642
643 { "detach", GIMP_ICON_DETACH,
644 N_("Detach"), NULL,
645 N_("Detach the animation from the dialog window"),
646 G_CALLBACK (detach_callback), FALSE }
647 };
648
649 GtkUIManager *ui_manager = gtk_ui_manager_new ();
650 GtkActionGroup *group = gtk_action_group_new ("Actions");
651 GError *error = NULL;
652
653 gtk_action_group_set_translation_domain (group, NULL);
654
655 gtk_action_group_add_actions (group,
656 actions,
657 G_N_ELEMENTS (actions),
658 window);
659 gtk_action_group_add_toggle_actions (group,
660 toggle_actions,
661 G_N_ELEMENTS (toggle_actions),
662 NULL);
663
664 gtk_window_add_accel_group (GTK_WINDOW (window),
665 gtk_ui_manager_get_accel_group (ui_manager));
666 gtk_accel_group_lock (gtk_ui_manager_get_accel_group (ui_manager));
667
668 gtk_ui_manager_insert_action_group (ui_manager, group, -1);
669 g_object_unref (group);
670
671 gtk_ui_manager_add_ui_from_string (ui_manager,
672 "<ui>"
673 " <toolbar name=\"anim-play-toolbar\">"
674 " <toolitem action=\"play\" />"
675 " <toolitem action=\"step-back\" />"
676 " <toolitem action=\"step\" />"
677 " <toolitem action=\"rewind\" />"
678 " <separator />"
679 " <toolitem action=\"detach\" />"
680 " <toolitem action=\"refresh\" />"
681 " <separator name=\"space\" />"
682 " <toolitem action=\"help\" />"
683 " </toolbar>"
684 " <accelerator action=\"close\" />"
685 " <accelerator action=\"quit\" />"
686 "</ui>",
687 -1, &error);
688
689 if (error)
690 {
691 g_warning ("error parsing ui: %s", error->message);
692 g_clear_error (&error);
693 }
694
695 gtk_ui_manager_add_ui_from_string (ui_manager,
696 "<ui>"
697 " <popup name=\"anim-play-popup\">"
698 " <menuitem action=\"play\" />"
699 " <menuitem action=\"step-back\" />"
700 " <menuitem action=\"step\" />"
701 " <menuitem action=\"rewind\" />"
702 " <separator />"
703 " <menuitem action=\"speed-down\" />"
704 " <menuitem action=\"speed-up\" />"
705 " <menuitem action=\"speed-reset\" />"
706 " <separator />"
707 " <menuitem action=\"detach\" />"
708 " <menuitem action=\"refresh\" />"
709 " <menuitem action=\"close\" />"
710 " </popup>"
711 "</ui>",
712 -1, &error);
713
714 if (error)
715 {
716 g_warning ("error parsing ui: %s", error->message);
717 g_clear_error (&error);
718 }
719
720 return ui_manager;
721 }
722
723 static void
refresh_dialog(gchar * imagename)724 refresh_dialog (gchar *imagename)
725 {
726 gchar *name;
727 GdkScreen *screen;
728 guint screen_width, screen_height;
729 gint window_width, window_height;
730
731 /* Image Name */
732 name = g_strconcat (_("Animation Playback:"), " ", imagename, NULL);
733 gtk_window_set_title (GTK_WINDOW (window), name);
734 g_free (name);
735
736 /* Update GUI size. */
737 screen = gtk_widget_get_screen (window);
738 screen_height = gdk_screen_get_height (screen);
739 screen_width = gdk_screen_get_width (screen);
740 gtk_window_get_size (GTK_WINDOW (window), &window_width, &window_height);
741
742 /* if the *window* size is bigger than the screen size,
743 * diminish the drawing area by as much, then compute the corresponding scale. */
744 if (window_width + 50 > screen_width || window_height + 50 > screen_height)
745 {
746 guint expected_drawing_area_width = MAX (1, width - window_width + screen_width);
747 guint expected_drawing_area_height = MAX (1, height - window_height + screen_height);
748 gdouble expected_scale = MIN ((gdouble) expected_drawing_area_width / (gdouble) width,
749 (gdouble) expected_drawing_area_height / (gdouble) height);
750 update_scale (expected_scale);
751
752 /* There is unfortunately no good way to know the size of the decorations, taskbars, etc.
753 * So we take a wild guess by making the window slightly smaller to fit into any case. */
754 gtk_window_set_default_size (GTK_WINDOW (window),
755 MIN (expected_drawing_area_width + 20, screen_width - 60),
756 MIN (expected_drawing_area_height + 90, screen_height - 60));
757
758 gtk_window_reshow_with_initial_size (GTK_WINDOW (window));
759 }
760 }
761
762 static void
build_dialog(gchar * imagename)763 build_dialog (gchar *imagename)
764 {
765 GtkWidget *toolbar;
766 GtkWidget *frame;
767 GtkWidget *viewport;
768 GtkWidget *main_vbox;
769 GtkWidget *vbox;
770 GtkWidget *hbox;
771 GtkWidget *abox;
772 GtkToolItem *item;
773 GtkAction *action;
774 GdkCursor *cursor;
775 gint index;
776 gchar *text;
777
778 gimp_ui_init (PLUG_IN_BINARY, TRUE);
779
780
781 window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
782 gtk_window_set_role (GTK_WINDOW (window), "animation-playback");
783
784 g_signal_connect (window, "destroy",
785 G_CALLBACK (window_destroy),
786 NULL);
787 g_signal_connect (window, "popup-menu",
788 G_CALLBACK (popup_menu),
789 NULL);
790
791 gimp_help_connect (window, gimp_standard_help_func, PLUG_IN_PROC, NULL);
792
793 ui_manager = ui_manager_new (window);
794
795 main_vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
796 gtk_container_add (GTK_CONTAINER (window), main_vbox);
797 gtk_widget_show (main_vbox);
798
799 toolbar = gtk_ui_manager_get_widget (ui_manager, "/anim-play-toolbar");
800 gtk_box_pack_start (GTK_BOX (main_vbox), toolbar, FALSE, FALSE, 0);
801 gtk_widget_show (toolbar);
802
803 item =
804 GTK_TOOL_ITEM (gtk_ui_manager_get_widget (ui_manager,
805 "/anim-play-toolbar/space"));
806 gtk_separator_tool_item_set_draw (GTK_SEPARATOR_TOOL_ITEM (item), FALSE);
807 gtk_tool_item_set_expand (item, TRUE);
808
809 vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 2);
810 gtk_box_pack_start (GTK_BOX (main_vbox), vbox, TRUE, TRUE, 0);
811 gtk_widget_show (vbox);
812
813 /* Alignment for the scrolling window, which can be resized by the user. */
814 abox = gtk_alignment_new (0.5, 0.5, 1.0, 1.0);
815 gtk_box_pack_start (GTK_BOX (vbox), abox, TRUE, TRUE, 0);
816 gtk_widget_show (abox);
817
818 frame = gtk_scrolled_window_new (NULL, NULL);
819 gtk_container_add (GTK_CONTAINER (abox), frame);
820 gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (frame),
821 GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
822 gtk_widget_show (frame);
823
824 viewport = gtk_viewport_new (NULL, NULL);
825 gtk_container_add (GTK_CONTAINER (frame), viewport);
826 gtk_widget_show (viewport);
827
828 /* I add the drawing area inside an alignment box to prevent it from being resized. */
829 abox = gtk_alignment_new (0.5, 0.5, 0.0, 0.0);
830 gtk_container_add (GTK_CONTAINER (viewport), abox);
831 gtk_widget_show (abox);
832
833 /* Build a drawing area, with a default size same as the image */
834 drawing_area = gtk_drawing_area_new ();
835 gtk_widget_add_events (drawing_area, GDK_BUTTON_PRESS_MASK);
836 gtk_container_add (GTK_CONTAINER (abox), drawing_area);
837 gtk_widget_show (drawing_area);
838
839 g_signal_connect (drawing_area, "size-allocate",
840 G_CALLBACK(da_size_callback),
841 NULL);
842 g_signal_connect (drawing_area, "button-press-event",
843 G_CALLBACK (button_press),
844 NULL);
845
846 /* Lower option bar. */
847
848 hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 2);
849 gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0);
850 gtk_widget_show (hbox);
851
852 /* Progress bar. */
853
854 progress = gtk_progress_bar_new ();
855 gtk_box_pack_end (GTK_BOX (hbox), progress, TRUE, TRUE, 0);
856 gtk_widget_show (progress);
857
858 /* Zoom */
859 zoomcombo = gtk_combo_box_text_new_with_entry ();
860 gtk_box_pack_end (GTK_BOX (hbox), zoomcombo, FALSE, FALSE, 0);
861 gtk_widget_show (zoomcombo);
862 for (index = 0; index < 5; index++)
863 {
864 /* list is given in "fps" - frames per second */
865 text = g_strdup_printf (_("%.1f %%"), get_scale (index) * 100.0);
866 gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (zoomcombo), text);
867 g_free (text);
868 }
869
870 gtk_combo_box_set_active (GTK_COMBO_BOX (zoomcombo), 2); /* 1.0 by default. */
871
872 g_signal_connect (GTK_ENTRY (gtk_bin_get_child (GTK_BIN (zoomcombo))),
873 "activate",
874 G_CALLBACK (zoomcombo_activated),
875 NULL);
876 g_signal_connect (zoomcombo, "changed",
877 G_CALLBACK (zoomcombo_changed),
878 NULL);
879
880 gimp_help_set_help_data (zoomcombo, _("Zoom"), NULL);
881
882 /* fps combo */
883 fpscombo = gtk_combo_box_text_new ();
884 gtk_box_pack_end (GTK_BOX (hbox), fpscombo, FALSE, FALSE, 0);
885 gtk_widget_show (fpscombo);
886
887 for (index = 0; index < 9; index++)
888 {
889 /* list is given in "fps" - frames per second */
890 text = g_strdup_printf (_("%d fps"), get_fps (index));
891 gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (fpscombo), text);
892 g_free (text);
893 if (settings.default_frame_duration == 1000 / get_fps(index))
894 gtk_combo_box_set_active (GTK_COMBO_BOX (fpscombo), index);
895 }
896
897 g_signal_connect (fpscombo, "changed",
898 G_CALLBACK (fpscombo_changed),
899 NULL);
900
901 gimp_help_set_help_data (fpscombo, _("Default framerate"), NULL);
902
903 /* Speed Combo */
904 speedcombo = gtk_combo_box_text_new ();
905 gtk_box_pack_end (GTK_BOX (hbox), speedcombo, FALSE, FALSE, 0);
906 gtk_widget_show (speedcombo);
907
908 for (index = 0; index < 7; index++)
909 {
910 text = g_strdup_printf ("%g\303\227", (100 / get_duration_factor (index)) / 100);
911 gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (speedcombo), text);
912 g_free (text);
913 }
914
915 gtk_combo_box_set_active (GTK_COMBO_BOX (speedcombo), settings.duration_index);
916
917 g_signal_connect (speedcombo, "changed",
918 G_CALLBACK (speedcombo_changed),
919 NULL);
920
921 gimp_help_set_help_data (speedcombo, _("Playback speed"), NULL);
922
923 action = gtk_ui_manager_get_action (ui_manager,
924 "/anim-play-popup/speed-reset");
925 gtk_action_set_sensitive (action, FALSE);
926
927 /* Set up the frame disposal combo. */
928 frame_disposal_combo = gtk_combo_box_text_new ();
929
930 /* 2 styles of default frame disposals: cumulative layers and one frame per layer. */
931 text = g_strdup (_("Cumulative layers (combine)"));
932 gtk_combo_box_text_insert_text (GTK_COMBO_BOX_TEXT (frame_disposal_combo), DISPOSE_COMBINE, text);
933 g_free (text);
934
935 text = g_strdup (_("One frame per layer (replace)"));
936 gtk_combo_box_text_insert_text (GTK_COMBO_BOX_TEXT (frame_disposal_combo), DISPOSE_REPLACE, text);
937 g_free (text);
938
939 gtk_combo_box_set_active (GTK_COMBO_BOX (frame_disposal_combo), settings.default_frame_disposal);
940
941 g_signal_connect (frame_disposal_combo, "changed",
942 G_CALLBACK (framecombo_changed),
943 NULL);
944
945 gtk_box_pack_end (GTK_BOX (hbox), frame_disposal_combo, FALSE, FALSE, 0);
946 gtk_widget_show (frame_disposal_combo);
947
948 gtk_window_set_resizable (GTK_WINDOW (window), TRUE);
949 gtk_window_set_default_size (GTK_WINDOW (window), width + 20, height + 90);
950 gtk_widget_show (window);
951
952 /* shape_drawing_area for detached feature. */
953 shape_window = gtk_window_new (GTK_WINDOW_POPUP);
954 gtk_window_set_resizable (GTK_WINDOW (shape_window), FALSE);
955
956 shape_drawing_area = gtk_drawing_area_new ();
957 gtk_container_add (GTK_CONTAINER (shape_window), shape_drawing_area);
958 gtk_widget_show (shape_drawing_area);
959 gtk_widget_add_events (shape_drawing_area, GDK_BUTTON_PRESS_MASK);
960 gtk_widget_realize (shape_drawing_area);
961
962 gdk_window_set_back_pixmap (gtk_widget_get_window (shape_window), NULL, FALSE);
963
964 cursor = gdk_cursor_new_for_display (gtk_widget_get_display (shape_window),
965 GDK_HAND2);
966 gdk_window_set_cursor (gtk_widget_get_window (shape_window), cursor);
967 gdk_cursor_unref (cursor);
968
969 g_signal_connect(shape_drawing_area, "size-allocate",
970 G_CALLBACK(sda_size_callback),
971 NULL);
972 g_signal_connect (shape_window, "button-press-event",
973 G_CALLBACK (shape_pressed),
974 NULL);
975 g_signal_connect (shape_window, "button-release-event",
976 G_CALLBACK (shape_released),
977 NULL);
978 g_signal_connect (shape_window, "motion-notify-event",
979 G_CALLBACK (shape_motion),
980 NULL);
981
982 g_object_set_data (G_OBJECT (shape_window),
983 "cursor-offset", g_new0 (CursorOffset, 1));
984
985 g_signal_connect (drawing_area, "expose-event",
986 G_CALLBACK (repaint_da),
987 NULL);
988
989 g_signal_connect (shape_drawing_area, "expose-event",
990 G_CALLBACK (repaint_sda),
991 NULL);
992
993 /* We request a minimum size *after* having connecting the
994 * size-allocate signal for correct initialization. */
995 gtk_widget_set_size_request (drawing_area, width, height);
996 gtk_widget_set_size_request (shape_drawing_area, width, height);
997
998 root_win = gdk_get_default_root_window ();
999 }
1000
1001 static void
init_frames(void)1002 init_frames (void)
1003 {
1004 /* Frames are associated to an unused image. */
1005 gint i;
1006 gint32 new_frame, previous_frame, new_layer;
1007 gboolean animated;
1008 GtkAction *action;
1009 gint duration = 0;
1010 DisposeType disposal = settings.default_frame_disposal;
1011 gchar *layer_name;
1012
1013 total_frames = total_layers;
1014
1015 /* Cleanup before re-generation. */
1016 if (frames)
1017 {
1018 gimp_image_delete (frames_image_id);
1019 g_free (frames);
1020 g_free (frame_durations);
1021 }
1022 frames = g_try_malloc0_n (total_frames, sizeof (gint32));
1023 frame_durations = g_try_malloc0_n (total_frames, sizeof (guint32));
1024 if (! frames || ! frame_durations)
1025 {
1026 gimp_message (_("Memory could not be allocated to the frame container."));
1027 gtk_main_quit ();
1028 gimp_quit ();
1029 return;
1030 }
1031 /* We only use RGB images for display because indexed images would somehow
1032 render terrible colors. Layers from other types will be automatically
1033 converted. */
1034 frames_image_id = gimp_image_new (width, height, GIMP_RGB);
1035 /* Save processing time and memory by not saving history and merged frames. */
1036 gimp_image_undo_disable (frames_image_id);
1037
1038 for (i = 0; i < total_frames; i++)
1039 {
1040 layer_name = gimp_item_get_name (layers[total_layers - (i + 1)]);
1041 if (layer_name)
1042 {
1043 duration = parse_ms_tag (layer_name);
1044 disposal = parse_disposal_tag (layer_name);
1045 g_free (layer_name);
1046 }
1047
1048 if (i > 0 && disposal != DISPOSE_REPLACE)
1049 {
1050 previous_frame = gimp_layer_copy (frames[i - 1]);
1051 gimp_image_insert_layer (frames_image_id, previous_frame, 0, -1);
1052 gimp_item_set_visible (previous_frame, TRUE);
1053 }
1054 new_layer = gimp_layer_new_from_drawable (layers[total_layers - (i + 1)], frames_image_id);
1055 gimp_image_insert_layer (frames_image_id, new_layer, 0, -1);
1056 gimp_item_set_visible (new_layer, TRUE);
1057 new_frame = gimp_image_merge_visible_layers (frames_image_id, GIMP_CLIP_TO_IMAGE);
1058 frames[i] = new_frame;
1059 gimp_item_set_visible (new_frame, FALSE);
1060
1061 if (duration <= 0)
1062 duration = settings.default_frame_duration;
1063 frame_durations[i] = (guint32) duration;
1064 }
1065
1066 /* Update the UI. */
1067 animated = total_frames >= 2;
1068 action = gtk_ui_manager_get_action (ui_manager,
1069 "/ui/anim-play-toolbar/play");
1070 gtk_action_set_sensitive (action, animated);
1071
1072 action = gtk_ui_manager_get_action (ui_manager,
1073 "/ui/anim-play-toolbar/step-back");
1074 gtk_action_set_sensitive (action, animated);
1075
1076 action = gtk_ui_manager_get_action (ui_manager,
1077 "/ui/anim-play-toolbar/step");
1078 gtk_action_set_sensitive (action, animated);
1079
1080 action = gtk_ui_manager_get_action (ui_manager,
1081 "/ui/anim-play-toolbar/rewind");
1082 gtk_action_set_sensitive (action, animated);
1083
1084 /* Keep the same frame number, unless it is now invalid. */
1085 if (frame_number >= total_frames)
1086 frame_number = 0;
1087 }
1088
1089 static void
initialize(void)1090 initialize (void)
1091 {
1092 /* Freeing existing data after a refresh. */
1093 g_free (layers);
1094
1095 /* Catch the case when the user has closed the image in the meantime. */
1096 if (! gimp_image_is_valid (image_id))
1097 {
1098 gimp_message (_("Invalid image. Did you close it?"));
1099 gtk_main_quit ();
1100 return;
1101 }
1102
1103 width = gimp_image_width (image_id);
1104 height = gimp_image_height (image_id);
1105 layers = gimp_image_get_layers (image_id, &total_layers);
1106
1107 if (!window)
1108 build_dialog (gimp_image_get_name (image_id));
1109 refresh_dialog (gimp_image_get_name (image_id));
1110
1111 init_frames ();
1112 render_frame (frame_number);
1113 show_frame ();
1114 }
1115
1116 /* Rendering Functions */
1117
1118 static void
render_frame(gint32 whichframe)1119 render_frame (gint32 whichframe)
1120 {
1121 GeglBuffer *buffer;
1122 gint i, j, k;
1123 guchar *srcptr;
1124 guchar *destptr;
1125 GtkWidget *da;
1126 guint drawing_width, drawing_height;
1127 gdouble drawing_scale;
1128 guchar *preview_data;
1129
1130 g_assert (whichframe < total_frames);
1131
1132 if (detached)
1133 {
1134 da = shape_drawing_area;
1135 preview_data = shape_drawing_area_data;
1136 drawing_width = shape_drawing_area_width;
1137 drawing_height = shape_drawing_area_height;
1138 drawing_scale = shape_scale;
1139 }
1140 else
1141 {
1142 da = drawing_area;
1143 preview_data = drawing_area_data;
1144 drawing_width = drawing_area_width;
1145 drawing_height = drawing_area_height;
1146 drawing_scale = scale;
1147
1148 /* Set "alpha grid" background. */
1149 total_alpha_preview ();
1150 }
1151
1152 buffer = gimp_drawable_get_buffer (frames[whichframe]);
1153
1154 /* Fetch and scale the whole raw new frame */
1155 gegl_buffer_get (buffer, GEGL_RECTANGLE (0, 0, drawing_width, drawing_height),
1156 drawing_scale, babl_format ("R'G'B'A u8"),
1157 rawframe, GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_CLAMP);
1158
1159 /* Number of pixels. */
1160 i = drawing_width * drawing_height;
1161 destptr = preview_data;
1162 srcptr = rawframe;
1163 while (i--)
1164 {
1165 if (! (srcptr[3] & 128))
1166 {
1167 srcptr += 4;
1168 destptr += 3;
1169 continue;
1170 }
1171
1172 *(destptr++) = *(srcptr++);
1173 *(destptr++) = *(srcptr++);
1174 *(destptr++) = *(srcptr++);
1175
1176 srcptr++;
1177 }
1178
1179 /* calculate the shape mask */
1180 if (detached)
1181 {
1182 memset (shape_preview_mask, 0, (drawing_width * drawing_height) / 8 + drawing_height);
1183 srcptr = rawframe + 3;
1184
1185 for (j = 0; j < drawing_height; j++)
1186 {
1187 k = j * ((7 + drawing_width) / 8);
1188
1189 for (i = 0; i < drawing_width; i++)
1190 {
1191 if ((*srcptr) & 128)
1192 shape_preview_mask[k + i/8] |= (1 << (i&7));
1193
1194 srcptr += 4;
1195 }
1196 }
1197 reshape_from_bitmap (shape_preview_mask);
1198 }
1199
1200 /* Display the preview buffer. */
1201 if (gtk_widget_get_realized (da))
1202 gdk_draw_rgb_image (gtk_widget_get_window (da),
1203 (gtk_widget_get_style (da))->white_gc,
1204 (gint) ((drawing_width - drawing_scale * width) / 2),
1205 (gint) ((drawing_height - drawing_scale * height) / 2),
1206 drawing_width, drawing_height,
1207 (total_frames == 1 ?
1208 GDK_RGB_DITHER_MAX : DITHERTYPE),
1209 preview_data, drawing_width * 3);
1210
1211 /* clean up */
1212 g_object_unref (buffer);
1213 }
1214
1215 static void
show_frame(void)1216 show_frame (void)
1217 {
1218 gchar *text;
1219
1220 /* update the dialog's progress bar */
1221 gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (progress),
1222 ((gfloat) frame_number /
1223 (gfloat) (total_frames - 0.999)));
1224
1225 text = g_strdup_printf (_("Frame %d of %d"), frame_number + 1, total_frames);
1226 gtk_progress_bar_set_text (GTK_PROGRESS_BAR (progress), text);
1227 g_free (text);
1228 }
1229
1230 static void
update_alpha_preview(void)1231 update_alpha_preview (void)
1232 {
1233 gint i;
1234
1235 g_free (preview_alpha1_data);
1236 g_free (preview_alpha2_data);
1237
1238 preview_alpha1_data = g_malloc (drawing_area_width * 3);
1239 preview_alpha2_data = g_malloc (drawing_area_width * 3);
1240
1241 for (i = 0; i < drawing_area_width; i++)
1242 {
1243 if (i & 8)
1244 {
1245 preview_alpha1_data[i*3 + 0] =
1246 preview_alpha1_data[i*3 + 1] =
1247 preview_alpha1_data[i*3 + 2] = 102;
1248 preview_alpha2_data[i*3 + 0] =
1249 preview_alpha2_data[i*3 + 1] =
1250 preview_alpha2_data[i*3 + 2] = 154;
1251 }
1252 else
1253 {
1254 preview_alpha1_data[i*3 + 0] =
1255 preview_alpha1_data[i*3 + 1] =
1256 preview_alpha1_data[i*3 + 2] = 154;
1257 preview_alpha2_data[i*3 + 0] =
1258 preview_alpha2_data[i*3 + 1] =
1259 preview_alpha2_data[i*3 + 2] = 102;
1260 }
1261 }
1262 }
1263
1264 static void
total_alpha_preview(void)1265 total_alpha_preview (void)
1266 {
1267 gint i;
1268
1269 for (i = 0; i < drawing_area_height; i++)
1270 {
1271 if (i & 8)
1272 memcpy (&drawing_area_data[i * 3 * drawing_area_width], preview_alpha1_data, 3 * drawing_area_width);
1273 else
1274 memcpy (&drawing_area_data[i * 3 * drawing_area_width], preview_alpha2_data, 3 * drawing_area_width);
1275 }
1276 }
1277
1278 /* Util. */
1279
1280 static void
remove_timer(void)1281 remove_timer (void)
1282 {
1283 if (timer)
1284 {
1285 g_source_remove (timer);
1286 timer = 0;
1287 }
1288 }
1289
1290 static void
do_back_step(void)1291 do_back_step (void)
1292 {
1293 if (frame_number == 0)
1294 frame_number = total_frames - 1;
1295 else
1296 frame_number = (frame_number - 1) % total_frames;
1297 render_frame (frame_number);
1298 }
1299
1300 static void
do_step(void)1301 do_step (void)
1302 {
1303 frame_number = (frame_number + 1) % total_frames;
1304 render_frame (frame_number);
1305 }
1306
1307
1308 /* Callbacks */
1309
1310 static void
window_destroy(GtkWidget * widget)1311 window_destroy (GtkWidget *widget)
1312 {
1313 if (playing)
1314 remove_timer ();
1315
1316 if (shape_window)
1317 gtk_widget_destroy (GTK_WIDGET (shape_window));
1318
1319 gtk_main_quit ();
1320 }
1321
1322
1323 static gint
advance_frame_callback(gpointer data)1324 advance_frame_callback (gpointer data)
1325 {
1326 gdouble duration;
1327
1328 remove_timer();
1329
1330 duration = frame_durations[(frame_number + 1) % total_frames];
1331
1332 timer = g_timeout_add (duration * get_duration_factor (settings.duration_index),
1333 advance_frame_callback, NULL);
1334
1335 do_step ();
1336 show_frame ();
1337
1338 return FALSE;
1339 }
1340
1341
1342 static void
play_callback(GtkToggleAction * action)1343 play_callback (GtkToggleAction *action)
1344 {
1345 if (playing)
1346 remove_timer ();
1347
1348 playing = gtk_toggle_action_get_active (action);
1349
1350 if (playing)
1351 {
1352 timer = g_timeout_add ((gdouble) frame_durations[frame_number] *
1353 get_duration_factor (settings.duration_index),
1354 advance_frame_callback, NULL);
1355
1356 gtk_action_set_icon_name (GTK_ACTION (action), "media-playback-pause");
1357 }
1358 else
1359 {
1360 gtk_action_set_icon_name (GTK_ACTION (action), "media-playback-start");
1361 }
1362
1363 g_object_set (action,
1364 "tooltip", playing ? _("Stop playback") : _("Start playback"),
1365 NULL);
1366 }
1367
1368 static gdouble
get_duration_factor(gint index)1369 get_duration_factor (gint index)
1370 {
1371 switch (index)
1372 {
1373 case 0:
1374 return 0.125;
1375 case 1:
1376 return 0.25;
1377 case 2:
1378 return 0.5;
1379 case 3:
1380 return 1.0;
1381 case 4:
1382 return 2.0;
1383 case 5:
1384 return 4.0;
1385 case 6:
1386 return 8.0;
1387 default:
1388 return 1.0;
1389 }
1390 }
1391
1392 static gint
get_fps(gint index)1393 get_fps (gint index)
1394 {
1395 switch (index)
1396 {
1397 case 0:
1398 return 10;
1399 case 1:
1400 return 12;
1401 case 2:
1402 return 15;
1403 case 3:
1404 return 24;
1405 case 4:
1406 return 25;
1407 case 5:
1408 return 30;
1409 case 6:
1410 return 50;
1411 case 7:
1412 return 60;
1413 case 8:
1414 return 72;
1415 default:
1416 return 10;
1417 }
1418 }
1419
1420 static gdouble
get_scale(gint index)1421 get_scale (gint index)
1422 {
1423 switch (index)
1424 {
1425 case 0:
1426 return 0.51;
1427 case 1:
1428 return 1.0;
1429 case 2:
1430 return 1.25;
1431 case 3:
1432 return 1.5;
1433 case 4:
1434 return 2.0;
1435 default:
1436 {
1437 /* likely -1 returned if there is no active item from the list.
1438 * Try a text conversion, locale-aware in such a case, assuming people write in percent. */
1439 gchar* active_text = gtk_combo_box_text_get_active_text (GTK_COMBO_BOX_TEXT (zoomcombo));
1440 gdouble zoom = g_strtod (active_text, NULL);
1441
1442 /* Negative scales are inconsistent. And we want to avoid huge scaling. */
1443 if (zoom > 400.0)
1444 zoom = 400.0;
1445 else if (zoom <= 50.0)
1446 /* FIXME: scales under 0.5 are broken. See bug 690265. */
1447 zoom = 50.1;
1448 g_free (active_text);
1449 return zoom / 100.0;
1450 }
1451 }
1452 }
1453
1454 static void
step_back_callback(GtkAction * action)1455 step_back_callback (GtkAction *action)
1456 {
1457 if (playing)
1458 gtk_action_activate (gtk_ui_manager_get_action (ui_manager,
1459 "/anim-play-toolbar/play"));
1460 do_back_step();
1461 show_frame();
1462 }
1463
1464 static void
step_callback(GtkAction * action)1465 step_callback (GtkAction *action)
1466 {
1467 if (playing)
1468 gtk_action_activate (gtk_ui_manager_get_action (ui_manager,
1469 "/anim-play-toolbar/play"));
1470 do_step();
1471 show_frame();
1472 }
1473
1474 static void
refresh_callback(GtkAction * action)1475 refresh_callback (GtkAction *action)
1476 {
1477 initialize ();
1478 }
1479
1480 static void
rewind_callback(GtkAction * action)1481 rewind_callback (GtkAction *action)
1482 {
1483 if (playing)
1484 gtk_action_activate (gtk_ui_manager_get_action (ui_manager,
1485 "/anim-play-toolbar/play"));
1486 frame_number = 0;
1487 render_frame (frame_number);
1488 show_frame ();
1489 }
1490
1491 static void
speed_up_callback(GtkAction * action)1492 speed_up_callback (GtkAction *action)
1493 {
1494 if (settings.duration_index > 0)
1495 --settings.duration_index;
1496
1497 gtk_action_set_sensitive (action, settings.duration_index > 0);
1498
1499 action = gtk_ui_manager_get_action (ui_manager,
1500 "/anim-play-popup/speed-reset");
1501 gtk_action_set_sensitive (action, settings.duration_index != 3);
1502
1503 action = gtk_ui_manager_get_action (ui_manager,
1504 "/anim-play-popup/speed-down");
1505 gtk_action_set_sensitive (action, TRUE);
1506
1507 update_combobox ();
1508 }
1509
1510 static void
speed_down_callback(GtkAction * action)1511 speed_down_callback (GtkAction *action)
1512 {
1513 if (settings.duration_index < 6)
1514 ++settings.duration_index;
1515
1516 gtk_action_set_sensitive (action, settings.duration_index < 6);
1517
1518 action = gtk_ui_manager_get_action (ui_manager,
1519 "/anim-play-popup/speed-reset");
1520 gtk_action_set_sensitive (action, settings.duration_index != 3);
1521
1522 action = gtk_ui_manager_get_action (ui_manager,
1523 "/anim-play-popup/speed-up");
1524 gtk_action_set_sensitive (action, TRUE);
1525
1526 update_combobox ();
1527 }
1528
1529 static void
speed_reset_callback(GtkAction * action)1530 speed_reset_callback (GtkAction *action)
1531 {
1532 settings.duration_index = 3;
1533
1534 gtk_action_set_sensitive (action, FALSE);
1535
1536 action = gtk_ui_manager_get_action (ui_manager,
1537 "/anim-play-popup/speed-down");
1538 gtk_action_set_sensitive (action, TRUE);
1539
1540 action = gtk_ui_manager_get_action (ui_manager,
1541 "/anim-play-popup/speed-up");
1542 gtk_action_set_sensitive (action, TRUE);
1543
1544 update_combobox ();
1545 }
1546
1547 static void
framecombo_changed(GtkWidget * combo,gpointer data)1548 framecombo_changed (GtkWidget *combo, gpointer data)
1549 {
1550 settings.default_frame_disposal = gtk_combo_box_get_active (GTK_COMBO_BOX (combo));
1551 init_frames ();
1552 render_frame (frame_number);
1553 }
1554
1555 static void
speedcombo_changed(GtkWidget * combo,gpointer data)1556 speedcombo_changed (GtkWidget *combo, gpointer data)
1557 {
1558 GtkAction * action;
1559
1560 settings.duration_index = gtk_combo_box_get_active (GTK_COMBO_BOX (combo));
1561
1562 action = gtk_ui_manager_get_action (ui_manager,
1563 "/anim-play-popup/speed-reset");
1564 gtk_action_set_sensitive (action, settings.duration_index != 3);
1565
1566 action = gtk_ui_manager_get_action (ui_manager,
1567 "/anim-play-popup/speed-down");
1568 gtk_action_set_sensitive (action, settings.duration_index < 6);
1569
1570 action = gtk_ui_manager_get_action (ui_manager,
1571 "/anim-play-popup/speed-up");
1572 gtk_action_set_sensitive (action, settings.duration_index > 0);
1573 }
1574
1575 static void
update_scale(gdouble scale)1576 update_scale (gdouble scale)
1577 {
1578 guint expected_drawing_area_width;
1579 guint expected_drawing_area_height;
1580
1581 /* FIXME: scales under 0.5 are broken. See bug 690265. */
1582 if (scale <= 0.5)
1583 scale = 0.501;
1584
1585 expected_drawing_area_width = width * scale;
1586 expected_drawing_area_height = height * scale;
1587
1588 gtk_widget_set_size_request (drawing_area, expected_drawing_area_width, expected_drawing_area_height);
1589 gtk_widget_set_size_request (shape_drawing_area, expected_drawing_area_width, expected_drawing_area_height);
1590 /* I force the shape window to a smaller size if we scale down. */
1591 if (detached)
1592 {
1593 gint x, y;
1594
1595 gdk_window_get_origin (gtk_widget_get_window (shape_window), &x, &y);
1596 gtk_window_reshow_with_initial_size (GTK_WINDOW (shape_window));
1597 gtk_window_move (GTK_WINDOW (shape_window), x, y);
1598 }
1599 }
1600
1601 /*
1602 * Callback emitted when the user hits the Enter key of the zoom combo.
1603 */
1604 static void
zoomcombo_activated(GtkEntry * combo,gpointer data)1605 zoomcombo_activated (GtkEntry *combo, gpointer data)
1606 {
1607 update_scale (get_scale (-1));
1608 }
1609
1610 /*
1611 * Callback emitted when the user selects a zoom in the dropdown,
1612 * or edits the text entry.
1613 * We don't want to process manual edits because it greedily emits
1614 * signals after each character deleted or added.
1615 */
1616 static void
zoomcombo_changed(GtkWidget * combo,gpointer data)1617 zoomcombo_changed (GtkWidget *combo, gpointer data)
1618 {
1619 gint index = gtk_combo_box_get_active (GTK_COMBO_BOX (combo));
1620
1621 /* If no index, user is probably editing by hand. We wait for him to click "Enter". */
1622 if (index != -1)
1623 update_scale (get_scale (index));
1624 }
1625
1626 static void
fpscombo_changed(GtkWidget * combo,gpointer data)1627 fpscombo_changed (GtkWidget *combo, gpointer data)
1628 {
1629 settings.default_frame_duration = 1000 / get_fps (gtk_combo_box_get_active (GTK_COMBO_BOX (combo)));
1630 }
1631
1632 static void
update_combobox(void)1633 update_combobox (void)
1634 {
1635 gtk_combo_box_set_active (GTK_COMBO_BOX (speedcombo), settings.duration_index);
1636 }
1637
1638 /* tag util. */
1639
1640 static gboolean
is_ms_tag(const gchar * str,gint * duration,gint * taglength)1641 is_ms_tag (const gchar *str,
1642 gint *duration,
1643 gint *taglength)
1644 {
1645 gint sum = 0;
1646 gint offset;
1647 gint length;
1648
1649 length = strlen(str);
1650
1651 if (str[0] != '(')
1652 return FALSE;
1653
1654 offset = 1;
1655
1656 /* eat any spaces between open-parenthesis and number */
1657 while ((offset < length) && (str[offset] == ' '))
1658 offset++;
1659
1660 if ((offset>=length) || (!g_ascii_isdigit (str[offset])))
1661 return FALSE;
1662
1663 do
1664 {
1665 sum *= 10;
1666 sum += str[offset] - '0';
1667 offset++;
1668 }
1669 while ((offset<length) && (g_ascii_isdigit (str[offset])));
1670
1671 if (length - offset <= 2)
1672 return FALSE;
1673
1674 /* eat any spaces between number and 'ms' */
1675 while ((offset < length) && (str[offset] == ' '))
1676 offset++;
1677
1678 if (length - offset <= 2 ||
1679 g_ascii_toupper (str[offset]) != 'M' ||
1680 g_ascii_toupper (str[offset + 1]) != 'S')
1681 return FALSE;
1682
1683 offset += 2;
1684
1685 /* eat any spaces between 'ms' and close-parenthesis */
1686 while ((offset < length) && (str[offset] == ' '))
1687 offset++;
1688
1689 if ((length - offset < 1) || (str[offset] != ')'))
1690 return FALSE;
1691
1692 offset++;
1693
1694 *duration = sum;
1695 *taglength = offset;
1696
1697 return TRUE;
1698 }
1699
1700 static gint
parse_ms_tag(const gchar * str)1701 parse_ms_tag (const gchar *str)
1702 {
1703 gint i;
1704 gint length = strlen (str);
1705
1706 for (i = 0; i < length; i++)
1707 {
1708 gint rtn;
1709 gint dummy;
1710
1711 if (is_ms_tag (&str[i], &rtn, &dummy))
1712 return rtn;
1713 }
1714
1715 return -1;
1716 }
1717
1718 static gboolean
is_disposal_tag(const gchar * str,DisposeType * disposal,gint * taglength)1719 is_disposal_tag (const gchar *str,
1720 DisposeType *disposal,
1721 gint *taglength)
1722 {
1723 if (strlen (str) != 9)
1724 return FALSE;
1725
1726 if (strncmp (str, "(combine)", 9) == 0)
1727 {
1728 *taglength = 9;
1729 *disposal = DISPOSE_COMBINE;
1730 return TRUE;
1731 }
1732 else if (strncmp (str, "(replace)", 9) == 0)
1733 {
1734 *taglength = 9;
1735 *disposal = DISPOSE_REPLACE;
1736 return TRUE;
1737 }
1738
1739 return FALSE;
1740 }
1741
1742 static DisposeType
parse_disposal_tag(const gchar * str)1743 parse_disposal_tag (const gchar *str)
1744 {
1745 gint i;
1746 gint length = strlen (str);
1747
1748 for (i = 0; i < length; i++)
1749 {
1750 DisposeType rtn;
1751 gint dummy;
1752
1753 if (is_disposal_tag (&str[i], &rtn, &dummy))
1754 return rtn;
1755 }
1756
1757 return settings.default_frame_disposal;
1758 }
1759
1760