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 }