Index: gtk/gtkmarshal.list
===================================================================
--- gtk/gtkmarshal.list	(revision 19790)
+++ gtk/gtkmarshal.list	(working copy)
@@ -15,6 +15,7 @@ NONE:ENUM,FLOAT,BOOL
 NONE:INT
 NONE:INT,INT
 NONE:INT,INT,POINTER
+NONE:INT,STRING
 NONE:NONE
 NONE:OBJECT
 NONE:POINTER
Index: gtk/gtk.h
===================================================================
--- gtk/gtk.h	(revision 19790)
+++ gtk/gtk.h	(working copy)
@@ -160,6 +160,7 @@
 #include <gtk/gtktextbuffer.h>
 #include <gtk/gtktextbufferrichtext.h>
 #include <gtk/gtktextview.h>
+#include <gtk/gtktimeline.h>
 #include <gtk/gtktoggleaction.h>
 #include <gtk/gtktogglebutton.h>
 #include <gtk/gtktoggletoolbutton.h>
Index: gtk/gtk.symbols
===================================================================
--- gtk/gtk.symbols	(revision 19790)
+++ gtk/gtk.symbols	(working copy)
@@ -3964,6 +3964,42 @@ gtk_theme_engine_get_type G_GNUC_CONST
 #endif
 #endif
 
+#if IN_HEADER(__GTK_TIMELINE_H__)
+#if IN_FILE(__GTK_TIMELINE_C__)
+gtk_timeline_add_marker_at_frame
+gtk_timeline_add_marker_at_time
+gtk_timeline_advance
+gtk_timeline_advance_to_marker
+gtk_timeline_clone
+gtk_timeline_get_current_frame
+gtk_timeline_get_delay
+gtk_timeline_get_delta
+gtk_timeline_get_direction
+gtk_timeline_get_duration
+gtk_timeline_get_loop
+gtk_timeline_get_n_frames
+gtk_timeline_get_progress
+gtk_timeline_get_speed
+gtk_timeline_get_type G_GNUC_CONST
+gtk_timeline_is_playing
+gtk_timeline_list_markers G_GNUC_MALLOC
+gtk_timeline_new
+gtk_timeline_new_for_duration
+gtk_timeline_pause
+gtk_timeline_remove_marker
+gtk_timeline_rewind
+gtk_timeline_set_delay
+gtk_timeline_set_direction
+gtk_timeline_set_duration
+gtk_timeline_set_loop
+gtk_timeline_set_n_frames
+gtk_timeline_set_speed
+gtk_timeline_skip
+gtk_timeline_start
+gtk_timeline_stop
+#endif
+#endif
+
 #if IN_HEADER(__GTK_TIPS_QUERY_H__)
 #if IN_FILE(__GTK_TIPS_QUERY_C__)
 gtk_tips_query_get_type G_GNUC_CONST
Index: gtk/gtkmarshalers.list
===================================================================
--- gtk/gtkmarshalers.list	(revision 19790)
+++ gtk/gtkmarshalers.list	(working copy)
@@ -102,6 +102,7 @@ VOID:POINTER,POINTER,POINTER
 VOID:POINTER,UINT
 VOID:STRING
 VOID:STRING,STRING
+VOID:STRING,UINT
 VOID:STRING,INT,POINTER
 VOID:STRING,UINT,FLAGS
 VOID:STRING,UINT,FLAGS,UINT
Index: gtk/gtkmain.c
===================================================================
--- gtk/gtkmain.c	(revision 19790)
+++ gtk/gtkmain.c	(working copy)
@@ -204,7 +204,8 @@ static const GDebugKey gtk_debug_keys[] 
   {"geometry", GTK_DEBUG_GEOMETRY},
   {"icontheme", GTK_DEBUG_ICONTHEME},
   {"printing", GTK_DEBUG_PRINTING},
-  {"builder", GTK_DEBUG_BUILDER}
+  {"builder", GTK_DEBUG_BUILDER},
+  {"scheduler", GTK_DEBUG_SCHEDULER},
 };
 #endif /* G_ENABLE_DEBUG */
 
Index: gtk/Makefile.am
===================================================================
--- gtk/Makefile.am	(revision 19790)
+++ gtk/Makefile.am	(working copy)
@@ -283,6 +283,7 @@ gtk_public_h_sources =          \
 	gtktexttag.h		\
 	gtktexttagtable.h	\
 	gtktextview.h		\
+	gtktimeline.h		\
 	gtktoggleaction.h	\
 	gtktogglebutton.h	\
 	gtktoggletoolbutton.h	\
@@ -554,6 +555,7 @@ gtk_base_c_sources =            \
 	gtktextutil.c		\
 	gtktextview.c		\
 	gtkthemes.c		\
+	gtktimeline.c		\
 	gtktoggleaction.c	\
 	gtktogglebutton.c	\
 	gtktoggletoolbutton.c	\
Index: gtk/gtkcontainer.c
===================================================================
--- gtk/gtkcontainer.c	(revision 19790)
+++ gtk/gtkcontainer.c	(working copy)
@@ -1537,6 +1537,17 @@ gtk_container_foreach_full (GtkContainer
     notify (callback_data);
 }
 
+/**
+ * gtk_container_set_focus_child:
+ * @container: a #GtkContainer
+ * @widget: a #GtkWidget or %NULL
+ *
+ * Sets, or unsets if @widget is %NULL, the focused child of @container.
+ *
+ * This function emits the GtkContainer::set_focus_child signal of
+ * @container. Implementations of #GtkContainer can override the
+ * default behaviour by overriding the class closure of this signal.
+ */
 void
 gtk_container_set_focus_child (GtkContainer *container,
 			       GtkWidget    *widget)
