1 /*
2 * Copyright © 2009-2018 Siyan Panayotov <contact@siyanpanayotov.com>
3 *
4 *
5 * Based on code by (see README for details):
6 * - Björn Lindqvist <bjourne@gmail.com>
7 *
8 * This file is part of Viewnior.
9 *
10 * Viewnior is free software: you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License as published by
12 * the Free Software Foundation, either version 3 of the License, or
13 * (at your option) any later version.
14 *
15 * Viewnior is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License for more details.
19 *
20 * You should have received a copy of the GNU General Public License
21 * along with Viewnior. If not, see <http://www.gnu.org/licenses/>.
22 */
23
24 #include <glib.h>
25 #include <gdk/gdkkeysyms.h>
26 #include "uni-anim-view.h"
27
28 /*************************************************************/
29 /***** Private data ******************************************/
30 /*************************************************************/
31 enum {
32 TOGGLE_RUNNING,
33 STEP,
34 LAST_SIGNAL
35 };
36
37 static guint uni_anim_view_signals[LAST_SIGNAL] = { 0 };
38
39 G_DEFINE_TYPE (UniAnimView, uni_anim_view, UNI_TYPE_IMAGE_VIEW);
40
41 /*************************************************************/
42 /***** Static stuff ******************************************/
43 /*************************************************************/
44
45 static gboolean
uni_anim_view_updator(gpointer data)46 uni_anim_view_updator (gpointer data)
47 {
48 UniAnimView *aview = (UniAnimView *) data;
49
50 // Workaround for #437791.
51 glong delay_us = aview->delay * 1000;
52 if (aview->delay == 20)
53 {
54 // If the delay time is 20 ms, the GIF is a "fast player." and
55 // we increase it to a more reasonable 100 ms so that the
56 // frame is only updated 1/5 of the times
57 // uni_anim_view_updator() is run.
58 delay_us = 200;
59 }
60 g_time_val_add (&aview->time, delay_us);
61
62 gboolean next = gdk_pixbuf_animation_iter_advance (aview->iter,
63 &aview->time);
64 uni_anim_view_set_is_playing (aview, FALSE);
65
66 aview->delay = gdk_pixbuf_animation_iter_get_delay_time (aview->iter);
67 aview->timer_id = g_timeout_add (aview->delay,
68 uni_anim_view_updator, aview);
69
70 if (!next)
71 return FALSE;
72
73 GdkPixbuf *pixbuf = gdk_pixbuf_animation_iter_get_pixbuf (aview->iter);
74 uni_image_view_set_pixbuf (UNI_IMAGE_VIEW (aview), pixbuf, FALSE);
75
76 return FALSE;
77 }
78
79 /*************************************************************/
80 /***** Private signal handlers *******************************/
81 /*************************************************************/
82 static void
uni_anim_view_toggle_running(UniAnimView * aview)83 uni_anim_view_toggle_running (UniAnimView * aview)
84 {
85 uni_anim_view_set_is_playing (aview, !aview->timer_id);
86 }
87
88 /* Steps the animation one frame forward. If the animation is playing
89 * it will be stopped. Will it wrap around if the animation is at its
90 * last frame?
91 **/
92 static void
uni_anim_view_step(UniAnimView * aview)93 uni_anim_view_step (UniAnimView * aview)
94 {
95 if (aview->anim)
96 {
97 /* Part of workaround for #437791. uni_anim_view_updator()
98 * might not always immidiately step to the next frame, so we
99 * loop until the frame is changed.
100 *
101 * If we are on the last frame, it will not wrap around so the
102 * frame will never change. So we risk an infinite loop.
103 * Unfortunately but expectedly, GdkPixbufAnimationIter
104 * doesn't provide a way to check if we
105 * are on the last frame because the API is totally brain
106 * damaged. The work-around is to give uni_anim_view_updator
107 * exactly 10 chances to advance the frame before bailing out.
108 * */
109 int n = 0;
110 GdkPixbuf *old = gdk_pixbuf_animation_iter_get_pixbuf (aview->iter);
111 while ((gdk_pixbuf_animation_iter_get_pixbuf (aview->iter) == old)
112 && (n < 10))
113 {
114 uni_anim_view_updator (aview);
115 n++;
116 }
117 }
118 uni_anim_view_set_is_playing (aview, FALSE);
119 }
120
121 /*************************************************************/
122 /***** Stuff that deals with the type ************************/
123 /*************************************************************/
124 static void
uni_anim_view_init(UniAnimView * aview)125 uni_anim_view_init (UniAnimView * aview)
126 {
127 aview->anim = NULL;
128 aview->iter = NULL;
129 aview->timer_id = 0;
130 }
131
132 static void
uni_anim_view_finalize(GObject * object)133 uni_anim_view_finalize (GObject * object)
134 {
135 uni_anim_view_set_is_playing (UNI_ANIM_VIEW (object), FALSE);
136
137 /* Chain up. */
138 G_OBJECT_CLASS (uni_anim_view_parent_class)->finalize (object);
139 }
140
141 static void
uni_anim_view_init_signals(UniAnimViewClass * klass)142 uni_anim_view_init_signals (UniAnimViewClass * klass)
143 {
144 /**
145 * UniAnimView::toggle-running:
146 * @aview: a #UniAnimView
147 *
148 * Stops the animation if it was playing or resumes it, if it was
149 * playing. ::toggle-running is a keybinding signal emitted when
150 * %GDK_KEY_p is pressed on the widget and should not be used by
151 * clients of this library.
152 **/
153 uni_anim_view_signals[TOGGLE_RUNNING] =
154 g_signal_new ("toggle_running",
155 G_TYPE_FROM_CLASS (klass),
156 G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
157 G_STRUCT_OFFSET (UniAnimViewClass, toggle_running),
158 NULL, NULL,
159 g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0);
160 /**
161 * UniAnimView::step:
162 * @aview: a #UniAnimView
163 *
164 * Steps the animation one frame forward. If the animation is
165 * playing it will first be stopped. ::step is a keybinding signal
166 * emitted when %GDK_KEY_j is pressed on the widget and should not be
167 * used by clients of this library.
168 **/
169 uni_anim_view_signals[STEP] =
170 g_signal_new ("step",
171 G_TYPE_FROM_CLASS (klass),
172 G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
173 G_STRUCT_OFFSET (UniAnimViewClass, step),
174 NULL, NULL,
175 g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0);
176 }
177
178 static void
uni_anim_view_class_init(UniAnimViewClass * klass)179 uni_anim_view_class_init (UniAnimViewClass * klass)
180 {
181 uni_anim_view_init_signals (klass);
182
183 GObjectClass *object_class = G_OBJECT_CLASS (klass);
184 object_class->finalize = uni_anim_view_finalize;
185
186 klass->toggle_running = uni_anim_view_toggle_running;
187 klass->step = uni_anim_view_step;
188
189 /* Add keybindings. */
190 GtkBindingSet *binding_set = gtk_binding_set_by_class (klass);
191
192 /* Stop */
193 gtk_binding_entry_add_signal (binding_set, GDK_KEY_p, 0, "toggle_running", 0);
194
195 /* Step */
196 gtk_binding_entry_add_signal (binding_set, GDK_KEY_j, 0, "step", 0);
197 }
198
199 /**
200 * uni_anim_view_new:
201 * @returns: A new #UniAnimView.
202 *
203 * Creates a new #UniAnimView with default values.
204 **/
205 GtkWidget *
uni_anim_view_new(void)206 uni_anim_view_new (void)
207 {
208 GtkWidget *aview = g_object_new (UNI_TYPE_ANIM_VIEW, NULL);
209 return aview;
210 }
211
212
213 /*************************************************************/
214 /***** Read-write properties *********************************/
215 /*************************************************************/
216 /**
217 * uni_anim_view_set_anim:
218 * @aview: A #UniAnimView.
219 * @anim: A pixbuf animation to play.
220 *
221 * Sets the pixbuf animation to play, or %NULL to not play any
222 * animation.
223 *
224 * If the animation is a static image or only has one frame, then the
225 * static image will be displayed instead. If more frames are loaded
226 * into the animation, then #UniAnimView will automatically animate to
227 * those frames.
228 *
229 * The effect of this method is analoguous to
230 * uni_image_view_set_pixbuf(). Fit mode is reset to
231 * %GTK_FIT_SIZE_IF_LARGER so that the whole area of the animation
232 * fits in the view. Three signals are emitted, first the
233 * #UniImageView will emit ::zoom-changed and then ::pixbuf-changed,
234 * second, #UniAnimView itself will emit ::anim-changed.
235 *
236 * The default pixbuf animation is %NULL.
237 **/
238
239 /* Return TRUE if anim is a static image */
240 gboolean
uni_anim_view_set_anim(UniAnimView * aview,GdkPixbufAnimation * anim)241 uni_anim_view_set_anim (UniAnimView * aview, GdkPixbufAnimation * anim)
242 {
243 gboolean is_static;
244
245 if (aview->anim)
246 g_object_unref (aview->anim);
247 aview->anim = anim;
248
249 if (!anim)
250 {
251 uni_anim_view_set_is_playing (aview, FALSE);
252 uni_image_view_set_pixbuf (UNI_IMAGE_VIEW (aview), NULL, TRUE);
253 return TRUE;
254 }
255
256 g_object_ref (aview->anim);
257 if (aview->iter)
258 g_object_unref (aview->iter);
259
260 g_get_current_time (&aview->time);
261 aview->iter = gdk_pixbuf_animation_get_iter (aview->anim, &aview->time);
262
263 GdkPixbuf *pixbuf;
264
265 is_static = gdk_pixbuf_animation_is_static_image (anim);
266
267 if (is_static)
268 {
269 pixbuf = gdk_pixbuf_animation_get_static_image (anim);
270 }
271 else
272 {
273 pixbuf = gdk_pixbuf_animation_iter_get_pixbuf (aview->iter);
274 }
275
276 uni_image_view_set_pixbuf (UNI_IMAGE_VIEW (aview), pixbuf, TRUE);
277
278 uni_anim_view_set_is_playing (aview, FALSE);
279 aview->delay = gdk_pixbuf_animation_iter_get_delay_time (aview->iter);
280
281 if(!is_static)
282 aview->timer_id = g_timeout_add (aview->delay,
283 uni_anim_view_updator, aview);
284 return is_static;
285 }
286
287
288 /* No conversion from GdkPixbuf to GdkPixbufAnim can be made
289 * directly using the current API, so this makes a static
290 * GdkPixbufAnimation and updates the UniAnimView */
291 void
uni_anim_view_set_static(UniAnimView * aview,GdkPixbuf * pixbuf)292 uni_anim_view_set_static (UniAnimView * aview, GdkPixbuf * pixbuf)
293 {
294 GdkPixbufSimpleAnim *s_anim;
295
296 s_anim = gdk_pixbuf_simple_anim_new (gdk_pixbuf_get_width(pixbuf),
297 gdk_pixbuf_get_height(pixbuf),
298 -1);
299 gdk_pixbuf_simple_anim_add_frame(s_anim, pixbuf);
300
301 /* Simple version of uni_anim_view_set_anim */
302 if (aview->anim)
303 g_object_unref (aview->anim);
304
305 aview->anim = (GdkPixbufAnimation*)s_anim;
306
307 g_object_ref (aview->anim);
308 if (aview->iter)
309 g_object_unref (aview->iter);
310
311 uni_image_view_set_pixbuf (UNI_IMAGE_VIEW (aview), pixbuf, TRUE);
312 uni_anim_view_set_is_playing (aview, FALSE);
313 aview->delay = -1;
314 aview->iter = NULL;
315
316 g_object_unref(pixbuf);
317 }
318
319
320 /**
321 * uni_anim_view_set_is_playing:
322 * @aview: a #UniImageView
323 * @playing: %TRUE to play the animation, %FALSE otherwise
324 *
325 * Sets whether the animation should play or not. If there is no
326 * current animation this method does not have any effect.
327 **/
328 void
uni_anim_view_set_is_playing(UniAnimView * aview,gboolean playing)329 uni_anim_view_set_is_playing (UniAnimView * aview, gboolean playing)
330 {
331 if (!playing && aview->timer_id)
332 {
333 /* Commanded to stop AND the animation is playing. */
334 g_source_remove (aview->timer_id);
335 aview->timer_id = 0;
336 }
337 else if (playing && aview->anim)
338 uni_anim_view_updator (aview);
339 }
340
341 /**
342 * uni_anim_view_get_is_playing:
343 * @aview: A #UniImageView.
344 * @returns: %TRUE if an animation is playing, %FALSE otherwise.
345 *
346 * Returns whether the animation is playing or not. If there is no
347 * current animation, this method will always returns %FALSE.
348 **/
349 gboolean
uni_anim_view_get_is_playing(UniAnimView * aview)350 uni_anim_view_get_is_playing (UniAnimView * aview)
351 {
352 return aview->timer_id && aview->anim;
353 }
354