1 /*
2 * Clutter.
3 *
4 * An OpenGL based 'interactive canvas' library.
5 *
6 * Authored By Matthew Allum <mallum@openedhand.com>
7 *
8 * Copyright (C) 2006 OpenedHand
9 *
10 * This library is free software; you can redistribute it and/or
11 * modify it under the terms of the GNU Lesser General Public
12 * License as published by the Free Software Foundation; either
13 * version 2 of the License, or (at your option) any later version.
14 *
15 * This library 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 GNU
18 * Lesser General Public License for more details.
19 *
20 * You should have received a copy of the GNU Lesser General Public
21 * License along with this library; if not, write to the
22 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
23 * Boston, MA 02111-1307, USA.
24 */
25
26 /**
27 * SECTION:egg-timeline
28 * @short_description: A class for time-based events
29 *
30 * #EggTimeline is a base class for managing time based events such
31 * as animations.
32 *
33 * Every timeline shares the same #EggTimeoutPool to decrease the
34 * possibility of starving the main loop when using many timelines
35 * at the same time; this might cause problems if you are also using
36 * a library making heavy use of threads with no GLib main loop integration.
37 *
38 * In that case you might disable the common timeline pool by setting
39 * the %EGG_TIMELINE=no-pool environment variable prior to launching
40 * your application.
41 *
42 * One way to visualise a timeline is as a path with marks along its length.
43 * When creating a timeline of @n_frames via egg_timeline_new(), then the
44 * number of frames can be seen as the paths length, and each unit of length
45 * (each frame) is delimited by a mark.
46 *
47 * For a non looping timeline there will be (n_frames + 1) marks along its
48 * length. For a looping timeline, the two ends are joined with one mark.
49 * Technically this mark represents two discrete frame numbers, but for a
50 * looping timeline the start and end frame numbers are considered equivalent.
51 *
52 * When you create a timeline it starts with
53 * egg_timeline_get_current_frame() == 0.
54 *
55 * After starting a timeline, the first timeout is for current_frame_num == 1
56 * (Notably it isn't 0 since there is a delay before the first timeout signals
57 * so re-asserting the starting frame (0) wouldn't make sense.)
58 * Notably, this implies that actors you intend to be affected by the
59 * timeline's progress, should be manually primed/positioned for frame 0 which
60 * will be displayed before the first timeout. (If you are not careful about
61 * this point you will likely see flashes of incorrect actor state in your
62 * program)
63 *
64 * For a non looping timeline the last timeout would be for
65 * current_frame_num == @n_frames
66 *
67 * For a looping timeline the timeout for current_frame_num == @n_frames would
68 * be followed by a timeout for current_frame_num == 1 (remember frame 0 is
69 * considered == frame (@n_frames)).
70 *
71 * There may be times when a system is not able to meet the frame rate
72 * requested for a timeline, and in this case the frame number will be
73 * interpolated at the next timeout event. The interpolation is calculated from
74 * the time that the timeline was started, not from the time of the last
75 * timeout, so a given timeline should basically elapse in the same - real
76 * world - time on any given system. An invariable here though is that
77 * current_frame_num == @n_frames will always be signaled, but notably frame 1
78 * can be interpolated past and so never signaled.
79 *
80 */
81
82 #ifdef HAVE_CONFIG_H
83 #include "config.h"
84 #endif
85
86 #include "egg-timeout-pool.h"
87 #include "egg-timeline.h"
88 // #include "egg-main.h"
89 // #include "egg-marshal.h"
90 // #include "egg-private.h"
91 #include "egg-hack.h"
92 #include "egg-debug.h"
93 // #include "egg-enum-types.h"
94
95 G_DEFINE_TYPE (EggTimeline, egg_timeline, G_TYPE_OBJECT);
96
97 #define FPS_TO_INTERVAL(f) (1000 / (f))
98
99 struct _EggTimelinePrivate
100 {
101 EggTimelineDirection direction;
102
103 guint timeout_id;
104 guint delay_id;
105
106 gint current_frame_num;
107
108 guint fps;
109 guint n_frames;
110 guint delay;
111
112 gint skipped_frames;
113
114 GTimeVal prev_frame_timeval;
115 guint msecs_delta;
116
117 GHashTable *markers_by_frame;
118 GHashTable *markers_by_name;
119
120 guint loop : 1;
121 };
122
123 typedef struct {
124 gchar *name;
125 guint frame_num;
126 GQuark quark;
127 } TimelineMarker;
128
129 enum
130 {
131 PROP_0,
132
133 PROP_FPS,
134 PROP_NUM_FRAMES,
135 PROP_LOOP,
136 PROP_DELAY,
137 PROP_DURATION,
138 PROP_DIRECTION
139 };
140
141 enum
142 {
143 NEW_FRAME,
144 STARTED,
145 PAUSED,
146 COMPLETED,
147 MARKER_REACHED,
148
149 LAST_SIGNAL
150 };
151
152 static guint timeline_signals[LAST_SIGNAL] = { 0 };
153 static gint timeline_use_pool = -1;
154 static EggTimeoutPool *timeline_pool = NULL;
155
156 static inline void
157 timeline_pool_init (void)
158 {
159 if (timeline_use_pool == -1)
160 {
161 const gchar *timeline_env;
162
163 timeline_env = g_getenv ("EGG_TIMELINE");
164 if (timeline_env && timeline_env[0] != '\0' &&
165 strcmp (timeline_env, "no-pool") == 0)
166 {
167 timeline_use_pool = FALSE;
168 }
169 else
170 {
171 timeline_pool = egg_timeout_pool_new (EGG_PRIORITY_TIMELINE);
172 timeline_use_pool = TRUE;
173 }
174 }
175 }
176
177 static guint
178 timeout_add (guint interval,
179 GSourceFunc func,
180 gpointer data,
181 GDestroyNotify notify)
182 {
183 guint res;
184
185 if (G_LIKELY (timeline_use_pool))
186 {
187 g_assert (timeline_pool != NULL);
188 res = egg_timeout_pool_add (timeline_pool,
189 interval,
190 func, data, notify);
191 }
192 else
193 {
194 #if 0
195 res = egg_threads_add_frame_source_full (EGG_PRIORITY_TIMELINE,
196 interval,
197 func, data, notify);
198 #endif
199 g_assert (G_LIKELY (timeline_use_pool)); /* ie, FAIL */
200 }
201
202 return res;
203 }
204
205 static void
206 timeout_remove (guint tag)
207 {
208 if (G_LIKELY (timeline_use_pool))
209 {
210 g_assert (timeline_pool != NULL);
211 egg_timeout_pool_remove (timeline_pool, tag);
212 }
213 else
214 g_source_remove (tag);
215 }
216
217 static TimelineMarker *
218 timeline_marker_new (const gchar *name,
219 guint frame_num)
220 {
221 TimelineMarker *marker = g_slice_new0 (TimelineMarker);
222
223 marker->name = g_strdup (name);
224 marker->quark = g_quark_from_string (marker->name);
225 marker->frame_num = frame_num;
226
227 return marker;
228 }
229
230 static void
231 timeline_marker_free (gpointer data)
232 {
233 if (G_LIKELY (data))
234 {
235 TimelineMarker *marker = data;
236
237 g_free (marker->name);
238 g_slice_free (TimelineMarker, marker);
239 }
240 }
241
242 /* Object */
243
244 static void
245 egg_timeline_set_property (GObject *object,
246 guint prop_id,
247 const GValue *value,
248 GParamSpec *pspec)
249 {
250 EggTimeline *timeline;
251 EggTimelinePrivate *priv;
252
253 timeline = EGG_TIMELINE(object);
254 priv = timeline->priv;
255
256 switch (prop_id)
257 {
258 case PROP_FPS:
259 egg_timeline_set_speed (timeline, g_value_get_uint (value));
260 break;
261 case PROP_NUM_FRAMES:
262 egg_timeline_set_n_frames (timeline, g_value_get_uint (value));
263 break;
264 case PROP_LOOP:
265 priv->loop = g_value_get_boolean (value);
266 break;
267 case PROP_DELAY:
268 priv->delay = g_value_get_uint (value);
269 break;
270 case PROP_DURATION:
271 egg_timeline_set_duration (timeline, g_value_get_uint (value));
272 break;
273 case PROP_DIRECTION:
274 egg_timeline_set_direction (timeline, g_value_get_enum (value));
275 break;
276 default:
277 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
278 break;
279 }
280 }
281
282 static void
283 egg_timeline_get_property (GObject *object,
284 guint prop_id,
285 GValue *value,
286 GParamSpec *pspec)
287 {
288 EggTimeline *timeline;
289 EggTimelinePrivate *priv;
290
291 timeline = EGG_TIMELINE(object);
292 priv = timeline->priv;
293
294 switch (prop_id)
295 {
296 case PROP_FPS:
297 g_value_set_uint (value, priv->fps);
298 break;
299 case PROP_NUM_FRAMES:
300 g_value_set_uint (value, priv->n_frames);
301 break;
302 case PROP_LOOP:
303 g_value_set_boolean (value, priv->loop);
304 break;
305 case PROP_DELAY:
306 g_value_set_uint (value, priv->delay);
307 break;
308 case PROP_DURATION:
309 g_value_set_uint (value, egg_timeline_get_duration (timeline));
310 break;
311 case PROP_DIRECTION:
312 g_value_set_enum (value, priv->direction);
313 break;
314 default:
315 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
316 break;
317 }
318 }
319
320 static void
321 egg_timeline_finalize (GObject *object)
322 {
323 EggTimelinePrivate *priv = EGG_TIMELINE (object)->priv;
324
325 g_hash_table_destroy (priv->markers_by_frame);
326 g_hash_table_destroy (priv->markers_by_name);
327
328 G_OBJECT_CLASS (egg_timeline_parent_class)->finalize (object);
329 }
330
331 static void
332 egg_timeline_dispose (GObject *object)
333 {
334 EggTimeline *self = EGG_TIMELINE(object);
335 EggTimelinePrivate *priv;
336
337 priv = self->priv;
338
339 if (priv->delay_id)
340 {
341 timeout_remove (priv->delay_id);
342 priv->delay_id = 0;
343 }
344
345 if (priv->timeout_id)
346 {
347 timeout_remove (priv->timeout_id);
348 priv->timeout_id = 0;
349 }
350
351 G_OBJECT_CLASS (egg_timeline_parent_class)->dispose (object);
352 }
353
354 static void
355 egg_timeline_class_init (EggTimelineClass *klass)
356 {
357 GObjectClass *object_class = G_OBJECT_CLASS (klass);
358
359 timeline_pool_init ();
360
361 object_class->set_property = egg_timeline_set_property;
362 object_class->get_property = egg_timeline_get_property;
363 object_class->finalize = egg_timeline_finalize;
364 object_class->dispose = egg_timeline_dispose;
365
366 g_type_class_add_private (klass, sizeof (EggTimelinePrivate));
367
368 /**
369 * EggTimeline:fps:
370 *
371 * Timeline frames per second. Because of the nature of the main
372 * loop used by Egg this is to be considered a best approximation.
373 */
374 g_object_class_install_property (object_class,
375 PROP_FPS,
376 g_param_spec_uint ("fps",
377 "Frames Per Second",
378 "Timeline frames per second",
379 1, 1000,
380 60,
381 EGG_PARAM_READWRITE));
382 /**
383 * EggTimeline:num-frames:
384 *
385 * Total number of frames for the timeline.
386 */
387 g_object_class_install_property (object_class,
388 PROP_NUM_FRAMES,
389 g_param_spec_uint ("num-frames",
390 "Total number of frames",
391 "Timelines total number of frames",
392 1, G_MAXUINT,
393 1,
394 EGG_PARAM_READWRITE));
395 /**
396 * EggTimeline:loop:
397 *
398 * Whether the timeline should automatically rewind and restart.
399 */
400 g_object_class_install_property (object_class,
401 PROP_LOOP,
402 g_param_spec_boolean ("loop",
403 "Loop",
404 "Should the timeline automatically restart",
405 FALSE,
406 EGG_PARAM_READWRITE));
407 /**
408 * EggTimeline:delay:
409 *
410 * A delay, in milliseconds, that should be observed by the
411 * timeline before actually starting.
412 *
413 * Since: 0.4
414 */
415 g_object_class_install_property (object_class,
416 PROP_DELAY,
417 g_param_spec_uint ("delay",
418 "Delay",
419 "Delay before start",
420 0, G_MAXUINT,
421 0,
422 EGG_PARAM_READWRITE));
423 /**
424 * EggTimeline:duration:
425 *
426 * Duration of the timeline in milliseconds, depending on the
427 * EggTimeline:fps value.
428 *
429 * Since: 0.6
430 */
431 g_object_class_install_property (object_class,
432 PROP_DURATION,
433 g_param_spec_uint ("duration",
434 "Duration",
435 "Duration of the timeline in milliseconds",
436 0, G_MAXUINT,
437 1000,
438 EGG_PARAM_READWRITE));
439 /**
440 * EggTimeline:direction:
441 *
442 * The direction of the timeline, either %EGG_TIMELINE_FORWARD or
443 * %EGG_TIMELINE_BACKWARD.
444 *
445 * Since: 0.6
446 */
447 g_object_class_install_property (object_class,
448 PROP_DIRECTION,
449 g_param_spec_enum ("direction",
450 "Direction",
451 "Direction of the timeline",
452 EGG_TYPE_TIMELINE_DIRECTION,
453 EGG_TIMELINE_FORWARD,
454 EGG_PARAM_READWRITE));
455
456 /**
457 * EggTimeline::new-frame:
458 * @timeline: the timeline which received the signal
459 * @frame_num: the number of the new frame between 0 and
460 * EggTimeline:num-frames
461 *
462 * The ::new-frame signal is emitted each time a new frame in the
463 * timeline is reached.
464 */
465 timeline_signals[NEW_FRAME] =
466 g_signal_new ("new-frame",
467 G_TYPE_FROM_CLASS (object_class),
468 G_SIGNAL_RUN_LAST,
469 G_STRUCT_OFFSET (EggTimelineClass, new_frame),
470 NULL, NULL,
471 egg_marshal_VOID__INT,
472 G_TYPE_NONE,
473 1, G_TYPE_INT);
474 /**
475 * EggTimeline::completed:
476 * @timeline: the #EggTimeline which received the signal
477 *
478 * The ::completed signal is emitted when the timeline reaches the
479 * number of frames specified by the EggTimeline:num-frames property.
480 */
481 timeline_signals[COMPLETED] =
482 g_signal_new ("completed",
483 G_TYPE_FROM_CLASS (object_class),
484 G_SIGNAL_RUN_LAST,
485 G_STRUCT_OFFSET (EggTimelineClass, completed),
486 NULL, NULL,
487 egg_marshal_VOID__VOID,
488 G_TYPE_NONE, 0);
489 /**
490 * EggTimeline::started:
491 * @timeline: the #EggTimeline which received the signal
492 *
493 * The ::started signal is emitted when the timeline starts its run.
494 * This might be as soon as egg_timeline_start() is invoked or
495 * after the delay set in the EggTimeline:delay property has
496 * expired.
497 */
498 timeline_signals[STARTED] =
499 g_signal_new ("started",
500 G_TYPE_FROM_CLASS (object_class),
501 G_SIGNAL_RUN_LAST,
502 G_STRUCT_OFFSET (EggTimelineClass, started),
503 NULL, NULL,
504 egg_marshal_VOID__VOID,
505 G_TYPE_NONE, 0);
506 /**
507 * EggTimeline::paused:
508 * @timeline: the #EggTimeline which received the signal
509 *
510 * The ::paused signal is emitted when egg_timeline_pause() is invoked.
511 */
512 timeline_signals[PAUSED] =
513 g_signal_new ("paused",
514 G_TYPE_FROM_CLASS (object_class),
515 G_SIGNAL_RUN_LAST,
516 G_STRUCT_OFFSET (EggTimelineClass, paused),
517 NULL, NULL,
518 egg_marshal_VOID__VOID,
519 G_TYPE_NONE, 0);
520 /**
521 * EggTimeline::marker-reached:
522 * @timeline: the #EggTimeline which received the signal
523 * @marker_name: the name of the marker reached
524 * @frame_num: the frame number
525 *
526 * The ::marker-reached signal is emitted each time a timeline
527 * reaches a marker set with egg_timeline_add_marker_at_frame()
528 * or egg_timeline_add_marker_at_time(). This signal is
529 * detailed with the name of the marker as well, so it is
530 * possible to connect a callback to the ::marker-reached signal
531 * for a specific marker with:
532 *
533 * <informalexample><programlisting>
534 * egg_timeline_add_marker_at_frame (timeline, "foo", 24);
535 * egg_timeline_add_marker_at_frame (timeline, "bar", 48);
536 *
537 * g_signal_connect (timeline, "marker-reached",
538 * G_CALLBACK (each_marker_reached), NULL);
539 * g_signal_connect (timeline, "marker-reached::foo",
540 * G_CALLBACK (foo_marker_reached), NULL);
541 * g_signal_connect (timeline, "marker-reached::bar",
542 * G_CALLBACK (bar_marker_reached), NULL);
543 * </programlisting></informalexample>
544 *
545 * In the example, the first callback will be invoked for both
546 * the "foo" and "bar" marker, while the second and third callbacks
547 * will be invoked for the "foo" or "bar" markers, respectively.
548 *
549 * Since: 0.8
550 */
551 timeline_signals[MARKER_REACHED] =
552 g_signal_new ("marker-reached",
553 G_TYPE_FROM_CLASS (object_class),
554 G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_DETAILED | G_SIGNAL_NO_HOOKS,
555 G_STRUCT_OFFSET (EggTimelineClass, marker_reached),
556 NULL, NULL,
557 egg_marshal_VOID__STRING_INT,
558 G_TYPE_NONE, 2,
559 G_TYPE_STRING,
560 G_TYPE_INT);
561 }
562
563 static void
564 egg_timeline_init (EggTimeline *self)
565 {
566 EggTimelinePrivate *priv;
567
568 self->priv = priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
569 EGG_TYPE_TIMELINE,
570 EggTimelinePrivate);
571
572 priv->fps = egg_get_default_frame_rate ();
573 priv->n_frames = 0;
574 priv->msecs_delta = 0;
575
576 priv->markers_by_frame = g_hash_table_new (NULL, NULL);
577 priv->markers_by_name = g_hash_table_new_full (g_str_hash, g_str_equal,
578 NULL,
579 timeline_marker_free);
580 }
581
582 static gboolean
583 timeline_timeout_func (gpointer data)
584 {
585 EggTimeline *timeline = data;
586 EggTimelinePrivate *priv;
587 GTimeVal timeval;
588 guint n_frames;
589 gulong msecs;
590
591 priv = timeline->priv;
592
593 g_object_ref (timeline);
594
595 /* Figure out potential frame skips */
596 g_get_current_time (&timeval);
597
598 EGG_TIMESTAMP (SCHEDULER, "Timeline [%p] activated (cur: %d)\n",
599 timeline,
600 priv->current_frame_num);
601
602 if (!priv->prev_frame_timeval.tv_sec)
603 {
604 EGG_NOTE (SCHEDULER,
605 "Timeline [%p] recieved timeout before being initialised!",
606 timeline);
607 priv->prev_frame_timeval = timeval;
608 }
609
610 /* Interpolate the current frame based on the timeval of the
611 * previous frame */
612 msecs = (timeval.tv_sec - priv->prev_frame_timeval.tv_sec) * 1000;
613 msecs += (timeval.tv_usec - priv->prev_frame_timeval.tv_usec) / 1000;
614 priv->msecs_delta = msecs;
615 n_frames = msecs / (1000 / priv->fps);
616 if (n_frames == 0)
617 n_frames = 1;
618
619 priv->skipped_frames = n_frames - 1;
620
621 if (priv->skipped_frames)
622 EGG_TIMESTAMP (SCHEDULER,
623 "Timeline [%p], skipping %d frames\n",
624 timeline,
625 priv->skipped_frames);
626
627 priv->prev_frame_timeval = timeval;
628
629 /* Advance frames */
630 if (priv->direction == EGG_TIMELINE_FORWARD)
631 priv->current_frame_num += n_frames;
632 else
633 priv->current_frame_num -= n_frames;
634
635 /* If we have not reached the end of the timeline: */
636 if (!(
637 ((priv->direction == EGG_TIMELINE_FORWARD) &&
638 (priv->current_frame_num >= priv->n_frames)) ||
639 ((priv->direction == EGG_TIMELINE_BACKWARD) &&
640 (priv->current_frame_num <= 0))
641 ))
642 {
643 gint i;
644
645 /* Fire off signal */
646 g_signal_emit (timeline, timeline_signals[NEW_FRAME], 0,
647 priv->current_frame_num);
648
649 for (i = priv->skipped_frames; i >= 0; i--)
650 {
651 gint frame_num = priv->current_frame_num - i;
652 GSList *markers, *l;
653
654 markers = g_hash_table_lookup (priv->markers_by_frame,
655 GUINT_TO_POINTER (frame_num));
656 for (l = markers; l; l = l->next)
657 {
658 TimelineMarker *marker = l->data;
659
660 EGG_NOTE (SCHEDULER, "Marker `%s' reached", marker->name);
661
662 g_signal_emit (timeline, timeline_signals[MARKER_REACHED],
663 marker->quark,
664 marker->name,
665 marker->frame_num);
666 }
667 }
668
669 /* Signal pauses timeline ? */
670 if (!priv->timeout_id)
671 {
672 g_object_unref (timeline);
673 return FALSE;
674 }
675
676 g_object_unref (timeline);
677 return TRUE;
678 }
679 else
680 {
681 /* Handle loop or stop */
682 EggTimelineDirection saved_direction = priv->direction;
683 guint overflow_frame_num = priv->current_frame_num;
684 gint end_frame;
685
686 /* In case the signal handlers want to take a peek... */
687 if (priv->direction == EGG_TIMELINE_FORWARD)
688 priv->current_frame_num = priv->n_frames;
689 else if (priv->direction == EGG_TIMELINE_BACKWARD)
690 priv->current_frame_num = 0;
691
692 end_frame = priv->current_frame_num;
693
694 /* Fire off signal */
695 g_signal_emit (timeline, timeline_signals[NEW_FRAME], 0,
696 priv->current_frame_num);
697
698 /* Did the signal handler modify the current_frame_num */
699 if (priv->current_frame_num != end_frame)
700 {
701 g_object_unref (timeline);
702 return TRUE;
703 }
704
705 /* Note: If the new-frame signal handler paused the timeline
706 * on the last frame we will still go ahead and send the
707 * completed signal */
708 EGG_NOTE (SCHEDULER,
709 "Timeline [%p] completed (cur: %d, tot: %d, drop: %d)",
710 timeline,
711 priv->current_frame_num,
712 priv->n_frames,
713 n_frames - 1);
714
715 if (!priv->loop && priv->timeout_id)
716 {
717 /* We remove the timeout now, so that the completed signal handler
718 * may choose to re-start the timeline
719 *
720 * ** Perhaps we should remove this earlier, and regardless
721 * of priv->loop. Are we limiting the things that could be done in
722 * the above new-frame signal handler */
723 timeout_remove (priv->timeout_id);
724 priv->timeout_id = 0;
725 }
726
727 g_signal_emit (timeline, timeline_signals[COMPLETED], 0);
728
729 /* Again check to see if the user has manually played with
730 * current_frame_num, before we finally stop or loop the timeline */
731
732 if (priv->current_frame_num != end_frame &&
733 !(/* Except allow moving from frame 0 -> n_frame (or vica-versa)
734 since these are considered equivalent */
735 (priv->current_frame_num == 0 && end_frame == priv->n_frames) ||
736 (priv->current_frame_num == priv->n_frames && end_frame == 0)
737 ))
738 {
739 g_object_unref (timeline);
740 return TRUE;
741 }
742
743 if (priv->loop)
744 {
745 /* We try and interpolate smoothly around a loop */
746 if (saved_direction == EGG_TIMELINE_FORWARD)
747 priv->current_frame_num = overflow_frame_num - priv->n_frames;
748 else
749 priv->current_frame_num = priv->n_frames + overflow_frame_num;
750
751 /* Or if the direction changed, we try and bounce */
752 if (priv->direction != saved_direction)
753 {
754 priv->current_frame_num = priv->n_frames
755 - priv->current_frame_num;
756 }
757
758 g_object_unref (timeline);
759 return TRUE;
760 }
761 else
762 {
763 egg_timeline_rewind (timeline);
764
765 priv->prev_frame_timeval.tv_sec = 0;
766 priv->prev_frame_timeval.tv_usec = 0;
767
768 g_object_unref (timeline);
769 return FALSE;
770 }
771 }
772 }
773
774 static guint
775 timeline_timeout_add (EggTimeline *timeline,
776 guint interval,
777 GSourceFunc func,
778 gpointer data,
779 GDestroyNotify notify)
780 {
781 EggTimelinePrivate *priv;
782 GTimeVal timeval;
783
784 priv = timeline->priv;
785
786 if (priv->prev_frame_timeval.tv_sec == 0)
787 {
788 g_get_current_time (&timeval);
789 priv->prev_frame_timeval = timeval;
790 }
791 priv->skipped_frames = 0;
792 priv->msecs_delta = 0;
793
794 return timeout_add (interval, func, data, notify);
795 }
796
797 static gboolean
798 delay_timeout_func (gpointer data)
799 {
800 EggTimeline *timeline = data;
801 EggTimelinePrivate *priv = timeline->priv;
802
803 priv->delay_id = 0;
804
805 priv->timeout_id = timeline_timeout_add (timeline,
806 FPS_TO_INTERVAL (priv->fps),
807 timeline_timeout_func,
808 timeline, NULL);
809
810 g_signal_emit (timeline, timeline_signals[STARTED], 0);
811
812 return FALSE;
813 }
814
815 /**
816 * egg_timeline_start:
817 * @timeline: A #EggTimeline
818 *
819 * Starts the #EggTimeline playing.
820 **/
821 void
822 egg_timeline_start (EggTimeline *timeline)
823 {
824 EggTimelinePrivate *priv;
825
826 g_return_if_fail (EGG_IS_TIMELINE (timeline));
827
828 priv = timeline->priv;
829
830 if (priv->delay_id || priv->timeout_id)
831 return;
832
833 if (priv->n_frames == 0)
834 return;
835
836 if (priv->delay)
837 {
838 priv->delay_id = timeout_add (priv->delay,
839 delay_timeout_func,
840 timeline, NULL);
841 }
842 else
843 {
844 priv->timeout_id = timeline_timeout_add (timeline,
845 FPS_TO_INTERVAL (priv->fps),
846 timeline_timeout_func,
847 timeline, NULL);
848
849 g_signal_emit (timeline, timeline_signals[STARTED], 0);
850 }
851 }
852
853 /**
854 * egg_timeline_pause:
855 * @timeline: A #EggTimeline
856 *
857 * Pauses the #EggTimeline on current frame
858 **/
859 void
860 egg_timeline_pause (EggTimeline *timeline)
861 {
862 EggTimelinePrivate *priv;
863
864 g_return_if_fail (EGG_IS_TIMELINE (timeline));
865
866 priv = timeline->priv;
867
868 if (priv->delay_id)
869 {
870 timeout_remove (priv->delay_id);
871 priv->delay_id = 0;
872 }
873
874 if (priv->timeout_id)
875 {
876 timeout_remove (priv->timeout_id);
877 priv->timeout_id = 0;
878 }
879
880 priv->prev_frame_timeval.tv_sec = 0;
881 priv->prev_frame_timeval.tv_usec = 0;
882
883 g_signal_emit (timeline, timeline_signals[PAUSED], 0);
884 }
885
886 /**
887 * egg_timeline_stop:
888 * @timeline: A #EggTimeline
889 *
890 * Stops the #EggTimeline and moves to frame 0
891 **/
892 void
893 egg_timeline_stop (EggTimeline *timeline)
894 {
895 egg_timeline_pause (timeline);
896 egg_timeline_rewind (timeline);
897 }
898
899 /**
900 * egg_timeline_set_loop:
901 * @timeline: a #EggTimeline
902 * @loop: %TRUE for enable looping
903 *
904 * Sets whether @timeline should loop.
905 */
906 void
907 egg_timeline_set_loop (EggTimeline *timeline,
908 gboolean loop)
909 {
910 g_return_if_fail (EGG_IS_TIMELINE (timeline));
911
912 if (timeline->priv->loop != loop)
913 {
914 timeline->priv->loop = loop;
915
916 g_object_notify (G_OBJECT (timeline), "loop");
917 }
918 }
919
920 /**
921 * egg_timeline_get_loop:
922 * @timeline: a #EggTimeline
923 *
924 * Gets whether @timeline is looping
925 *
926 * Return value: %TRUE if the timeline is looping
927 */
928 gboolean
929 egg_timeline_get_loop (EggTimeline *timeline)
930 {
931 g_return_val_if_fail (EGG_IS_TIMELINE (timeline), FALSE);
932
933 return timeline->priv->loop;
934 }
935
936 /**
937 * egg_timeline_rewind:
938 * @timeline: A #EggTimeline
939 *
940 * Rewinds #EggTimeline to the first frame if its direction is
941 * EGG_TIMELINE_FORWARD and the last frame if it is
942 * EGG_TIMELINE_BACKWARD.
943 **/
944 void
945 egg_timeline_rewind (EggTimeline *timeline)
946 {
947 EggTimelinePrivate *priv;
948
949 g_return_if_fail (EGG_IS_TIMELINE (timeline));
950
951 priv = timeline->priv;
952
953 if (priv->direction == EGG_TIMELINE_FORWARD)
954 egg_timeline_advance (timeline, 0);
955 else if (priv->direction == EGG_TIMELINE_BACKWARD)
956 egg_timeline_advance (timeline, priv->n_frames);
957 }
958
959 /**
960 * egg_timeline_skip:
961 * @timeline: A #EggTimeline
962 * @n_frames: Number of frames to skip
963 *
964 * Advance timeline by requested number of frames.
965 **/
966 void
967 egg_timeline_skip (EggTimeline *timeline,
968 guint n_frames)
969 {
970 EggTimelinePrivate *priv;
971
972 g_return_if_fail (EGG_IS_TIMELINE (timeline));
973
974 priv = timeline->priv;
975
976 if (priv->direction == EGG_TIMELINE_FORWARD)
977 {
978 priv->current_frame_num += n_frames;
979
980 if (priv->current_frame_num > priv->n_frames)
981 priv->current_frame_num = 1;
982 }
983 else if (priv->direction == EGG_TIMELINE_BACKWARD)
984 {
985 priv->current_frame_num -= n_frames;
986
987 if (priv->current_frame_num < 1)
988 priv->current_frame_num = priv->n_frames - 1;
989 }
990 }
991
992 /**
993 * egg_timeline_advance:
994 * @timeline: A #EggTimeline
995 * @frame_num: Frame number to advance to
996 *
997 * Advance timeline to requested frame number
998 **/
999 void
1000 egg_timeline_advance (EggTimeline *timeline,
1001 guint frame_num)
1002 {
1003 EggTimelinePrivate *priv;
1004
1005 g_return_if_fail (EGG_IS_TIMELINE (timeline));
1006
1007 priv = timeline->priv;
1008
1009 priv->current_frame_num = CLAMP (frame_num, 0, priv->n_frames);
1010 }
1011
1012 /**
1013 * egg_timeline_get_current_frame:
1014 * @timeline: A #EggTimeline
1015 *
1016 * Request the current frame number of the timeline.
1017 *
1018 * Return Value: current frame number
1019 **/
1020 gint
1021 egg_timeline_get_current_frame (EggTimeline *timeline)
1022 {
1023 g_return_val_if_fail (EGG_IS_TIMELINE (timeline), 0);
1024
1025 return timeline->priv->current_frame_num;
1026 }
1027
1028 /**
1029 * egg_timeline_get_n_frames:
1030 * @timeline: A #EggTimeline
1031 *
1032 * Request the total number of frames for the #EggTimeline.
1033 *
1034 * Return Value: Number of frames for this #EggTimeline.
1035 **/
1036 guint
1037 egg_timeline_get_n_frames (EggTimeline *timeline)
1038 {
1039 g_return_val_if_fail (EGG_IS_TIMELINE (timeline), 0);
1040
1041 return timeline->priv->n_frames;
1042 }
1043
1044 /**
1045 * egg_timeline_set_n_frames:
1046 * @timeline: a #EggTimeline
1047 * @n_frames: the number of frames
1048 *
1049 * Sets the total number of frames for @timeline
1050 */
1051 void
1052 egg_timeline_set_n_frames (EggTimeline *timeline,
1053 guint n_frames)
1054 {
1055 EggTimelinePrivate *priv;
1056
1057 g_return_if_fail (EGG_IS_TIMELINE (timeline));
1058 g_return_if_fail (n_frames > 0);
1059
1060 priv = timeline->priv;
1061
1062 if (priv->n_frames != n_frames)
1063 {
1064 g_object_ref (timeline);
1065
1066 g_object_freeze_notify (G_OBJECT (timeline));
1067
1068 priv->n_frames = n_frames;
1069
1070 g_object_notify (G_OBJECT (timeline), "num-frames");
1071 g_object_notify (G_OBJECT (timeline), "duration");
1072
1073 g_object_thaw_notify (G_OBJECT (timeline));
1074 g_object_unref (timeline);
1075 }
1076 }
1077
1078 /**
1079 * egg_timeline_set_speed:
1080 * @timeline: A #EggTimeline
1081 * @fps: New speed of timeline as frames per second
1082 *
1083 * Set the speed in frames per second of the timeline.
1084 **/
1085 void
1086 egg_timeline_set_speed (EggTimeline *timeline,
1087 guint fps)
1088 {
1089 EggTimelinePrivate *priv;
1090
Condition "!__inst", taking false branch
Condition "__inst->g_class", taking true branch
Condition "__inst->g_class->g_type == __t", taking true branch
Condition "({...})", taking true branch
Condition "({...})", taking true branch
Falling through to end of if statement
End of if statement
1091 g_return_if_fail (EGG_IS_TIMELINE (timeline));
Condition "fps > 0", taking false branch
Condition "({...})", taking true branch
Falling through to end of if statement
End of if statement
1092 g_return_if_fail (fps > 0);
1093
1094 priv = timeline->priv;
1095
Condition "priv->fps != fps", taking true branch
1096 if (priv->fps != fps)
1097 {
1098 g_object_ref (timeline);
1099
CID 13648 - DIVIDE_BY_ZERO
Assigning: "priv->fps" = "fps".
1100 priv->fps = fps;
1101
1102 /* if the timeline is playing restart */
Condition "priv->timeout_id", taking true branch
1103 if (priv->timeout_id)
1104 {
1105 timeout_remove (priv->timeout_id);
1106
CID 13648 - DIVIDE_BY_ZERO
In expression "1000U / priv->fps", division by expression "priv->fps" which may be zero has undefined behavior.
1107 priv->timeout_id = timeline_timeout_add (timeline,
1108 FPS_TO_INTERVAL (priv->fps),
1109 timeline_timeout_func,
1110 timeline, NULL);
1111 }
1112
1113 g_object_freeze_notify (G_OBJECT (timeline));
1114
1115 g_object_notify (G_OBJECT (timeline), "duration");
1116 g_object_notify (G_OBJECT (timeline), "fps");
1117
1118 g_object_thaw_notify (G_OBJECT (timeline));
1119
1120 g_object_unref (timeline);
1121 }
1122 }
1123
1124 /**
1125 * egg_timeline_get_speed:
1126 * @timeline: a #EggTimeline
1127 *
1128 * Gets the frames per second played by @timeline
1129 *
1130 * Return value: the number of frames per second.
1131 */
1132 guint
1133 egg_timeline_get_speed (EggTimeline *timeline)
1134 {
1135 g_return_val_if_fail (EGG_IS_TIMELINE (timeline), 0);
1136
1137 return timeline->priv->fps;
1138 }
1139
1140 /**
1141 * egg_timeline_is_playing:
1142 * @timeline: A #EggTimeline
1143 *
1144 * Query state of a #EggTimeline instance.
1145 *
1146 * Return Value: TRUE if timeline is currently playing, FALSE if not.
1147 */
1148 gboolean
1149 egg_timeline_is_playing (EggTimeline *timeline)
1150 {
1151 g_return_val_if_fail (EGG_IS_TIMELINE (timeline), FALSE);
1152
1153 return (timeline->priv->timeout_id != 0);
1154 }
1155
1156 /**
1157 * egg_timeline_clone:
1158 * @timeline: #EggTimeline to duplicate.
1159 *
1160 * Create a new #EggTimeline instance which has property values
1161 * matching that of supplied timeline. The cloned timeline will not
1162 * be started and will not be positioned to the current position of
1163 * @timeline: you will have to start it with egg_timeline_start().
1164 *
1165 * Return Value: a new #EggTimeline, cloned from @timeline
1166 *
1167 * Since 0.4
1168 */
1169 EggTimeline *
1170 egg_timeline_clone (EggTimeline *timeline)
1171 {
1172 EggTimeline *copy;
1173
1174 g_return_val_if_fail (EGG_IS_TIMELINE (timeline), NULL);
1175
1176 copy = g_object_new (EGG_TYPE_TIMELINE,
1177 "fps", egg_timeline_get_speed (timeline),
1178 "num-frames", egg_timeline_get_n_frames (timeline),
1179 "loop", egg_timeline_get_loop (timeline),
1180 "delay", egg_timeline_get_delay (timeline),
1181 "direction", egg_timeline_get_direction (timeline),
1182 NULL);
1183
1184 return copy;
1185 }
1186
1187 /**
1188 * egg_timeline_new_for_duration:
1189 * @msecs: Duration of the timeline in milliseconds
1190 *
1191 * Creates a new #EggTimeline with a duration of @msecs using
1192 * the value of the EggTimeline:fps property to compute the
1193 * equivalent number of frames.
1194 *
1195 * Return value: the newly created #EggTimeline
1196 *
1197 * Since: 0.6
1198 */
1199 EggTimeline *
1200 egg_timeline_new_for_duration (guint msecs)
1201 {
1202 return g_object_new (EGG_TYPE_TIMELINE,
1203 "duration", msecs,
1204 NULL);
1205 }
1206
1207 /**
1208 * egg_timeline_new:
1209 * @n_frames: the number of frames
1210 * @fps: the number of frames per second
1211 *
1212 * Create a new #EggTimeline instance.
1213 *
1214 * Return Value: a new #EggTimeline
1215 */
1216 EggTimeline*
1217 egg_timeline_new (guint n_frames,
1218 guint fps)
1219 {
1220 g_return_val_if_fail (n_frames > 0, NULL);
1221 g_return_val_if_fail (fps > 0, NULL);
1222
1223 return g_object_new (EGG_TYPE_TIMELINE,
1224 "fps", fps,
1225 "num-frames", n_frames,
1226 NULL);
1227 }
1228
1229 /**
1230 * egg_timeline_get_delay:
1231 * @timeline: a #EggTimeline
1232 *
1233 * Retrieves the delay set using egg_timeline_set_delay().
1234 *
1235 * Return value: the delay in milliseconds.
1236 *
1237 * Since: 0.4
1238 */
1239 guint
1240 egg_timeline_get_delay (EggTimeline *timeline)
1241 {
1242 g_return_val_if_fail (EGG_IS_TIMELINE (timeline), 0);
1243
1244 return timeline->priv->delay;
1245 }
1246
1247 /**
1248 * egg_timeline_set_delay:
1249 * @timeline: a #EggTimeline
1250 * @msecs: delay in milliseconds
1251 *
1252 * Sets the delay, in milliseconds, before @timeline should start.
1253 *
1254 * Since: 0.4
1255 */
1256 void
1257 egg_timeline_set_delay (EggTimeline *timeline,
1258 guint msecs)
1259 {
1260 EggTimelinePrivate *priv;
1261
1262 g_return_if_fail (EGG_IS_TIMELINE (timeline));
1263
1264 priv = timeline->priv;
1265
1266 if (priv->delay != msecs)
1267 {
1268 priv->delay = msecs;
1269 g_object_notify (G_OBJECT (timeline), "delay");
1270 }
1271 }
1272
1273 /**
1274 * egg_timeline_get_duration:
1275 * @timeline: a #EggTimeline
1276 *
1277 * Retrieves the duration of a #EggTimeline in milliseconds.
1278 * See egg_timeline_set_duration().
1279 *
1280 * Return value: the duration of the timeline, in milliseconds.
1281 *
1282 * Since: 0.6
1283 */
1284 guint
1285 egg_timeline_get_duration (EggTimeline *timeline)
1286 {
1287 EggTimelinePrivate *priv;
1288
1289 g_return_val_if_fail (EGG_IS_TIMELINE (timeline), 0);
1290
1291 priv = timeline->priv;
1292
1293 return priv->n_frames * 1000 / priv->fps;
1294 }
1295
1296 /**
1297 * egg_timeline_set_duration:
1298 * @timeline: a #EggTimeline
1299 * @msecs: duration of the timeline in milliseconds
1300 *
1301 * Sets the duration of the timeline, in milliseconds. The speed
1302 * of the timeline depends on the EggTimeline:fps setting.
1303 *
1304 * Since: 0.6
1305 */
1306 void
1307 egg_timeline_set_duration (EggTimeline *timeline,
1308 guint msecs)
1309 {
1310 EggTimelinePrivate *priv;
1311 guint n_frames;
1312
1313 g_return_if_fail (EGG_IS_TIMELINE (timeline));
1314
1315 priv = timeline->priv;
1316
1317 n_frames = msecs * priv->fps / 1000;
1318 if (n_frames < 1)
1319 n_frames = 1;
1320
1321 egg_timeline_set_n_frames (timeline, n_frames);
1322 }
1323
1324 /**
1325 * egg_timeline_get_progress:
1326 * @timeline: a #EggTimeline
1327 *
1328 * The position of the timeline in a [0, 1] interval.
1329 *
1330 * Return value: the position of the timeline.
1331 *
1332 * Since: 0.6
1333 */
1334 gdouble
1335 egg_timeline_get_progress (EggTimeline *timeline)
1336 {
1337 g_return_val_if_fail (EGG_IS_TIMELINE (timeline), 0.);
1338
1339 return EGG_FIXED_TO_DOUBLE (egg_timeline_get_progressx (timeline));
1340 }
1341
1342 /**
1343 * egg_timeline_get_progressx:
1344 * @timeline: a #EggTimeline
1345 *
1346 * Fixed point version of egg_timeline_get_progress().
1347 *
1348 * Return value: the position of the timeline as a fixed point value
1349 *
1350 * Since: 0.6
1351 */
1352 EggFixed
1353 egg_timeline_get_progressx (EggTimeline *timeline)
1354 {
1355 EggTimelinePrivate *priv;
1356 EggFixed progress;
1357
1358 g_return_val_if_fail (EGG_IS_TIMELINE (timeline), 0);
1359
1360 priv = timeline->priv;
1361
1362 progress = egg_qdivx (EGG_INT_TO_FIXED (priv->current_frame_num),
1363 EGG_INT_TO_FIXED (priv->n_frames));
1364
1365 if (priv->direction == EGG_TIMELINE_BACKWARD)
1366 progress = CFX_ONE - progress;
1367
1368 return progress;
1369 }
1370
1371 /**
1372 * egg_timeline_get_direction:
1373 * @timeline: a #EggTimeline
1374 *
1375 * Retrieves the direction of the timeline set with
1376 * egg_timeline_set_direction().
1377 *
1378 * Return value: the direction of the timeline
1379 *
1380 * Since: 0.6
1381 */
1382 EggTimelineDirection
1383 egg_timeline_get_direction (EggTimeline *timeline)
1384 {
1385 g_return_val_if_fail (EGG_IS_TIMELINE (timeline), EGG_TIMELINE_FORWARD);
1386
1387 return timeline->priv->direction;
1388 }
1389
1390 /**
1391 * egg_timeline_set_direction:
1392 * @timeline: a #EggTimeline
1393 * @direction: the direction of the timeline
1394 *
1395 * Sets the direction of @timeline, either %EGG_TIMELINE_FORWARD or
1396 * %EGG_TIMELINE_BACKWARD.
1397 *
1398 * Since: 0.6
1399 */
1400 void
1401 egg_timeline_set_direction (EggTimeline *timeline,
1402 EggTimelineDirection direction)
1403 {
1404 EggTimelinePrivate *priv;
1405
1406 g_return_if_fail (EGG_IS_TIMELINE (timeline));
1407
1408 priv = timeline->priv;
1409
1410 if (priv->direction != direction)
1411 {
1412 priv->direction = direction;
1413
1414 if (priv->current_frame_num == 0)
1415 priv->current_frame_num = priv->n_frames;
1416
1417 g_object_notify (G_OBJECT (timeline), "direction");
1418 }
1419 }
1420
1421 /**
1422 * egg_timeline_get_delta:
1423 * @timeline: a #EggTimeline
1424 * @msecs: return location for the milliseconds elapsed since the last
1425 * frame, or %NULL
1426 *
1427 * Retrieves the number of frames and the amount of time elapsed since
1428 * the last EggTimeline::new-frame signal.
1429 *
1430 * This function is only useful inside handlers for the ::new-frame
1431 * signal, and its behaviour is undefined if the timeline is not
1432 * playing.
1433 *
1434 * Return value: the amount of frames elapsed since the last one
1435 *
1436 * Since: 0.6
1437 */
1438 guint
1439 egg_timeline_get_delta (EggTimeline *timeline,
1440 guint *msecs)
1441 {
1442 EggTimelinePrivate *priv;
1443
1444 g_return_val_if_fail (EGG_IS_TIMELINE (timeline), 0);
1445
1446 if (!egg_timeline_is_playing (timeline))
1447 {
1448 if (msecs)
1449 *msecs = 0;
1450
1451 return 0;
1452 }
1453
1454 priv = timeline->priv;
1455
1456 if (msecs)
1457 *msecs = timeline->priv->msecs_delta;
1458
1459 return priv->skipped_frames + 1;
1460 }
1461
1462 static inline void
1463 egg_timeline_add_marker_internal (EggTimeline *timeline,
1464 const gchar *marker_name,
1465 guint frame_num)
1466 {
1467 EggTimelinePrivate *priv = timeline->priv;
1468 TimelineMarker *marker;
1469 GSList *markers;
1470
1471 marker = g_hash_table_lookup (priv->markers_by_name, marker_name);
1472 if (G_UNLIKELY (marker))
1473 {
1474 g_warning ("A marker named `%s' already exists on frame %d",
1475 marker->name,
1476 marker->frame_num);
1477 return;
1478 }
1479
1480 marker = timeline_marker_new (marker_name, frame_num);
1481 g_hash_table_insert (priv->markers_by_name, marker->name, marker);
1482
1483 markers = g_hash_table_lookup (priv->markers_by_frame,
1484 GUINT_TO_POINTER (frame_num));
1485 if (!markers)
1486 {
1487 markers = g_slist_prepend (NULL, marker);
1488 g_hash_table_insert (priv->markers_by_frame,
1489 GUINT_TO_POINTER (frame_num),
1490 markers);
1491 }
1492 else
1493 {
1494 markers = g_slist_prepend (markers, marker);
1495 g_hash_table_replace (priv->markers_by_frame,
1496 GUINT_TO_POINTER (frame_num),
1497 markers);
1498 }
1499 }
1500
1501 /**
1502 * egg_timeline_add_marker_at_frame:
1503 * @timeline: a #EggTimeline
1504 * @marker_name: the unique name for this marker
1505 * @frame_num: the marker's frame
1506 *
1507 * Adds a named marker at @frame_num. Markers are unique string identifiers
1508 * for a specific frame. Once @timeline reaches @frame_num, it will emit
1509 * a ::marker-reached signal for each marker attached to that frame.
1510 *
1511 * A marker can be removed with egg_timeline_remove_marker(). The
1512 * timeline can be advanced to a marker using
1513 * egg_timeline_advance_to_marker().
1514 *
1515 * Since: 0.8
1516 */
1517 void
1518 egg_timeline_add_marker_at_frame (EggTimeline *timeline,
1519 const gchar *marker_name,
1520 guint frame_num)
1521 {
1522 g_return_if_fail (EGG_IS_TIMELINE (timeline));
1523 g_return_if_fail (marker_name != NULL);
1524 g_return_if_fail (frame_num <= egg_timeline_get_n_frames (timeline));
1525
1526 egg_timeline_add_marker_internal (timeline, marker_name, frame_num);
1527 }
1528
1529 /**
1530 * egg_timeline_add_marker_at_time:
1531 * @timeline: a #EggTimeline
1532 * @marker_name: the unique name for this marker
1533 * @msecs: position of the marker in milliseconds
1534 *
1535 * Time-based variant of egg_timeline_add_marker_at_frame().
1536 *
1537 * Adds a named marker at @msecs.
1538 *
1539 * Since: 0.8
1540 */
1541 void
1542 egg_timeline_add_marker_at_time (EggTimeline *timeline,
1543 const gchar *marker_name,
1544 guint msecs)
1545 {
1546 guint frame_num;
1547
1548 g_return_if_fail (EGG_IS_TIMELINE (timeline));
1549 g_return_if_fail (marker_name != NULL);
1550 g_return_if_fail (msecs <= egg_timeline_get_duration (timeline));
1551
1552 frame_num = msecs * timeline->priv->fps / 1000;
1553
1554 egg_timeline_add_marker_internal (timeline, marker_name, frame_num);
1555 }
1556
1557 /**
1558 * egg_timeline_list_markers:
1559 * @timeline: a #EggTimeline
1560 * @frame_num: the frame number to check, or -1
1561 * @n_markers: the number of markers returned
1562 *
1563 * Retrieves the list of markers at @frame_num. If @frame_num is a
1564 * negative integer, all the markers attached to @timeline will be
1565 * returned.
1566 *
1567 * Return value: a newly allocated, %NULL terminated string array
1568 * containing the names of the markers. Use g_strfreev() when
1569 * done.
1570 *
1571 * Since: 0.8
1572 */
1573 gchar **
1574 egg_timeline_list_markers (EggTimeline *timeline,
1575 gint frame_num,
1576 gsize *n_markers)
1577 {
1578 EggTimelinePrivate *priv;
1579 gchar **retval = NULL;
1580 gsize i;
1581
1582 g_return_val_if_fail (EGG_IS_TIMELINE (timeline), NULL);
1583
1584 priv = timeline->priv;
1585
1586 if (frame_num < 0)
1587 {
1588 GList *markers, *l;
1589
1590 markers = g_hash_table_get_keys (priv->markers_by_name);
1591 retval = g_new0 (gchar*, g_list_length (markers) + 1);
1592
1593 for (i = 0, l = markers; l != NULL; i++, l = l->next)
1594 retval[i] = g_strdup (l->data);
1595
1596 g_list_free (markers);
1597 }
1598 else
1599 {
1600 GSList *markers, *l;
1601
1602 markers = g_hash_table_lookup (priv->markers_by_frame,
1603 GUINT_TO_POINTER (frame_num));
1604 retval = g_new0 (gchar*, g_slist_length (markers) + 1);
1605
1606 for (i = 0, l = markers; l != NULL; i++, l = l->next)
1607 retval[i] = g_strdup (((TimelineMarker *) l->data)->name);
1608 }
1609
1610 if (n_markers)
1611 *n_markers = i;
1612
1613 return retval;
1614 }
1615
1616 /**
1617 * egg_timeline_advance_to_marker:
1618 * @timeline: a #EggTimeline
1619 * @marker_name: the name of the marker
1620 *
1621 * Advances @timeline to the frame of the given @marker_name.
1622 *
1623 * Since: 0.8
1624 */
1625 void
1626 egg_timeline_advance_to_marker (EggTimeline *timeline,
1627 const gchar *marker_name)
1628 {
1629 EggTimelinePrivate *priv;
1630 TimelineMarker *marker;
1631
1632 g_return_if_fail (EGG_IS_TIMELINE (timeline));
1633 g_return_if_fail (marker_name != NULL);
1634
1635 priv = timeline->priv;
1636
1637 marker = g_hash_table_lookup (priv->markers_by_name, marker_name);
1638 if (!marker)
1639 {
1640 g_warning ("No marker named `%s' found.", marker_name);
1641 return;
1642 }
1643
1644 egg_timeline_advance (timeline, marker->frame_num);
1645 }
1646
1647 /**
1648 * egg_timeline_remove_marker:
1649 * @timeline: a #EggTimeline
1650 * @marker_name: the name of the marker to remove
1651 *
1652 * Removes @marker_name, if found, from @timeline.
1653 *
1654 * Since: 0.8
1655 */
1656 void
1657 egg_timeline_remove_marker (EggTimeline *timeline,
1658 const gchar *marker_name)
1659 {
1660 EggTimelinePrivate *priv;
1661 TimelineMarker *marker;
1662 GSList *markers;
1663
1664 g_return_if_fail (EGG_IS_TIMELINE (timeline));
1665 g_return_if_fail (marker_name != NULL);
1666
1667 priv = timeline->priv;
1668
1669 marker = g_hash_table_lookup (priv->markers_by_name, marker_name);
1670 if (!marker)
1671 {
1672 g_warning ("No marker named `%s' found.", marker_name);
1673 return;
1674 }
1675
1676 /* remove from the list of markers at the same frame */
1677 markers = g_hash_table_lookup (priv->markers_by_frame,
1678 GUINT_TO_POINTER (marker->frame_num));
1679 if (G_LIKELY (markers))
1680 {
1681 markers = g_slist_remove (markers, marker);
1682 if (!markers)
1683 {
1684 /* no markers left, remove the slot */
1685 g_hash_table_remove (priv->markers_by_frame,
1686 GUINT_TO_POINTER (marker->frame_num));
1687 }
1688 else
1689 g_hash_table_replace (priv->markers_by_frame,
1690 GUINT_TO_POINTER (marker->frame_num),
1691 markers);
1692 }
1693 else
1694 {
1695 /* uh-oh, dangling marker; this should never happen */
1696 g_warning ("Dangling marker %s at frame %d",
1697 marker->name,
1698 marker->frame_num);
1699 }
1700
1701 /* this will take care of freeing the marker as well */
1702 g_hash_table_remove (priv->markers_by_name, marker_name);
1703 }
1704
1705 /**
1706 * egg_timeline_has_marker:
1707 * @timeline: a #EggTimeline
1708 * @marker_name: the name of the marker
1709 *
1710 * Checks whether @timeline has a marker set with the given name.
1711 *
1712 * Return value: %TRUE if the marker was found
1713 *
1714 * Since: 0.8
1715 */
1716 gboolean
1717 egg_timeline_has_marker (EggTimeline *timeline,
1718 const gchar *marker_name)
1719 {
1720 g_return_val_if_fail (EGG_IS_TIMELINE (timeline), FALSE);
1721 g_return_val_if_fail (marker_name != NULL, FALSE);
1722
1723 return NULL != g_hash_table_lookup (timeline->priv->markers_by_name,
1724 marker_name);
1725 }