Index: gtk/gtktimeline.c
===================================================================
--- gtk/gtktimeline.c	(revision 0)
+++ gtk/gtktimeline.c	(revision 0)
@@ -0,0 +1,1588 @@
+/* gtktimeline.h: Class for time based operations
+ *
+ * Copyright (C) 2008 OpenedHand
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Based on similar code from Gtk
+ * Authors:
+ *      Matthew Allum <mallum@openedhand.com>
+ *      Emmanuele Bassi <ebassi@openedhand.com>
+ */
+
+/**
+ * SECTION:gtk-timeline
+ * @short_description: A class for time based operations
+ *
+ * #GtkTimeline is a base class for managing time based operations such
+ * as animations.
+ *
+ * Timelines operate in frames, but the duration of a timeline can be
+ * defined in milliseconds.
+ *
+ * #GtkTimeline is available since GTK+ 2.14.
+ */
+
+#include <config.h>
+
+#include "gtktimeline.h"
+#include "gtkmarshalers.h"
+#include "gtkprivate.h"
+#include "gtkalias.h"
+
+G_DEFINE_TYPE (GtkTimeline, gtk_timeline, G_TYPE_OBJECT);
+
+#define FPS_TO_INTERVAL(f)          (1000 / (f))
+#define GTK_TIMELINE_PRIORITY       (G_PRIORITY_DEFAULT + 30)
+
+struct _GtkTimelinePrivate
+{
+  GtkTimelineDirection direction;
+
+  guint timeout_id;
+  guint delay_id;
+
+  gint current_frame_num;
+
+  guint fps;
+  guint n_frames;
+  guint delay;
+  guint duration;
+
+  gint skipped_frames;
+
+  GTimeVal prev_frame_timeval;
+  guint  msecs_delta;
+
+  GHashTable *markers_by_frame;
+  GHashTable *markers_by_name;
+
+  guint loop : 1;
+};
+
+typedef struct {
+  gchar *name;
+  guint frame_num;
+  GQuark quark;
+} TimelineMarker;
+
+enum
+{
+  PROP_0,
+
+  PROP_FPS,
+  PROP_NUM_FRAMES,
+  PROP_LOOP,
+  PROP_DELAY,
+  PROP_DURATION,
+  PROP_DIRECTION
+};
+
+enum
+{
+  NEW_FRAME,
+  STARTED,
+  PAUSED,
+  COMPLETED,
+  MARKER_REACHED,
+
+  LAST_SIGNAL
+};
+
+static guint               timeline_signals[LAST_SIGNAL] = { 0 };
+
+static guint
+timeout_add (guint          interval,
+             GSourceFunc    func,
+             gpointer       data,
+             GDestroyNotify notify)
+{
+  return gdk_threads_add_timeout_full (GTK_TIMELINE_PRIORITY,
+                                       interval,
+                                       func, data, notify);
+}
+
+static void
+timeout_remove (guint tag)
+{
+  g_source_remove (tag);
+}
+
+static TimelineMarker *
+timeline_marker_new (const gchar *name,
+                     guint        frame_num)
+{
+  TimelineMarker *marker = g_slice_new0 (TimelineMarker);
+
+  marker->name = g_strdup (name);
+  marker->quark = g_quark_from_string (marker->name);
+  marker->frame_num = frame_num;
+
+  return marker;
+}
+
+static void
+timeline_marker_free (gpointer data)
+{
+  if (G_LIKELY (data))
+    {
+      TimelineMarker *marker = data;
+
+      g_free (marker->name);
+      g_slice_free (TimelineMarker, marker);
+    }
+}
+
+/* Object */
+
+static void
+gtk_timeline_set_property (GObject      *object,
+			       guint         prop_id,
+			       const GValue *value,
+			       GParamSpec   *pspec)
+{
+  GtkTimeline        *timeline;
+  GtkTimelinePrivate *priv;
+
+  timeline = GTK_TIMELINE(object);
+  priv = timeline->priv;
+
+  switch (prop_id)
+    {
+    case PROP_FPS:
+      gtk_timeline_set_speed (timeline, g_value_get_uint (value));
+      break;
+    case PROP_NUM_FRAMES:
+      priv->n_frames = g_value_get_uint (value);
+      break;
+    case PROP_LOOP:
+      priv->loop = g_value_get_boolean (value);
+      break;
+    case PROP_DELAY:
+      priv->delay = g_value_get_uint (value);
+      break;
+    case PROP_DURATION:
+      gtk_timeline_set_duration (timeline, g_value_get_uint (value));
+      break;
+    case PROP_DIRECTION:
+      gtk_timeline_set_direction (timeline, g_value_get_enum (value));
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+    }
+}
+
+static void
+gtk_timeline_get_property (GObject    *object,
+			       guint       prop_id,
+			       GValue     *value,
+			       GParamSpec *pspec)
+{
+  GtkTimeline        *timeline;
+  GtkTimelinePrivate *priv;
+
+  timeline = GTK_TIMELINE(object);
+  priv = timeline->priv;
+
+  switch (prop_id)
+    {
+    case PROP_FPS:
+      g_value_set_uint (value, priv->fps);
+      break;
+    case PROP_NUM_FRAMES:
+      g_value_set_uint (value, priv->n_frames);
+      break;
+    case PROP_LOOP:
+      g_value_set_boolean (value, priv->loop);
+      break;
+    case PROP_DELAY:
+      g_value_set_uint (value, priv->delay);
+      break;
+    case PROP_DURATION:
+      g_value_set_uint (value, priv->duration);
+      break;
+    case PROP_DIRECTION:
+      g_value_set_enum (value, priv->direction);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+    }
+}
+
+static void
+gtk_timeline_finalize (GObject *object)
+{
+  GtkTimelinePrivate *priv = GTK_TIMELINE (object)->priv;
+
+  g_hash_table_destroy (priv->markers_by_frame);
+  g_hash_table_destroy (priv->markers_by_name);
+
+  G_OBJECT_CLASS (gtk_timeline_parent_class)->finalize (object);
+}
+
+static void
+gtk_timeline_dispose (GObject *object)
+{
+  GtkTimeline *self = GTK_TIMELINE(object);
+  GtkTimelinePrivate *priv;
+
+  priv = self->priv;
+
+  if (priv->delay_id)
+    {
+      timeout_remove (priv->delay_id);
+      priv->delay_id = 0;
+    }
+
+  if (priv->timeout_id)
+    {
+      timeout_remove (priv->timeout_id);
+      priv->timeout_id = 0;
+    }
+
+  G_OBJECT_CLASS (gtk_timeline_parent_class)->dispose (object);
+}
+
+static void
+gtk_timeline_class_init (GtkTimelineClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->set_property = gtk_timeline_set_property;
+  object_class->get_property = gtk_timeline_get_property;
+  object_class->finalize     = gtk_timeline_finalize;
+  object_class->dispose      = gtk_timeline_dispose;
+
+  g_type_class_add_private (klass, sizeof (GtkTimelinePrivate));
+
+  /**
+   * GtkTimeline:fps:
+   *
+   * Timeline frames per second. Because of the nature of the main
+   * loop used by GTK+ this is to be considered a best approximation.
+   *
+   * Since: 2.14
+   */
+  g_object_class_install_property (object_class,
+                                   PROP_FPS,
+                                   g_param_spec_uint ("fps",
+                                                      "Frames Per Second",
+                                                      "Timeline frames per second",
+                                                      1, 1000,
+                                                      60,
+                                                      GTK_PARAM_READWRITE));
+  /**
+   * GtkTimeline:num-frames:
+   *
+   * Total number of frames for the timeline. #GtkTimeline may skip frames
+   * to keep up with a slow animation. If the last frame was skipped, the
+   * ::new-frame signal for the last frame will still be emitted.
+   *
+   * Since: 2.14
+   */
+  g_object_class_install_property (object_class,
+                                   PROP_NUM_FRAMES,
+                                   g_param_spec_uint ("num-frames",
+                                                      "Total number of frames",
+                                                      "Timelines total number of frames",
+                                                      1, G_MAXUINT,
+                                                      1,
+                                                      GTK_PARAM_READWRITE));
+  /**
+   * GtkTimeline:loop:
+   *
+   * Whether the timeline should automatically rewind and restart.
+   *
+   * Since: 2.14
+   */
+  g_object_class_install_property (object_class,
+                                   PROP_LOOP,
+                                   g_param_spec_boolean ("loop",
+                                                         "Loop",
+                                                         "Should the timeline automatically restart",
+                                                         FALSE,
+                                                         GTK_PARAM_READWRITE));
+  /**
+   * GtkTimeline:delay:
+   *
+   * A delay, in milliseconds, that should be observed by the
+   * timeline before actually starting.
+   *
+   * Since: 2.14
+   */
+  g_object_class_install_property (object_class,
+                                   PROP_DELAY,
+                                   g_param_spec_uint ("delay",
+                                                      "Delay",
+                                                      "Delay before start",
+                                                      0, G_MAXUINT,
+                                                      0,
+                                                      GTK_PARAM_READWRITE));
+  /**
+   * GtkTimeline:duration:
+   *
+   * Duration of the timeline in milliseconds, depending on the
+   * GtkTimeline:fps value.
+   *
+   * Since: 2.14
+   */
+  g_object_class_install_property (object_class,
+                                   PROP_DURATION,
+                                   g_param_spec_uint ("duration",
+                                                      "Duration",
+                                                      "Duration of the timeline in milliseconds",
+                                                      0, G_MAXUINT,
+                                                      1000,
+                                                      GTK_PARAM_READWRITE));
+  /**
+   * GtkTimeline:direction:
+   * 
+   * The direction of the timeline, either %GTK_TIMELINE_FORWARD or
+   * %GTK_TIMELINE_BACKWARD.
+   *
+   * Since: 2.14
+   */
+  g_object_class_install_property (object_class,
+                                   PROP_DIRECTION,
+                                   g_param_spec_enum ("direction",
+                                                      "Direction",
+                                                      "Direction of the timeline",
+                                                      GTK_TYPE_TIMELINE_DIRECTION,
+                                                      GTK_TIMELINE_FORWARD,
+                                                      GTK_PARAM_READWRITE));
+
+  /**
+   * GtkTimeline::new-frame:
+   * @timeline: the timeline which received the signal
+   * @frame_num: the number of the new frame between 1 and
+   *   GtkTimeline:num-frames
+   *
+   * The ::new-frame signal is emitted each time a new frame in the
+   * timeline is reached.
+   *
+   * Since: 2.14
+   */
+  timeline_signals[NEW_FRAME] =
+    g_signal_new ("new-frame",
+		  G_TYPE_FROM_CLASS (object_class),
+		  G_SIGNAL_RUN_LAST,
+		  G_STRUCT_OFFSET (GtkTimelineClass, new_frame),
+		  NULL, NULL,
+		  _gtk_marshal_VOID__INT,
+		  G_TYPE_NONE,
+		  1, G_TYPE_INT);
+  /**
+   * GtkTimeline::completed:
+   * @timeline: the #GtkTimeline which received the signal
+   *
+   * The ::completed signal is emitted when the timeline reaches the
+   * number of frames specified by the GtkTimeline:num-frames property.
+   *
+   * Since: 2.14
+   */
+  timeline_signals[COMPLETED] =
+    g_signal_new ("completed",
+		  G_TYPE_FROM_CLASS (object_class),
+		  G_SIGNAL_RUN_LAST,
+		  G_STRUCT_OFFSET (GtkTimelineClass, completed),
+		  NULL, NULL,
+		  _gtk_marshal_VOID__VOID,
+		  G_TYPE_NONE, 0);
+  /**
+   * GtkTimeline::started:
+   * @timeline: the #GtkTimeline which received the signal
+   *
+   * The ::started signal is emitted when the timeline starts its run.
+   * This might be as soon as gtk_timeline_start() is invoked or
+   * after the delay set in the GtkTimeline:delay property has
+   * expired.
+   *
+   * Since: 2.14
+   */
+  timeline_signals[STARTED] =
+    g_signal_new ("started",
+		  G_TYPE_FROM_CLASS (object_class),
+		  G_SIGNAL_RUN_LAST,
+		  G_STRUCT_OFFSET (GtkTimelineClass, started),
+		  NULL, NULL,
+		  _gtk_marshal_VOID__VOID,
+		  G_TYPE_NONE, 0);
+  /**
+   * GtkTimeline::paused:
+   * @timeline: the #GtkTimeline which received the signal
+   *
+   * The ::paused signal is emitted when gtk_timeline_pause() is invoked.
+   *
+   * Since: 2.14
+   */
+  timeline_signals[PAUSED] =
+    g_signal_new ("paused",
+		  G_TYPE_FROM_CLASS (object_class),
+		  G_SIGNAL_RUN_LAST,
+		  G_STRUCT_OFFSET (GtkTimelineClass, paused),
+		  NULL, NULL,
+		  _gtk_marshal_VOID__VOID,
+		  G_TYPE_NONE, 0);
+  /**
+   * GtkTimeline::marker-reached:
+   * @timeline: the #GtkTimeline which received the signal
+   * @marker_name: the name of the marker reached
+   * @frame_num: the frame number
+   *
+   * The ::marker-reached signal is emitted each time a timeline
+   * reaches a marker set with gtk_timeline_add_marker_at_frame()
+   * or gtk_timeline_add_marker_at_time(). This signal is
+   * detailed with the name of the marker as well, so it is
+   * possible to connect a callback to the ::marker-reached signal
+   * for a specific marker with:
+   *
+   * <informalexample><programlisting>
+   *   gtk_timeline_add_marker_at_frame (timeline, "foo", 24);
+   *   gtk_timeline_add_marker_at_frame (timeline, "bar", 48);
+   *
+   *   g_signal_connect (timeline, "marker-reached",
+   *                     G_CALLBACK (each_marker_reached), NULL);
+   *   g_signal_connect (timeline, "marker-reached::foo",
+   *                     G_CALLBACK (foo_marker_reached), NULL);
+   *   g_signal_connect (timeline, "marker-reached::bar",
+   *                     G_CALLBACK (bar_marker_reached), NULL);
+   * </programlisting></informalexample>
+   *
+   * In the example, the first callback will be invoked for both
+   * the "foo" and "bar" marker, while the second and third callbacks
+   * will be invoked for the "foo" or "bar" markers, respectively.
+   *
+   * Since: 2.14
+   */
+  timeline_signals[MARKER_REACHED] =
+    g_signal_new ("marker-reached",
+                  G_TYPE_FROM_CLASS (object_class),
+                  G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_DETAILED | G_SIGNAL_NO_HOOKS,
+                  G_STRUCT_OFFSET (GtkTimelineClass, marker_reached),
+                  NULL, NULL,
+                  _gtk_marshal_VOID__STRING_UINT,
+                  G_TYPE_NONE, 2,
+                  G_TYPE_STRING,
+                  G_TYPE_UINT);
+}
+
+static void
+gtk_timeline_init (GtkTimeline *self)
+{
+  GtkTimelinePrivate *priv;
+
+  self->priv = priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
+                                                   GTK_TYPE_TIMELINE,
+                                                   GtkTimelinePrivate);
+
+  priv->fps = 60;
+  priv->n_frames = 0;
+  priv->msecs_delta = 0;
+
+  priv->markers_by_frame = g_hash_table_new (NULL, NULL);
+  priv->markers_by_name = g_hash_table_new_full (g_str_hash, g_str_equal,
+                                                 NULL,
+                                                 timeline_marker_free);
+}
+
+static gboolean
+timeline_timeout_func (gpointer data)
+{
+  GtkTimeline        *timeline = data;
+  GtkTimelinePrivate *priv;
+  GTimeVal                timeval;
+  guint                   n_frames;
+  gulong                  msecs;
+
+  priv = timeline->priv;
+
+  g_object_ref (timeline);
+
+  /* Figure out potential frame skips */
+  g_get_current_time (&timeval);
+
+  if (!priv->prev_frame_timeval.tv_sec)
+    {
+      GTK_NOTE (SCHEDULER,
+                g_warning ("Timeline [%p] received timeout before being initialised!",
+                           timeline));
+      priv->prev_frame_timeval = timeval;
+    }
+
+  /* Interpolate the current frame based on the timeval of the
+   * previous frame */
+  msecs = (timeval.tv_sec - priv->prev_frame_timeval.tv_sec) * 1000;
+  msecs += (timeval.tv_usec - priv->prev_frame_timeval.tv_usec) / 1000;
+  priv->msecs_delta = msecs;
+  n_frames = msecs / (1000 / priv->fps);
+  if (n_frames == 0)
+    n_frames = 1;
+
+  priv->skipped_frames = n_frames - 1;
+
+  if (priv->skipped_frames)
+    GTK_NOTE (SCHEDULER,
+              g_message ("Timeline [%p], skipping %d frames\n",
+                         timeline,
+                         priv->skipped_frames));
+
+  priv->prev_frame_timeval = timeval;
+
+  /* Advance frames */
+  if (priv->direction == GTK_TIMELINE_FORWARD)
+    priv->current_frame_num += n_frames;
+  else
+    priv->current_frame_num -= n_frames;
+
+  /* If we have not reached the end of the timeline: */
+  if (!(
+      ((priv->direction == GTK_TIMELINE_FORWARD) &&
+       (priv->current_frame_num >= priv->n_frames)) ||
+      ((priv->direction == GTK_TIMELINE_BACKWARD) &&
+       (priv->current_frame_num <= 0))
+       ))
+    {
+      gint i;
+
+      /* Fire off signal */
+      g_signal_emit (timeline, timeline_signals[NEW_FRAME], 0,
+                     priv->current_frame_num);
+
+      for (i = priv->skipped_frames; i >= 0; i--)
+        {
+          gint frame_num = priv->current_frame_num - i;
+          GSList *markers, *l;
+          
+          markers = g_hash_table_lookup (priv->markers_by_frame,
+                                         GUINT_TO_POINTER (frame_num));
+          for (l = markers; l; l = l->next)
+            {
+              TimelineMarker *marker = l->data;
+
+              g_signal_emit (timeline, timeline_signals[MARKER_REACHED],
+                             marker->quark,
+                             marker->name,
+                             marker->frame_num);
+            }
+        }
+
+      /* Signal pauses timeline ? */
+      if (!priv->timeout_id)
+        {
+          g_object_unref (timeline);
+          return FALSE;
+        }
+
+      g_object_unref (timeline);
+      return TRUE;
+    }
+  else
+    {
+      /* Handle loop or stop */
+      GtkTimelineDirection saved_direction = priv->direction;
+      guint overflow_frame_num = priv->current_frame_num;
+      gint end_frame;
+  
+      /* In case the signal handlers want to take a peek... */
+      if (priv->direction == GTK_TIMELINE_FORWARD)
+        priv->current_frame_num = priv->n_frames;
+      else if (priv->direction == GTK_TIMELINE_BACKWARD)
+        priv->current_frame_num = 0;
+
+      end_frame = priv->current_frame_num;
+
+      /* Fire off signal */
+      g_signal_emit (timeline, timeline_signals[NEW_FRAME], 0,
+                     priv->current_frame_num);
+
+      /* Did the signal handler modify the current_frame_num */
+      if (priv->current_frame_num != end_frame)
+        {
+          g_object_unref (timeline);
+          return TRUE;
+        }
+
+      /* Note: If the new-frame signal handler paused the timeline
+       * on the last frame we will still go ahead and send the
+       * completed signal */
+      GTK_NOTE (SCHEDULER,
+                g_message ("Timeline [%p] completed (cur: %d, tot: %d, drop: %d)",
+                           timeline,
+                           priv->current_frame_num,
+                           priv->n_frames,
+                           n_frames - 1));
+
+      if (!priv->loop && priv->timeout_id)
+        {
+          /* We remove the timeout now, so that the completed signal handler
+           * may choose to re-start the timeline 
+           *
+           * ** Perhaps we should remove this earlier, and regardless
+           * of priv->loop. Are we limiting the things that could be done in
+           * the above new-frame signal handler */
+          timeout_remove (priv->timeout_id);
+          priv->timeout_id = 0;
+        }
+
+      g_signal_emit (timeline, timeline_signals[COMPLETED], 0);
+
+      /* Again check to see if the user has manually played with
+       * current_frame_num, before we finally stop or loop the timeline */
+
+      if (priv->current_frame_num != end_frame && 
+          !(/* Except allow moving from frame 0 -> n_frame (or vica-versa)
+               since these are considered equivalent */
+            (priv->current_frame_num == 0 && end_frame == priv->n_frames) ||
+            (priv->current_frame_num == priv->n_frames && end_frame == 0)
+          ))
+        {
+          g_object_unref (timeline);
+          return TRUE;
+        }
+
+      if (priv->loop)
+        {
+          /* We try and interpolate smoothly around a loop */
+          if (saved_direction == GTK_TIMELINE_FORWARD)
+            priv->current_frame_num = overflow_frame_num - priv->n_frames;
+          else
+            priv->current_frame_num = priv->n_frames + overflow_frame_num;
+
+          /* Or if the direction changed, we try and bounce */
+          if (priv->direction != saved_direction)
+            {
+              priv->current_frame_num = priv->n_frames
+                                        - priv->current_frame_num;
+            }
+
+          g_object_unref (timeline);
+          return TRUE;
+        }
+      else
+        {
+          gtk_timeline_rewind (timeline);
+
+          priv->prev_frame_timeval.tv_sec = 0;
+          priv->prev_frame_timeval.tv_usec = 0;
+
+          g_object_unref (timeline);
+          return FALSE;
+        }
+    }
+}
+
+static guint
+timeline_timeout_add (GtkTimeline *timeline,
+                      guint          interval,
+                      GSourceFunc    func,
+                      gpointer       data,
+                      GDestroyNotify notify)
+{
+  GtkTimelinePrivate *priv;
+  GTimeVal timeval;
+
+  priv = timeline->priv;
+
+  if (priv->prev_frame_timeval.tv_sec == 0)
+    {
+      g_get_current_time (&timeval);
+      priv->prev_frame_timeval = timeval;
+    }
+  priv->skipped_frames   = 0;
+  priv->msecs_delta      = 0;
+
+  return timeout_add (interval, func, data, notify);
+}
+
+static gboolean
+delay_timeout_func (gpointer data)
+{
+  GtkTimeline *timeline = data;
+  GtkTimelinePrivate *priv = timeline->priv;
+
+  priv->delay_id = 0;
+
+  priv->timeout_id = timeline_timeout_add (timeline,
+                                           FPS_TO_INTERVAL (priv->fps),
+                                           timeline_timeout_func,
+                                           timeline, NULL);
+
+  g_signal_emit (timeline, timeline_signals[STARTED], 0);
+
+  return FALSE;
+}
+
+/**
+ * gtk_timeline_start:
+ * @timeline: A #GtkTimeline
+ *
+ * Starts the #GtkTimeline playing.
+ **/
+void
+gtk_timeline_start (GtkTimeline *timeline)
+{
+  GtkTimelinePrivate *priv;
+
+  g_return_if_fail (GTK_IS_TIMELINE (timeline));
+
+  priv = timeline->priv;
+
+  if (priv->delay_id || priv->timeout_id)
+    return;
+
+  if (priv->n_frames == 0)
+    return;
+
+  if (priv->delay)
+    {
+      priv->delay_id = timeout_add (priv->delay,
+                                    delay_timeout_func,
+                                    timeline, NULL);
+    }
+  else
+    {
+      priv->timeout_id = timeline_timeout_add (timeline,
+                                               FPS_TO_INTERVAL (priv->fps),
+                                               timeline_timeout_func,
+                                               timeline, NULL);
+
+      g_signal_emit (timeline, timeline_signals[STARTED], 0);
+    }
+}
+
+/**
+ * gtk_timeline_pause:
+ * @timeline: A #GtkTimeline
+ *
+ * Pauses the #GtkTimeline on current frame
+ **/
+void
+gtk_timeline_pause (GtkTimeline *timeline)
+{
+  GtkTimelinePrivate *priv;
+
+  g_return_if_fail (GTK_IS_TIMELINE (timeline));
+
+  priv = timeline->priv;
+
+  if (priv->delay_id)
+    {
+      timeout_remove (priv->delay_id);
+      priv->delay_id = 0;
+    }
+
+  if (priv->timeout_id)
+    {
+      timeout_remove (priv->timeout_id);
+      priv->timeout_id = 0;
+    }
+
+  priv->prev_frame_timeval.tv_sec = 0;
+  priv->prev_frame_timeval.tv_usec = 0;
+
+  g_signal_emit (timeline, timeline_signals[PAUSED], 0);
+}
+
+/**
+ * gtk_timeline_stop:
+ * @timeline: A #GtkTimeline
+ *
+ * Stops the #GtkTimeline and moves to frame 0
+ **/
+void
+gtk_timeline_stop (GtkTimeline *timeline)
+{
+  gtk_timeline_pause (timeline);
+  gtk_timeline_rewind (timeline);
+}
+
+/**
+ * gtk_timeline_set_loop:
+ * @timeline: a #GtkTimeline
+ * @loop: %TRUE for enable looping
+ *
+ * Sets whether @timeline should loop.
+ */
+void
+gtk_timeline_set_loop (GtkTimeline *timeline,
+			   gboolean         loop)
+{
+  g_return_if_fail (GTK_IS_TIMELINE (timeline));
+
+  if (timeline->priv->loop != loop)
+    {
+      timeline->priv->loop = loop;
+
+      g_object_notify (G_OBJECT (timeline), "loop");
+    }
+}
+
+/**
+ * gtk_timeline_get_loop:
+ * @timeline: a #GtkTimeline
+ *
+ * Gets whether @timeline is looping
+ *
+ * Return value: %TRUE if the timeline is looping
+ */
+gboolean
+gtk_timeline_get_loop (GtkTimeline *timeline)
+{
+  g_return_val_if_fail (GTK_IS_TIMELINE (timeline), FALSE);
+
+  return timeline->priv->loop;
+}
+
+/**
+ * gtk_timeline_rewind:
+ * @timeline: A #GtkTimeline
+ *
+ * Rewinds #GtkTimeline to the first frame if its direction is
+ * GTK_TIMELINE_FORWARD and the last frame if it is
+ * GTK_TIMELINE_BACKWARD.
+ **/
+void
+gtk_timeline_rewind (GtkTimeline *timeline)
+{
+  GtkTimelinePrivate *priv;
+
+  g_return_if_fail (GTK_IS_TIMELINE (timeline));
+
+  priv = timeline->priv;
+
+  if (priv->direction == GTK_TIMELINE_FORWARD)
+    gtk_timeline_advance (timeline, 0);
+  else if (priv->direction == GTK_TIMELINE_BACKWARD)
+    gtk_timeline_advance (timeline, priv->n_frames);
+}
+
+/**
+ * gtk_timeline_skip:
+ * @timeline: A #GtkTimeline
+ * @n_frames: Number of frames to skip
+ *
+ * Advance timeline by requested number of frames.
+ **/
+void
+gtk_timeline_skip (GtkTimeline *timeline,
+                       guint            n_frames)
+{
+  GtkTimelinePrivate *priv;
+
+  g_return_if_fail (GTK_IS_TIMELINE (timeline));
+
+  priv = timeline->priv;
+
+  if (priv->direction == GTK_TIMELINE_FORWARD)
+    {
+      priv->current_frame_num += n_frames;
+
+      if (priv->current_frame_num > priv->n_frames)
+        priv->current_frame_num = 1;
+    }
+  else if (priv->direction == GTK_TIMELINE_BACKWARD)
+    {
+      priv->current_frame_num -= n_frames;
+
+      if (priv->current_frame_num < 1)
+        priv->current_frame_num = priv->n_frames - 1;
+    }
+}
+
+/**
+ * gtk_timeline_advance:
+ * @timeline: A #GtkTimeline
+ * @frame_num: Frame number to advance to
+ *
+ * Advance timeline to requested frame number
+ **/
+void
+gtk_timeline_advance (GtkTimeline *timeline,
+                          guint            frame_num)
+{
+  GtkTimelinePrivate *priv;
+
+  g_return_if_fail (GTK_IS_TIMELINE (timeline));
+
+  priv = timeline->priv;
+
+  priv->current_frame_num = CLAMP (frame_num, 0, priv->n_frames);
+}
+
+/**
+ * gtk_timeline_get_current_frame:
+ * @timeline: A #GtkTimeline
+ *
+ * Request the current frame number of the timeline.
+ *
+ * Return Value: current frame number
+ **/
+gint
+gtk_timeline_get_current_frame (GtkTimeline *timeline)
+{
+  g_return_val_if_fail (GTK_IS_TIMELINE (timeline), 0);
+
+  return timeline->priv->current_frame_num;
+}
+
+/**
+ * gtk_timeline_get_n_frames:
+ * @timeline: A #GtkTimeline
+ *
+ * Request the total number of frames for the #GtkTimeline.
+ *
+ * Return Value: Number of frames for this #GtkTimeline.
+ **/
+guint
+gtk_timeline_get_n_frames (GtkTimeline *timeline)
+{
+  g_return_val_if_fail (GTK_IS_TIMELINE (timeline), 0);
+
+  return timeline->priv->n_frames;
+}
+
+/**
+ * gtk_timeline_set_n_frames:
+ * @timeline: a #GtkTimeline
+ * @n_frames: the number of frames
+ *
+ * Sets the total number of frames for @timeline
+ */
+void
+gtk_timeline_set_n_frames (GtkTimeline *timeline,
+                               guint            n_frames)
+{
+  GtkTimelinePrivate *priv;
+
+  g_return_if_fail (GTK_IS_TIMELINE (timeline));
+  g_return_if_fail (n_frames > 0);
+
+  priv = timeline->priv;
+
+  if (priv->n_frames != n_frames)
+    {
+      priv->n_frames = n_frames;
+
+      g_object_notify (G_OBJECT (timeline), "num-frames");
+    }
+}
+
+/**
+ * gtk_timeline_set_speed:
+ * @timeline: A #GtkTimeline
+ * @fps: New speed of timeline as frames per second
+ *
+ * Set the speed in frames per second of the timeline.
+ **/
+void
+gtk_timeline_set_speed (GtkTimeline *timeline,
+                            guint            fps)
+{
+  GtkTimelinePrivate *priv;
+
+  g_return_if_fail (GTK_IS_TIMELINE (timeline));
+  g_return_if_fail (fps > 0);
+
+  priv = timeline->priv;
+
+  if (priv->fps != fps)
+    {
+      g_object_ref (timeline);
+
+      priv->fps = fps;
+
+      /* if the timeline is playing restart */
+      if (priv->timeout_id)
+        {
+          timeout_remove (priv->timeout_id);
+
+          priv->timeout_id = timeline_timeout_add (timeline,
+                                                   FPS_TO_INTERVAL (priv->fps),
+                                                   timeline_timeout_func,
+                                                   timeline, NULL);
+        }
+
+      g_object_notify (G_OBJECT (timeline), "fps");
+      g_object_unref (timeline);
+    }
+}
+
+/**
+ * gtk_timeline_get_speed:
+ * @timeline: a #GtkTimeline
+ *
+ * Gets the frames per second played by @timeline
+ *
+ * Return value: the number of frames per second.
+ */
+guint
+gtk_timeline_get_speed (GtkTimeline *timeline)
+{
+  g_return_val_if_fail (GTK_IS_TIMELINE (timeline), 0);
+
+  return timeline->priv->fps;
+}
+
+/**
+ * gtk_timeline_is_playing:
+ * @timeline: A #GtkTimeline
+ *
+ * Query state of a #GtkTimeline instance.
+ *
+ * Return Value: TRUE if timeline is currently playing, FALSE if not.
+ */
+gboolean
+gtk_timeline_is_playing (GtkTimeline *timeline)
+{
+  g_return_val_if_fail (GTK_IS_TIMELINE (timeline), FALSE);
+
+  return (timeline->priv->timeout_id != 0);
+}
+
+/**
+ * gtk_timeline_clone:
+ * @timeline: #GtkTimeline to duplicate.
+ *
+ * Create a new #GtkTimeline instance which has property values
+ * matching that of supplied timeline. The cloned timeline will not
+ * be started and will not be positioned to the current position of
+ * @timeline: you will have to start it with gtk_timeline_start().
+ *
+ * Return Value: a new #GtkTimeline, cloned from @timeline
+ *
+ * Since 0.4
+ */
+GtkTimeline *
+gtk_timeline_clone (GtkTimeline *timeline)
+{
+  GtkTimeline *copy;
+
+  g_return_val_if_fail (GTK_IS_TIMELINE (timeline), NULL);
+
+  copy = g_object_new (GTK_TYPE_TIMELINE,
+                       "fps", gtk_timeline_get_speed (timeline),
+                       "num-frames", gtk_timeline_get_n_frames (timeline),
+                       "loop", gtk_timeline_get_loop (timeline),
+                       "delay", gtk_timeline_get_delay (timeline),
+                       "direction", gtk_timeline_get_direction (timeline),
+                       NULL);
+
+  return copy;
+}
+
+/**
+ * gtk_timeline_new_for_duration:
+ * @msecs: Duration of the timeline in milliseconds
+ *
+ * Creates a new #GtkTimeline with a duration of @msecs using
+ * the value of the GtkTimeline:fps property to compute the
+ * equivalent number of frames. 
+ *
+ * Return value: the newly created #GtkTimeline
+ *
+ * Since: 0.6
+ */
+GtkTimeline *
+gtk_timeline_new_for_duration (guint msecs)
+{
+  return g_object_new (GTK_TYPE_TIMELINE,
+                       "duration", msecs,
+                       NULL);
+}
+
+/**
+ * gtk_timeline_new:
+ * @n_frames: the number of frames
+ * @fps: the number of frames per second
+ *
+ * Create a new  #GtkTimeline instance.
+ *
+ * Return Value: a new #GtkTimeline
+ */
+GtkTimeline*
+gtk_timeline_new (guint n_frames,
+		      guint fps)
+{
+  g_return_val_if_fail (n_frames > 0, NULL);
+  g_return_val_if_fail (fps > 0, NULL);
+
+  return g_object_new (GTK_TYPE_TIMELINE,
+		       "fps", fps,
+		       "num-frames", n_frames,
+		       NULL);
+}
+
+/**
+ * gtk_timeline_get_delay:
+ * @timeline: a #GtkTimeline
+ *
+ * Retrieves the delay set using gtk_timeline_set_delay().
+ *
+ * Return value: the delay in milliseconds.
+ *
+ * Since: 0.4
+ */
+guint
+gtk_timeline_get_delay (GtkTimeline *timeline)
+{
+  g_return_val_if_fail (GTK_IS_TIMELINE (timeline), 0);
+
+  return timeline->priv->delay;
+}
+
+/**
+ * gtk_timeline_set_delay:
+ * @timeline: a #GtkTimeline
+ * @msecs: delay in milliseconds
+ *
+ * Sets the delay, in milliseconds, before @timeline should start.
+ *
+ * Since: 0.4
+ */
+void
+gtk_timeline_set_delay (GtkTimeline *timeline,
+                            guint            msecs)
+{
+  GtkTimelinePrivate *priv;
+
+  g_return_if_fail (GTK_IS_TIMELINE (timeline));
+
+  priv = timeline->priv;
+
+  if (priv->delay != msecs)
+    {
+      priv->delay = msecs;
+      g_object_notify (G_OBJECT (timeline), "delay");
+    }
+}
+
+/**
+ * gtk_timeline_get_duration:
+ * @timeline: a #GtkTimeline
+ *
+ * Retrieves the duration of a #GtkTimeline in milliseconds.
+ * See gtk_timeline_set_duration().
+ *
+ * Return value: the duration of the timeline, in milliseconds.
+ *
+ * Since: 0.6
+ */
+guint
+gtk_timeline_get_duration (GtkTimeline *timeline)
+{
+  g_return_val_if_fail (GTK_IS_TIMELINE (timeline), 0);
+
+  return timeline->priv->duration;
+}
+
+/**
+ * gtk_timeline_set_duration:
+ * @timeline: a #GtkTimeline
+ * @msecs: duration of the timeline in milliseconds
+ *
+ * Sets the duration of the timeline, in milliseconds. The speed
+ * of the timeline depends on the GtkTimeline:fps setting.
+ *
+ * Since: 0.6
+ */
+void
+gtk_timeline_set_duration (GtkTimeline *timeline,
+                               guint            msecs)
+{
+  GtkTimelinePrivate *priv;
+
+  g_return_if_fail (GTK_IS_TIMELINE (timeline));
+
+  priv = timeline->priv;
+
+  if (priv->duration != msecs)
+    {
+      g_object_ref (timeline);
+
+      g_object_freeze_notify (G_OBJECT (timeline));
+
+      priv->duration = msecs;
+
+      priv->n_frames = priv->duration * priv->fps / 1000;
+
+      g_object_notify (G_OBJECT (timeline), "num-frames");
+      g_object_notify (G_OBJECT (timeline), "duration");
+
+      g_object_thaw_notify (G_OBJECT (timeline));
+      g_object_unref (timeline);
+    }
+}
+
+/**
+ * gtk_timeline_get_progress:
+ * @timeline: a #GtkTimeline
+ *
+ * The position of the timeline in a [0, 1] interval.
+ *
+ * Return value: the position of the timeline.
+ *
+ * Since: 0.6
+ */
+gdouble
+gtk_timeline_get_progress (GtkTimeline *timeline)
+{
+  GtkTimelinePrivate *priv;
+
+  g_return_val_if_fail (GTK_IS_TIMELINE (timeline), 0.);
+
+  priv = timeline->priv;
+
+  if (priv->direction == GTK_TIMELINE_FORWARD)
+    return CLAMP (priv->current_frame_num / priv->n_frames, 0.0, 1.0);
+  else
+    return CLAMP (priv->n_frames / priv->current_frame_num, 0.0, 1.0);
+}
+
+/**
+ * gtk_timeline_get_direction:
+ * @timeline: a #GtkTimeline
+ *
+ * Retrieves the direction of the timeline set with
+ * gtk_timeline_set_direction().
+ *
+ * Return value: the direction of the timeline
+ *
+ * Since: 0.6
+ */
+GtkTimelineDirection
+gtk_timeline_get_direction (GtkTimeline *timeline)
+{
+  g_return_val_if_fail (GTK_IS_TIMELINE (timeline), GTK_TIMELINE_FORWARD);
+
+  return timeline->priv->direction;
+}
+
+/**
+ * gtk_timeline_set_direction:
+ * @timeline: a #GtkTimeline
+ * @direction: the direction of the timeline
+ *
+ * Sets the direction of @timeline, either %GTK_TIMELINE_FORWARD or
+ * %GTK_TIMELINE_BACKWARD.
+ *
+ * Since: 0.6
+ */
+void
+gtk_timeline_set_direction (GtkTimeline          *timeline,
+                                GtkTimelineDirection  direction)
+{
+  GtkTimelinePrivate *priv;
+
+  g_return_if_fail (GTK_IS_TIMELINE (timeline));
+
+  priv = timeline->priv;
+
+  if (priv->direction != direction)
+    {
+      priv->direction = direction;
+
+      if (priv->current_frame_num == 0)
+        priv->current_frame_num = priv->n_frames;
+
+      g_object_notify (G_OBJECT (timeline), "direction");
+    }
+}
+
+/**
+ * gtk_timeline_get_delta:
+ * @timeline: a #GtkTimeline
+ * @msecs: return location for the milliseconds elapsed since the last
+ *   frame, or %NULL
+ *
+ * Retrieves the number of frames and the amount of time elapsed since
+ * the last GtkTimeline::new-frame signal.
+ *
+ * This function is only useful inside handlers for the ::new-frame
+ * signal, and its behaviour is undefined if the timeline is not
+ * playing.
+ *
+ * Return value: the amount of frames elapsed since the last one
+ *
+ * Since: 0.6
+ */
+guint
+gtk_timeline_get_delta (GtkTimeline *timeline,
+                            guint           *msecs)
+{
+  GtkTimelinePrivate *priv;
+
+  g_return_val_if_fail (GTK_IS_TIMELINE (timeline), 0);
+
+  if (!gtk_timeline_is_playing (timeline))
+    {
+      if (msecs)
+        *msecs = 0;
+
+      return 0;
+    }
+
+  priv = timeline->priv;
+
+  if (msecs)
+    *msecs = timeline->priv->msecs_delta;
+
+  return priv->skipped_frames + 1;
+}
+
+static inline void
+gtk_timeline_add_marker_internal (GtkTimeline *timeline,
+                                      const gchar     *marker_name,
+                                      guint            frame_num)
+{
+  GtkTimelinePrivate *priv = timeline->priv;
+  TimelineMarker *marker;
+  GSList *markers;
+
+  marker = g_hash_table_lookup (priv->markers_by_name, marker_name);
+  if (G_UNLIKELY (marker))
+    {
+      g_warning ("A marker named `%s' already exists on frame %d",
+                 marker->name,
+                 marker->frame_num);
+      return;
+    }
+
+  marker = timeline_marker_new (marker_name, frame_num);
+  g_hash_table_insert (priv->markers_by_name, marker->name, marker);
+
+  markers = g_hash_table_lookup (priv->markers_by_frame,
+                                 GUINT_TO_POINTER (frame_num));
+  if (!markers)
+    {
+      markers = g_slist_prepend (NULL, marker);
+      g_hash_table_insert (priv->markers_by_frame,
+                           GUINT_TO_POINTER (frame_num),
+                           markers);
+    }
+  else
+    {
+      markers = g_slist_prepend (markers, marker);
+      g_hash_table_replace (priv->markers_by_frame,
+                            GUINT_TO_POINTER (frame_num),
+                            markers);
+    }
+}
+
+/**
+ * gtk_timeline_add_marker_at_frame:
+ * @timeline: a #GtkTimeline
+ * @marker_name: the unique name for this marker
+ * @frame_num: the marker's frame
+ *
+ * Adds a named marker at @frame_num. Markers are unique string identifiers
+ * for a specific frame. Once @timeline reaches @frame_num, it will emit
+ * a ::marker-reached signal for each marker attached to that frame.
+ *
+ * A marker can be removed with gtk_timeline_remove_marker(). The
+ * timeline can be advanced to a marker using
+ * gtk_timeline_advance_to_marker().
+ *
+ * Since: 2.14
+ */
+void
+gtk_timeline_add_marker_at_frame (GtkTimeline *timeline,
+                                      const gchar     *marker_name,
+                                      guint            frame_num)
+{
+  g_return_if_fail (GTK_IS_TIMELINE (timeline));
+  g_return_if_fail (marker_name != NULL);
+  g_return_if_fail (frame_num <= gtk_timeline_get_n_frames (timeline));
+
+  gtk_timeline_add_marker_internal (timeline, marker_name, frame_num);
+}
+
+/**
+ * gtk_timeline_add_marker_at_time:
+ * @timeline: a #GtkTimeline
+ * @marker_name: the unique name for this marker
+ * @msecs: position of the marker in milliseconds
+ *
+ * Time-based variant of gtk_timeline_add_marker_at_frame().
+ *
+ * Adds a named marker at @msecs.
+ *
+ * Since: 2.14
+ */
+void
+gtk_timeline_add_marker_at_time (GtkTimeline *timeline,
+                                     const gchar     *marker_name,
+                                     guint            msecs)
+{
+  guint frame_num;
+
+  g_return_if_fail (GTK_IS_TIMELINE (timeline));
+  g_return_if_fail (marker_name != NULL);
+  g_return_if_fail (msecs <= gtk_timeline_get_duration (timeline));
+
+  frame_num = msecs * timeline->priv->fps / 1000;
+
+  gtk_timeline_add_marker_internal (timeline, marker_name, frame_num);
+}
+
+/**
+ * gtk_timeline_list_markers:
+ * @timeline: a #GtkTimeline
+ * @frame_num: the frame number to check, or -1
+ * @n_markers: the number of markers returned
+ *
+ * Retrieves the list of markers at @frame_num. If @frame_num is a
+ * negative integer, all the markers attached to @timeline will be
+ * returned.
+ *
+ * Return value: a newly allocated, %NULL terminated string array
+ *   containing the names of the markers. Use g_strfreev() when
+ *   done.
+ *
+ * Since: 2.14
+ */
+gchar **
+gtk_timeline_list_markers (GtkTimeline *timeline,
+                               gint             frame_num,
+                               guint           *n_markers)
+{
+  GtkTimelinePrivate *priv;
+  gchar **retval = NULL;
+  gint i;
+
+  g_return_val_if_fail (GTK_IS_TIMELINE (timeline), NULL);
+
+  priv = timeline->priv;
+
+  if (frame_num < 0)
+    {
+      GList *markers, *l;
+
+      markers = g_hash_table_get_keys (priv->markers_by_name);
+      retval = g_new0 (gchar*, g_list_length (markers) + 1);
+
+      for (i = 0, l = markers; l != NULL; i++, l = l->next)
+        retval[i] = g_strdup (l->data);
+
+      g_list_free (markers);
+    }
+  else
+    {
+      GSList *markers, *l;
+
+      markers = g_hash_table_lookup (priv->markers_by_frame,
+                                     GUINT_TO_POINTER (frame_num));
+      retval = g_new0 (gchar*, g_slist_length (markers) + 1);
+
+      for (i = 0, l = markers; l != NULL; i++, l = l->next)
+        retval[i] = g_strdup (l->data);
+    }
+
+  if (n_markers)
+    *n_markers = i;
+
+  return retval;
+}
+
+/**
+ * gtk_timeline_advance_to_marker:
+ * @timeline: a #GtkTimeline
+ * @marker_name: the name of the marker
+ *
+ * Advances @timeline to the frame of the given @marker_name.
+ *
+ * Since: 2.14
+ */
+void
+gtk_timeline_advance_to_marker (GtkTimeline *timeline,
+                                    const gchar     *marker_name)
+{
+  GtkTimelinePrivate *priv;
+  TimelineMarker *marker;
+
+  g_return_if_fail (GTK_IS_TIMELINE (timeline));
+  g_return_if_fail (marker_name != NULL);
+
+  priv = timeline->priv;
+
+  marker = g_hash_table_lookup (priv->markers_by_name, marker_name);
+  if (!marker)
+    {
+      g_warning ("No marker named `%s' found.", marker_name);
+      return;
+    }
+
+  gtk_timeline_advance (timeline, marker->frame_num);
+}
+
+/**
+ * gtk_timeline_remove_marker:
+ * @timeline: a #GtkTimeline
+ * @marker_name: the name of the marker to remove
+ *
+ * Removes @marker_name, if found, from @timeline.
+ *
+ * Since: 2.14
+ */
+void
+gtk_timeline_remove_marker (GtkTimeline *timeline,
+                                const gchar     *marker_name)
+{
+  GtkTimelinePrivate *priv;
+  TimelineMarker *marker;
+  GSList *markers;
+
+  g_return_if_fail (GTK_IS_TIMELINE (timeline));
+  g_return_if_fail (marker_name != NULL);
+
+  priv = timeline->priv;
+
+  marker = g_hash_table_lookup (priv->markers_by_name, marker_name);
+  if (!marker)
+    {
+      g_warning ("No marker named `%s' found.", marker_name);
+      return;
+    }
+
+  /* remove from the list of markers at the same frame */
+  markers = g_hash_table_lookup (priv->markers_by_frame,
+                                 GUINT_TO_POINTER (marker->frame_num));
+  if (G_LIKELY (markers))
+    {
+      markers = g_slist_remove (markers, marker);
+      if (!markers)
+        {
+          /* no markers left, remove the slot */
+          g_hash_table_remove (priv->markers_by_frame,
+                               GUINT_TO_POINTER (marker->frame_num));
+        }
+      else
+        g_hash_table_replace (priv->markers_by_frame,
+                              GUINT_TO_POINTER (marker->frame_num),
+                              markers);
+    }
+  else
+    {
+      /* uh-oh, dangling marker; this should never happen */
+      g_warning ("Dangling marker %s at frame %d",
+                 marker->name,
+                 marker->frame_num);
+    }
+
+  /* this will take care of freeing the marker as well */
+  g_hash_table_remove (priv->markers_by_name, marker_name);
+}
Index: gtk/gtktimeline.h
===================================================================
--- gtk/gtktimeline.h	(revision 0)
+++ gtk/gtktimeline.h	(revision 0)
@@ -0,0 +1,147 @@
+/* gtktimeline.h: Class for time based operations
+ *
+ * Copyright (C) 2008 OpenedHand
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Based on similar code from Clutter
+ * Authors:
+ *      Matthew Allum <mallum@openedhand.com>
+ *      Emmanuele Bassi <ebassi@openedhand.com>
+ */
+
+#ifndef __GTK_TIMELINE_H__
+#define __GTK_TIMELINE_H__
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define GTK_TYPE_TIMELINE               (gtk_timeline_get_type ())
+#define GTK_TIMELINE(obj)               (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTK_TYPE_TIMELINE, GtkTimeline))
+#define GTK_IS_TIMELINE(obj)            (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTK_TYPE_TIMELINE))
+#define GTK_TIMELINE_CLASS(klass)       (G_TYPE_CHECK_CLASS_CAST ((klass), GTK_TYPE_TIMELINE, GtkTimelineClass))
+#define GTK_IS_TIMELINE_CLASS(klass)    (G_TYPE_CHECK_CLASS_TYPE ((klass), GTK_TYPE_TIMELINE))
+#define GTK_TIMELINE_GET_CLASS(obj)     (G_TYPE_INSTANCE_GET_CLASS ((obj), GTK_TYPE_TIMELINE, GtkTimelineClass))
+
+/**
+ * GtkTimelineDirection:
+ * @GTK_TIMELINE_FORWARD: forward direction for a timeline
+ * @GTK_TIMELINE_BACKWARD: backward direction for a timeline
+ *
+ * The direction of a #GtkTimeline
+ *
+ * Since: 2.14
+ */
+typedef enum {
+  GTK_TIMELINE_FORWARD,
+  GTK_TIMELINE_BACKWARD
+} GtkTimelineDirection;
+
+typedef struct _GtkTimeline        GtkTimeline;
+typedef struct _GtkTimelineClass   GtkTimelineClass; 
+typedef struct _GtkTimelinePrivate GtkTimelinePrivate;
+
+struct _GtkTimeline
+{
+  /*< private >*/
+  GObject parent;
+
+  GtkTimelinePrivate *priv;
+};
+
+struct _GtkTimelineClass
+{
+  /*< private >*/
+  GObjectClass parent_class;
+  
+  /*< public >*/
+  void (* started)        (GtkTimeline *timeline);
+  void (* completed)      (GtkTimeline *timeline);
+  void (* paused)         (GtkTimeline *timeline);
+  
+  void (* new_frame)      (GtkTimeline *timeline,
+                           gint         frame_num);
+
+  void (* marker_reached) (GtkTimeline *timeline,
+                           const gchar *marker_name,
+                           gint         frame_num);
+
+  /*< private >*/
+  /* padding for future expansion */
+  void (*_gtk_timeline_1) (void);
+  void (*_gtk_timeline_2) (void);
+  void (*_gtk_timeline_3) (void);
+  void (*_gtk_timeline_4) (void);
+  void (*_gtk_timeline_5) (void);
+};
+
+GType                gtk_timeline_get_type            (void) G_GNUC_CONST;
+
+GtkTimeline *        gtk_timeline_new                 (guint        n_frames,
+                                                       guint        fps);
+GtkTimeline *        gtk_timeline_new_for_duration    (guint        msecs);
+GtkTimeline *        gtk_timeline_clone               (GtkTimeline *timeline);
+
+guint                gtk_timeline_get_duration        (GtkTimeline *timeline);
+void                 gtk_timeline_set_duration        (GtkTimeline *timeline,
+                                                       guint        msecs);
+guint                gtk_timeline_get_speed           (GtkTimeline *timeline);
+void                 gtk_timeline_set_speed           (GtkTimeline *timeline,
+                                                       guint        fps);
+GtkTimelineDirection gtk_timeline_get_direction       (GtkTimeline *timeline);
+void                 gtk_timeline_set_direction       (GtkTimeline *timeline,
+                                                       GtkTimelineDirection direction);
+void                 gtk_timeline_start               (GtkTimeline *timeline);
+void                 gtk_timeline_pause               (GtkTimeline *timeline);
+void                 gtk_timeline_stop                (GtkTimeline *timeline);
+void                 gtk_timeline_set_loop            (GtkTimeline *timeline,
+                                                       gboolean     loop);
+gboolean             gtk_timeline_get_loop            (GtkTimeline *timeline);
+void                 gtk_timeline_rewind              (GtkTimeline *timeline);
+void                 gtk_timeline_skip                (GtkTimeline *timeline,
+                                                       guint        n_frames);
+void                 gtk_timeline_advance             (GtkTimeline *timeline,
+                                                       guint        frame_num);
+gint                 gtk_timeline_get_current_frame   (GtkTimeline *timeline);
+gdouble              gtk_timeline_get_progress        (GtkTimeline *timeline);
+void                 gtk_timeline_set_n_frames        (GtkTimeline *timeline,
+                                                       guint        n_frames);
+guint                gtk_timeline_get_n_frames        (GtkTimeline *timeline);
+gboolean             gtk_timeline_is_playing          (GtkTimeline *timeline);
+void                 gtk_timeline_set_delay           (GtkTimeline *timeline,
+                                                       guint        msecs);
+guint                gtk_timeline_get_delay           (GtkTimeline *timeline);
+guint                gtk_timeline_get_delta           (GtkTimeline *timeline,
+                                                       guint       *msecs);
+
+void                 gtk_timeline_add_marker_at_frame (GtkTimeline *timeline,
+                                                       const gchar *marker_name,
+                                                       guint        frame_num);
+void                 gtk_timeline_add_marker_at_time  (GtkTimeline *timeline,
+                                                       const gchar *marker_name,
+                                                       guint        msecs);
+void                 gtk_timeline_remove_marker       (GtkTimeline *timeline,
+                                                       const gchar *marker_name);
+gchar **             gtk_timeline_list_markers        (GtkTimeline *timeline,
+                                                       gint         frame_num,
+                                                       guint       *n_markers) G_GNUC_MALLOC;
+void                 gtk_timeline_advance_to_marker   (GtkTimeline *timeline,
+                                                       const gchar *marker_name);
+
+G_END_DECLS
+
+#endif /* __GTK_TIMELINE_H__ */
Index: gtk/gtkdebug.h
===================================================================
--- gtk/gtkdebug.h	(revision 19790)
+++ gtk/gtkdebug.h	(working copy)
@@ -43,7 +43,8 @@ typedef enum {
   GTK_DEBUG_GEOMETRY    = 1 << 8,
   GTK_DEBUG_ICONTHEME   = 1 << 9,
   GTK_DEBUG_PRINTING	= 1 << 10,
-  GTK_DEBUG_BUILDER	= 1 << 11
+  GTK_DEBUG_BUILDER	= 1 << 11,
+  GTK_DEBUG_SCHEDULER   = 1 << 12
 } GtkDebugFlag;
 
 #ifdef G_ENABLE_DEBUG
