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