[gtk+/wip/frame-synchronization: 2/33] Add GdkPaintClock



commit 090f1cc8b1467e4906e77bfce2a763bea0615a1d
Author: Owen W. Taylor <otaylor fishsoup net>
Date:   Wed Oct 3 18:34:01 2012 -0400

    Add GdkPaintClock
    
    Add an object GdkPaintClock that we associate with a GdkWindow.
    This tracks when the window needs to be repainted, and will also
    be used for other operations in the future like relayout and
    updating animations.
    
    Based on a patch from Havoc Pennington:
    
     https://mail.gnome.org/archives/gtk-devel-list/2010-October/msg00004.html
    
    https://bugzilla.gnome.org/show_bug.cgi?id=685460

 gdk/Makefile.am         |    4 +
 gdk/gdkinternals.h      |    2 +
 gdk/gdkpaintclock.c     |  287 +++++++++++++++++++++++++++++++++++++++++++++++
 gdk/gdkpaintclock.h     |   77 +++++++++++++
 gdk/gdkpaintclockidle.c |  182 ++++++++++++++++++++++++++++++
 gdk/gdkpaintclockidle.h |   68 +++++++++++
 gdk/gdkwindow.c         |  278 +++++++++++++++++++++++++++++++++++++---------
 gdk/gdkwindow.h         |    6 +
 gtk/gtkwidget.c         |   48 ++++++++
 gtk/gtkwidget.h         |    2 +
 10 files changed, 902 insertions(+), 52 deletions(-)
---
diff --git a/gdk/Makefile.am b/gdk/Makefile.am
index b6b74ce..4bd1c9b 100644
--- a/gdk/Makefile.am
+++ b/gdk/Makefile.am
@@ -80,6 +80,7 @@ gdk_public_h_sources = 				\
 	gdkkeysyms-compat.h			\
 	gdkmain.h				\
 	gdkpango.h				\
+	gdkpaintclock.h				\
 	gdkpixbuf.h				\
 	gdkprivate.h				\
 	gdkproperty.h				\
@@ -101,6 +102,7 @@ gdk_private_headers = 				\
 	gdkdisplaymanagerprivate.h		\
 	gdkdisplayprivate.h			\
 	gdkdndprivate.h				\
+	gdkpaintclockidle.h			\
 	gdkscreenprivate.h			\
 	gdkinternals.h				\
 	gdkintl.h				\
@@ -125,6 +127,8 @@ gdk_c_sources = 				\
 	gdkkeys.c				\
 	gdkkeyuni.c				\
 	gdkoffscreenwindow.c			\
+	gdkpaintclock.c				\
+	gdkpaintclockidle.c			\
 	gdkpango.c				\
 	gdkpixbuf-drawable.c			\
 	gdkrectangle.c				\
diff --git a/gdk/gdkinternals.h b/gdk/gdkinternals.h
index 35bbb7f..6760535 100644
--- a/gdk/gdkinternals.h
+++ b/gdk/gdkinternals.h
@@ -263,6 +263,8 @@ struct _GdkWindow
   gulong device_changed_handler_id;
 
   guint num_offscreen_children;
+
+  GdkPaintClock *paint_clock; /* NULL to use from parent or default */
 };
 
 #define GDK_WINDOW_TYPE(d) (((GDK_WINDOW (d)))->window_type)
diff --git a/gdk/gdkpaintclock.c b/gdk/gdkpaintclock.c
new file mode 100644
index 0000000..c4a0dad
--- /dev/null
+++ b/gdk/gdkpaintclock.c
@@ -0,0 +1,287 @@
+/* GDK - The GIMP Drawing Kit
+ * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
+ *
+ * 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.
+ */
+
+/*
+ * Modified by the GTK+ Team and others 1997-2010.  See the AUTHORS
+ * file for a list of people on the GTK+ Team.  See the ChangeLog
+ * files for a list of changes.  These files are distributed with
+ * GTK+ at ftp://ftp.gtk.org/pub/gtk/.
+ */
+
+#include "config.h"
+
+#include "gdkpaintclock.h"
+
+/**
+ * SECTION:paintclock
+ * @Short_description: Paint clock syncs painting to a window or display
+ * @Title: Paint clock
+ *
+ * A #GdkPaintClock tells the application when to repaint a window.
+ * This may be synced to the vertical refresh rate of the monitor, for
+ * example. Even when the paint clock uses a simple timer rather than
+ * a hardware-based vertical sync, the paint clock helps because it
+ * ensures everything paints at the same time (reducing the total
+ * number of frames). The paint clock can also automatically stop
+ * painting when it knows the frames will not be visible, or scale back
+ * animation framerates.
+ *
+ * #GdkPaintClock is designed to be compatible with an OpenGL-based
+ * implementation or with mozRequestAnimationFrame in Firefox,
+ * for example.
+ *
+ * A paint clock is idle until someone requests a frame with
+ * gdk_paint_clock_request_frame(). At that time, the paint clock
+ * emits its GdkPaintClock:frame-requested signal if no frame was
+ * already pending.
+ *
+ * At some later time after the frame is requested, the paint clock
+ * MAY indicate that a frame should be painted. To paint a frame the
+ * clock will: Emit GdkPaintClock:before-paint; update the frame time
+ * in the default handler for GdkPaintClock:before-paint; emit
+ * GdkPaintClock:paint; emit GdkPaintClock:after-paint.  The app
+ * should paint in a handler for the paint signal.
+ *
+ * If a given frame is not painted (the clock is idle), the frame time
+ * should still update to a conceptual "last frame." i.e. the frame
+ * time will keep moving forward roughly with wall clock time.
+ *
+ * The frame time is in milliseconds. However, it should not be
+ * thought of as having any particular relationship to wall clock
+ * time. Unlike wall clock time, it "snaps" to conceptual frame times
+ * so is low-resolution; it is guaranteed to never move backward (so
+ * say you reset your computer clock, the paint clock will not reset);
+ * and the frame clock is allowed to drift. For example nicer
+ * results when painting with vertical refresh sync may be obtained by
+ * painting as rapidly as possible, but always incrementing the frame
+ * time by the frame length on each frame. This results in a frame
+ * time that doesn't have a lot to do with wall clock time.
+ */
+
+G_DEFINE_INTERFACE (GdkPaintClock, gdk_paint_clock, G_TYPE_OBJECT)
+
+enum {
+  FRAME_REQUESTED,
+  BEFORE_PAINT,
+  PAINT,
+  AFTER_PAINT,
+  LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL];
+
+static void
+gdk_paint_clock_default_init (GdkPaintClockInterface *iface)
+{
+  /**
+   * GdkPaintClock::frame-requested:
+   * @clock: the paint clock emitting the signal
+   *
+   * This signal is emitted when a frame is not pending, and
+   * gdk_paint_clock_request_frame() is called to request a frame.
+   */
+  signals[FRAME_REQUESTED] =
+    g_signal_new (g_intern_static_string ("frame-requested"),
+                  GDK_TYPE_PAINT_CLOCK,
+                  G_SIGNAL_RUN_LAST,
+                  0,
+                  NULL, NULL,
+                  g_cclosure_marshal_VOID__VOID,
+                  G_TYPE_NONE, 0);
+
+  /**
+   * GdkPaintClock::before-paint:
+   * @clock: the paint clock emitting the signal
+   *
+   * This signal is emitted immediately before the paint signal and
+   * indicates that the frame time has been updated, and signal
+   * handlers should perform any preparatory work before painting.
+   */
+  signals[BEFORE_PAINT] =
+    g_signal_new (g_intern_static_string ("before-paint"),
+                  GDK_TYPE_PAINT_CLOCK,
+                  G_SIGNAL_RUN_LAST,
+                  0,
+                  NULL, NULL,
+                  g_cclosure_marshal_VOID__VOID,
+                  G_TYPE_NONE, 0);
+
+  /**
+   * GdkPaintClock::paint:
+   * @clock: the paint clock emitting the signal
+   *
+   * Signal handlers for this signal should paint the window, screen,
+   * or whatever they normally paint.
+   */
+  signals[PAINT] =
+    g_signal_new (g_intern_static_string ("paint"),
+                  GDK_TYPE_PAINT_CLOCK,
+                  G_SIGNAL_RUN_LAST,
+                  0,
+                  NULL, NULL,
+                  g_cclosure_marshal_VOID__VOID,
+                  G_TYPE_NONE, 0);
+
+  /**
+   * GdkPaintClock::after-paint:
+   * @clock: the paint clock emitting the signal
+   *
+   * This signal is emitted immediately after the paint signal and
+   * allows signal handlers to do anything they'd like to do after
+   * painting has been completed. This is a relatively good time to do
+   * "expensive" processing in order to get it done in between frames.
+   */
+  signals[AFTER_PAINT] =
+    g_signal_new (g_intern_static_string ("after-paint"),
+                  GDK_TYPE_PAINT_CLOCK,
+                  G_SIGNAL_RUN_LAST,
+                  0,
+                  NULL, NULL,
+                  g_cclosure_marshal_VOID__VOID,
+                  G_TYPE_NONE, 0);
+}
+
+/**
+ * gdk_paint_clock_get_frame_time:
+ * @clock: the clock
+ *
+ * Gets the time that should currently be used for animations.  Inside
+ * a paint, it's the time used to compute the animation position of
+ * everything in a frame. Outside a paint, it's the time of the
+ * conceptual "previous frame," which may be either the actual
+ * previous frame time, or if that's too old, an updated time.
+ *
+ * The returned time has no relationship to wall clock time.  It
+ * increases roughly at 1 millisecond per wall clock millisecond, and
+ * it never decreases, but its value is only meaningful relative to
+ * previous paint clock times.
+ *
+ *
+ * Since: 3.0
+ * Return value: a timestamp in milliseconds
+ */
+guint64
+gdk_paint_clock_get_frame_time (GdkPaintClock *clock)
+{
+  g_return_val_if_fail (GDK_IS_PAINT_CLOCK (clock), 0);
+
+  return GDK_PAINT_CLOCK_GET_IFACE (clock)->get_frame_time (clock);
+}
+
+/**
+ * gdk_paint_clock_request_frame:
+ * @clock: the clock
+ *
+ * Asks the frame clock to paint a frame. The frame
+ * may or may not ever be painted (the frame clock may
+ * stop itself for whatever reason), but the goal in
+ * normal circumstances would be to paint the frame
+ * at the next expected frame time. For example
+ * if the clock is running at 60fps the frame would
+ * ideally be painted within 1000/60=16 milliseconds.
+ *
+ * Since: 3.0
+ */
+void
+gdk_paint_clock_request_frame (GdkPaintClock *clock)
+{
+  g_return_if_fail (GDK_IS_PAINT_CLOCK (clock));
+
+  GDK_PAINT_CLOCK_GET_IFACE (clock)->request_frame (clock);
+}
+
+/**
+ * gdk_paint_clock_get_frame_requested:
+ * @clock: the clock
+ *
+ * Gets whether a frame paint has been requested but has not been
+ * performed.
+ *
+ *
+ * Since: 3.0
+ * Return value: TRUE if a frame paint is pending
+ */
+gboolean
+gdk_paint_clock_get_frame_requested (GdkPaintClock *clock)
+{
+  g_return_val_if_fail (GDK_IS_PAINT_CLOCK (clock), FALSE);
+
+  return GDK_PAINT_CLOCK_GET_IFACE (clock)->get_frame_requested (clock);
+}
+
+/**
+ * gdk_paint_clock_get_frame_time_val:
+ * @clock: the clock
+ * @timeval: #GTimeVal to fill in with frame time
+ *
+ * Like gdk_paint_clock_get_frame_time() but returns the time as a
+ * #GTimeVal which may be handy with some APIs (such as
+ * #GdkPixbufAnimation).
+ */
+void
+gdk_paint_clock_get_frame_time_val (GdkPaintClock *clock,
+                                    GTimeVal      *timeval)
+{
+  guint64 time_ms;
+
+  g_return_if_fail (GDK_IS_PAINT_CLOCK (clock));
+
+  time_ms = gdk_paint_clock_get_frame_time (clock);
+
+  timeval->tv_sec = time_ms / 1000;
+  timeval->tv_usec = (time_ms % 1000) * 1000;
+}
+
+/**
+ * gdk_paint_clock_frame_requested:
+ * @clock: the clock
+ *
+ * Emits the frame-requested signal. Used in implementations of the
+ * #GdkPaintClock interface.
+ */
+void
+gdk_paint_clock_frame_requested (GdkPaintClock *clock)
+{
+  g_return_if_fail (GDK_IS_PAINT_CLOCK (clock));
+
+  g_signal_emit (G_OBJECT (clock),
+                 signals[FRAME_REQUESTED], 0);
+}
+
+/**
+ * gdk_paint_clock_paint:
+ * @clock: the clock
+ *
+ * Emits the before-paint, paint, and after-paint signals. Used in
+ * implementations of the #GdkPaintClock interface.
+ */
+void
+gdk_paint_clock_paint (GdkPaintClock *clock)
+{
+  g_return_if_fail (GDK_IS_PAINT_CLOCK (clock));
+
+  g_signal_emit (G_OBJECT (clock),
+                 signals[BEFORE_PAINT], 0);
+
+  g_signal_emit (G_OBJECT (clock),
+                 signals[PAINT], 0);
+
+  g_signal_emit (G_OBJECT (clock),
+                 signals[AFTER_PAINT], 0);
+}
diff --git a/gdk/gdkpaintclock.h b/gdk/gdkpaintclock.h
new file mode 100644
index 0000000..0741722
--- /dev/null
+++ b/gdk/gdkpaintclock.h
@@ -0,0 +1,77 @@
+/* GDK - The GIMP Drawing Kit
+ * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
+ *
+ * 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.
+ */
+
+/*
+ * Modified by the GTK+ Team and others 1997-2010.  See the AUTHORS
+ * file for a list of people on the GTK+ Team.  See the ChangeLog
+ * files for a list of changes.  These files are distributed with
+ * GTK+ at ftp://ftp.gtk.org/pub/gtk/.
+ */
+
+#if !defined (__GDK_H_INSIDE__) && !defined (GDK_COMPILATION)
+#error "Only <gdk/gdk.h> can be included directly."
+#endif
+
+#ifndef __GDK_PAINT_CLOCK_H__
+#define __GDK_PAINT_CLOCK_H__
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define GDK_TYPE_PAINT_CLOCK             (gdk_paint_clock_get_type ())
+#define GDK_PAINT_CLOCK(obj)             (G_TYPE_CHECK_INSTANCE_CAST ((obj), GDK_TYPE_PAINT_CLOCK, GdkPaintClock))
+#define GDK_IS_PAINT_CLOCK(obj)          (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GDK_TYPE_PAINT_CLOCK))
+#define GDK_PAINT_CLOCK_GET_IFACE(inst)  (G_TYPE_INSTANCE_GET_INTERFACE ((inst), GDK_TYPE_PAINT_CLOCK, GdkPaintClockInterface))
+
+typedef struct _GdkPaintClock          GdkPaintClock;
+typedef struct _GdkPaintClockInterface GdkPaintClockInterface;
+
+struct _GdkPaintClockInterface
+{
+  GTypeInterface		   base_iface;
+
+  guint64  (* get_frame_time)      (GdkPaintClock *clock);
+  void     (* request_frame)       (GdkPaintClock *clock);
+  gboolean (* get_frame_requested) (GdkPaintClock *clock);
+
+  /* signals */
+  /* void (* frame_requested)    (GdkPaintClock *clock); */
+  /* void (* before_paint)       (GdkPaintClock *clock); */
+  /* void (* paint)              (GdkPaintClock *clock); */
+  /* void (* after_paint)        (GdkPaintClock *clock); */
+};
+
+GType    gdk_paint_clock_get_type             (void) G_GNUC_CONST;
+
+guint64  gdk_paint_clock_get_frame_time      (GdkPaintClock *clock);
+void     gdk_paint_clock_request_frame       (GdkPaintClock *clock);
+gboolean gdk_paint_clock_get_frame_requested (GdkPaintClock *clock);
+
+/* Convenience API */
+void  gdk_paint_clock_get_frame_time_val (GdkPaintClock  *clock,
+                                          GTimeVal       *timeval);
+
+/* Signal emitters (used in paint clock implementations) */
+void     gdk_paint_clock_frame_requested     (GdkPaintClock *clock);
+void     gdk_paint_clock_paint               (GdkPaintClock *clock);
+
+G_END_DECLS
+
+#endif /* __GDK_PAINT_CLOCK_H__ */
diff --git a/gdk/gdkpaintclockidle.c b/gdk/gdkpaintclockidle.c
new file mode 100644
index 0000000..c967af54
--- /dev/null
+++ b/gdk/gdkpaintclockidle.c
@@ -0,0 +1,182 @@
+/* GDK - The GIMP Drawing Kit
+ * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
+ *
+ * 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.
+ */
+
+/*
+ * Modified by the GTK+ Team and others 1997-2010.  See the AUTHORS
+ * file for a list of people on the GTK+ Team.  See the ChangeLog
+ * files for a list of changes.  These files are distributed with
+ * GTK+ at ftp://ftp.gtk.org/pub/gtk/.
+ */
+
+#include "config.h"
+
+#include "gdkpaintclockidle.h"
+#include "gdk.h"
+
+struct _GdkPaintClockIdlePrivate
+{
+  GTimer *timer;
+  /* timer_base is used to avoid ever going backward */
+  guint64 timer_base;
+  guint64 frame_time;
+
+  guint idle_id;
+
+  unsigned int in_paint : 1;
+};
+
+static void gdk_paint_clock_idle_finalize             (GObject                *object);
+static void gdk_paint_clock_idle_interface_init       (GdkPaintClockInterface *iface);
+
+G_DEFINE_TYPE_WITH_CODE (GdkPaintClockIdle, gdk_paint_clock_idle, G_TYPE_OBJECT,
+			 G_IMPLEMENT_INTERFACE (GDK_TYPE_PAINT_CLOCK,
+						gdk_paint_clock_idle_interface_init))
+
+static void
+gdk_paint_clock_idle_class_init (GdkPaintClockIdleClass *klass)
+{
+  GObjectClass *gobject_class = (GObjectClass*) klass;
+
+  gobject_class->finalize     = gdk_paint_clock_idle_finalize;
+
+  g_type_class_add_private (klass, sizeof (GdkPaintClockIdlePrivate));
+}
+
+static void
+gdk_paint_clock_idle_init (GdkPaintClockIdle *paint_clock_idle)
+{
+  GdkPaintClockIdlePrivate *priv;
+
+  paint_clock_idle->priv = G_TYPE_INSTANCE_GET_PRIVATE (paint_clock_idle,
+                                                        GDK_TYPE_PAINT_CLOCK_IDLE,
+                                                        GdkPaintClockIdlePrivate);
+  priv = paint_clock_idle->priv;
+
+  priv->timer = g_timer_new ();
+}
+
+static void
+gdk_paint_clock_idle_finalize (GObject *object)
+{
+  GdkPaintClockIdlePrivate *priv = GDK_PAINT_CLOCK_IDLE (object)->priv;
+
+  g_timer_destroy (priv->timer);
+
+  G_OBJECT_CLASS (gdk_paint_clock_idle_parent_class)->finalize (object);
+}
+
+static guint64
+compute_frame_time (GdkPaintClockIdle *idle)
+{
+  GdkPaintClockIdlePrivate *priv = idle->priv;
+  guint64 computed_frame_time;
+  guint64 elapsed;
+
+  elapsed = ((guint64) (g_timer_elapsed (priv->timer, NULL) * 1000)) + priv->timer_base;
+  if (elapsed < priv->frame_time)
+    {
+      /* clock went backward. adapt to that by forevermore increasing
+       * timer_base.  For now, assume we've gone forward in time 1ms.
+       */
+      /* hmm. just fix GTimer? */
+      computed_frame_time = priv->frame_time + 1;
+      priv->timer_base += (priv->frame_time - elapsed) + 1;
+    }
+  else
+    {
+      computed_frame_time = elapsed;
+    }
+
+  return computed_frame_time;
+}
+
+static guint64
+gdk_paint_clock_idle_get_frame_time (GdkPaintClock *clock)
+{
+  GdkPaintClockIdlePrivate *priv = GDK_PAINT_CLOCK_IDLE (clock)->priv;
+  guint64 computed_frame_time;
+
+  /* can't change frame time during a paint */
+  if (priv->in_paint)
+    return priv->frame_time;
+
+  /* Outside a paint, pick something close to "now" */
+  computed_frame_time = compute_frame_time (GDK_PAINT_CLOCK_IDLE (clock));
+
+  /* 16ms is 60fps. We only update frame time that often because we'd
+   * like to try to keep animations on the same start times.
+   * get_frame_time() would normally be used outside of a paint to
+   * record an animation start time for example.
+   */
+  if ((computed_frame_time - priv->frame_time) > 16)
+    priv->frame_time = computed_frame_time;
+
+  return priv->frame_time;
+}
+
+static gboolean
+gdk_paint_clock_paint_idle (void *data)
+{
+  GdkPaintClock *clock = GDK_PAINT_CLOCK (data);
+  GdkPaintClockIdle *clock_idle = GDK_PAINT_CLOCK_IDLE (clock);
+  GdkPaintClockIdlePrivate *priv = clock_idle->priv;
+
+  priv->idle_id = 0;
+
+  priv->in_paint = TRUE;
+  priv->frame_time = compute_frame_time (clock_idle);
+
+  gdk_paint_clock_paint (clock);
+
+  priv->in_paint = FALSE;
+
+  return FALSE;
+}
+
+static void
+gdk_paint_clock_idle_request_frame (GdkPaintClock *clock)
+{
+  GdkPaintClockIdlePrivate *priv = GDK_PAINT_CLOCK_IDLE (clock)->priv;
+
+  if (priv->idle_id == 0)
+    {
+      priv->idle_id = gdk_threads_add_idle_full (GDK_PRIORITY_REDRAW,
+                                                 gdk_paint_clock_paint_idle,
+                                                 g_object_ref (clock),
+                                                 (GDestroyNotify) g_object_unref);
+
+      gdk_paint_clock_frame_requested (clock);
+    }
+}
+
+static gboolean
+gdk_paint_clock_idle_get_frame_requested (GdkPaintClock *clock)
+{
+  GdkPaintClockIdlePrivate *priv = GDK_PAINT_CLOCK_IDLE (clock)->priv;
+
+  return priv->idle_id != 0;
+}
+
+static void
+gdk_paint_clock_idle_interface_init (GdkPaintClockInterface *iface)
+{
+  iface->get_frame_time = gdk_paint_clock_idle_get_frame_time;
+  iface->request_frame = gdk_paint_clock_idle_request_frame;
+  iface->get_frame_requested = gdk_paint_clock_idle_get_frame_requested;
+}
diff --git a/gdk/gdkpaintclockidle.h b/gdk/gdkpaintclockidle.h
new file mode 100644
index 0000000..c18e66f
--- /dev/null
+++ b/gdk/gdkpaintclockidle.h
@@ -0,0 +1,68 @@
+/* GDK - The GIMP Drawing Kit
+ * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
+ *
+ * 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.
+ */
+
+/*
+ * Modified by the GTK+ Team and others 1997-2010.  See the AUTHORS
+ * file for a list of people on the GTK+ Team.  See the ChangeLog
+ * files for a list of changes.  These files are distributed with
+ * GTK+ at ftp://ftp.gtk.org/pub/gtk/.
+ */
+
+/* Uninstalled header, internal to GDK */
+
+#ifndef __GDK_PAINT_CLOCK_IDLE_H__
+#define __GDK_PAINT_CLOCK_IDLE_H__
+
+#include <gdk/gdkpaintclock.h>
+
+G_BEGIN_DECLS
+
+#define GDK_TYPE_PAINT_CLOCK_IDLE            (gdk_paint_clock_idle_get_type ())
+#define GDK_PAINT_CLOCK_IDLE(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), GDK_TYPE_PAINT_CLOCK_IDLE, GdkPaintClockIdle))
+#define GDK_PAINT_CLOCK_IDLE_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), GDK_TYPE_PAINT_CLOCK_IDLE, GdkPaintClockIdleClass))
+#define GDK_IS_PAINT_CLOCK_IDLE(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GDK_TYPE_PAINT_CLOCK_IDLE))
+#define GDK_IS_PAINT_CLOCK_IDLE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GDK_TYPE_PAINT_CLOCK_IDLE))
+#define GDK_PAINT_CLOCK_IDLE_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), GDK_TYPE_PAINT_CLOCK_IDLE, GdkPaintClockIdleClass))
+
+
+typedef struct _GdkPaintClockIdle              GdkPaintClockIdle;
+typedef struct _GdkPaintClockIdlePrivate       GdkPaintClockIdlePrivate;
+typedef struct _GdkPaintClockIdleClass         GdkPaintClockIdleClass;
+
+struct _GdkPaintClockIdle
+{
+  GObject parent_instance;
+
+  /*< private >*/
+  GdkPaintClockIdlePrivate *priv;
+};
+
+struct _GdkPaintClockIdleClass
+{
+  GObjectClass parent_class;
+};
+
+GType	   gdk_paint_clock_idle_get_type          (void) G_GNUC_CONST;
+
+void _gdk_paint_clock_idle_freeze_updates (GdkPaintClockIdle *clock_idle);
+void _gdk_paint_clock_idle_thaw_updates (GdkPaintClockIdle *clock_idle);
+
+G_END_DECLS
+
+#endif /* __GDK_PAINT_CLOCK_IDLE_H__ */
diff --git a/gdk/gdkwindow.c b/gdk/gdkwindow.c
index 206edc3..e35d31b 100644
--- a/gdk/gdkwindow.c
+++ b/gdk/gdkwindow.c
@@ -37,6 +37,7 @@
 #include "gdkdeviceprivate.h"
 #include "gdkvisualprivate.h"
 #include "gdkmarshalers.h"
+#include "gdkpaintclockidle.h"
 #include "gdkwindowimpl.h"
 
 #include <math.h>
@@ -169,7 +170,8 @@ enum {
 
 enum {
   PROP_0,
-  PROP_CURSOR
+  PROP_CURSOR,
+  PROP_PAINT_CLOCK
 };
 
 typedef enum {
@@ -237,6 +239,10 @@ static void gdk_window_invalidate_rect_full (GdkWindow          *window,
 static void _gdk_window_propagate_has_alpha_background (GdkWindow *window);
 static cairo_surface_t *gdk_window_ref_impl_surface (GdkWindow *window);
 
+static void gdk_window_process_all_updates_internal (gboolean default_clock_only);
+
+static void gdk_ensure_default_paint_clock  (void);
+
 static guint signals[LAST_SIGNAL] = { 0 };
 
 static gpointer parent_class = NULL;
@@ -387,6 +393,23 @@ gdk_window_class_init (GdkWindowClass *klass)
                                                         G_PARAM_READWRITE));
 
   /**
+   * GdkWindow:paint-clock:
+   *
+   * The paint clock for a #GdkWindow, see #GdkPaintClock
+   *
+   * The paint clock remains the same for the lifetime of the window.
+   *
+   * Since: 3.0
+   */
+  g_object_class_install_property (object_class,
+                                   PROP_PAINT_CLOCK,
+                                   g_param_spec_object ("paint-clock",
+                                                        P_("Paint clock"),
+                                                        P_("Paint clock"),
+                                                        GDK_TYPE_PAINT_CLOCK,
+                                                        G_PARAM_READWRITE));
+
+  /**
    * GdkWindow::pick-embedded-child:
    * @window: the window on which the signal is emitted
    * @x: x coordinate in the window
@@ -598,6 +621,10 @@ gdk_window_set_property (GObject      *object,
       gdk_window_set_cursor (window, g_value_get_object (value));
       break;
 
+    case PROP_PAINT_CLOCK:
+      gdk_window_set_paint_clock (window, g_value_get_object (value));
+      break;
+
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
       break;
@@ -618,6 +645,10 @@ gdk_window_get_property (GObject    *object,
       g_value_set_object (value, gdk_window_get_cursor (window));
       break;
 
+    case PROP_PAINT_CLOCK:
+      g_value_set_object (value, gdk_window_get_paint_clock (window));
+      break;
+
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
       break;
@@ -3710,7 +3741,7 @@ gdk_cairo_create (GdkWindow *window)
 /* Code for dirty-region queueing
  */
 static GSList *update_windows = NULL;
-static guint update_idle = 0;
+static GdkPaintClock *_gdk_default_paint_clock = NULL;
 static gboolean debug_updates = FALSE;
 
 static inline gboolean
@@ -3809,12 +3840,25 @@ gdk_window_remove_update_window (GdkWindow *window)
   update_windows = g_slist_remove (update_windows, window);
 }
 
-static gboolean
-gdk_window_update_idle (gpointer data)
+static void
+gdk_window_paint_default_clock_updates (gpointer data)
 {
-  gdk_window_process_all_updates ();
+  gdk_window_process_all_updates_internal (TRUE);
+}
 
-  return FALSE;
+static void
+gdk_ensure_default_paint_clock (void)
+{
+  if (_gdk_default_paint_clock == NULL)
+    {
+      _gdk_default_paint_clock = g_object_new (GDK_TYPE_PAINT_CLOCK_IDLE,
+                                               NULL);
+
+      g_signal_connect (G_OBJECT (_gdk_default_paint_clock),
+                        "paint",
+                        G_CALLBACK (gdk_window_paint_default_clock_updates),
+                        NULL);
+    }
 }
 
 static gboolean
@@ -3835,11 +3879,7 @@ gdk_window_schedule_update (GdkWindow *window)
        gdk_window_is_toplevel_frozen (window)))
     return;
 
-  if (!update_idle)
-    update_idle =
-      gdk_threads_add_idle_full (GDK_PRIORITY_REDRAW,
-				 gdk_window_update_idle,
-				 NULL, NULL);
+  gdk_paint_clock_request_frame (gdk_window_get_paint_clock (window));
 }
 
 void
@@ -4137,6 +4177,19 @@ after_process_all_updates (void)
   g_slist_free (displays);
 }
 
+/**
+ * gdk_window_process_all_updates:
+ *
+ * Calls gdk_window_process_updates() for all windows (see #GdkWindow)
+ * in the application.
+ *
+ **/
+void
+gdk_window_process_all_updates (void)
+{
+  gdk_window_process_all_updates_internal (FALSE);
+}
+
 /* Currently it is not possible to override
  * gdk_window_process_all_updates in the same manner as
  * gdk_window_process_updates and gdk_window_invalidate_maybe_recurse
@@ -4147,15 +4200,8 @@ after_process_all_updates (void)
  * displays and call the mehod.
  */
 
-/**
- * gdk_window_process_all_updates:
- *
- * Calls gdk_window_process_updates() for all windows (see #GdkWindow)
- * in the application.
- *
- **/
-void
-gdk_window_process_all_updates (void)
+static void
+gdk_window_process_all_updates_internal (gboolean default_clock_only)
 {
   GSList *old_update_windows = update_windows;
   GSList *tmp_list = update_windows;
@@ -4167,18 +4213,13 @@ gdk_window_process_all_updates (void)
       /* We can't do this now since that would recurse, so
 	 delay it until after the recursion is done. */
       got_recursive_update = TRUE;
-      update_idle = 0;
       return;
     }
 
   in_process_all_updates = TRUE;
   got_recursive_update = FALSE;
 
-  if (update_idle)
-    g_source_remove (update_idle);
-
   update_windows = NULL;
-  update_idle = 0;
 
   before_process_all_updates ();
 
@@ -4191,7 +4232,8 @@ gdk_window_process_all_updates (void)
       if (!GDK_WINDOW_DESTROYED (window))
 	{
 	  if (window->update_freeze_count ||
-	      gdk_window_is_toplevel_frozen (window))
+	      gdk_window_is_toplevel_frozen (window) ||
+	      (default_clock_only && window->paint_clock != NULL))
 	    gdk_window_add_update_window (window);
 	  else
 	    gdk_window_process_updates_internal (window);
@@ -4213,31 +4255,20 @@ gdk_window_process_all_updates (void)
      redraw now so that it eventually happens,
      otherwise we could miss an update if nothing
      else schedules an update. */
-  if (got_recursive_update && !update_idle)
-    update_idle =
-      gdk_threads_add_idle_full (GDK_PRIORITY_REDRAW,
-				 gdk_window_update_idle,
-				 NULL, NULL);
+  if (got_recursive_update)
+    gdk_window_schedule_update (NULL);
 }
 
-/**
- * gdk_window_process_updates:
- * @window: a #GdkWindow
- * @update_children: whether to also process updates for child windows
- *
- * Sends one or more expose events to @window. The areas in each
- * expose event will cover the entire update area for the window (see
- * gdk_window_invalidate_region() for details). Normally GDK calls
- * gdk_window_process_all_updates() on your behalf, so there's no
- * need to call this function unless you want to force expose events
- * to be delivered immediately and synchronously (vs. the usual
- * case, where GDK delivers them in an idle handler). Occasionally
- * this is useful to produce nicer scrolling behavior, for example.
- *
- **/
-void
-gdk_window_process_updates (GdkWindow *window,
-			    gboolean   update_children)
+
+enum {
+  PROCESS_UPDATES_NO_RECURSE,
+  PROCESS_UPDATES_WITH_ALL_CHILDREN,
+  PROCESS_UPDATES_WITH_SAME_CLOCK_CHILDREN
+};
+
+static void
+gdk_window_process_updates_with_mode (GdkWindow     *window,
+                                      int            recurse_mode)
 {
   GdkWindow *impl_window;
 
@@ -4263,7 +4294,7 @@ gdk_window_process_updates (GdkWindow *window,
       gdk_window_remove_update_window ((GdkWindow *)impl_window);
     }
 
-  if (update_children)
+  if (recurse_mode != PROCESS_UPDATES_NO_RECURSE)
     {
       /* process updates in reverse stacking order so composition or
        * painting over achieves the desired effect for offscreen windows
@@ -4275,8 +4306,14 @@ gdk_window_process_updates (GdkWindow *window,
 
       for (node = g_list_last (children); node; node = node->prev)
 	{
-	  gdk_window_process_updates (node->data, TRUE);
-	  g_object_unref (node->data);
+          GdkWindow *child = node->data;
+          if (recurse_mode == PROCESS_UPDATES_WITH_ALL_CHILDREN ||
+              (recurse_mode == PROCESS_UPDATES_WITH_SAME_CLOCK_CHILDREN &&
+               child->paint_clock == NULL))
+            {
+              gdk_window_process_updates (child, TRUE);
+            }
+	  g_object_unref (child);
 	}
 
       g_list_free (children);
@@ -4285,6 +4322,33 @@ gdk_window_process_updates (GdkWindow *window,
   g_object_unref (window);
 }
 
+/**
+ * gdk_window_process_updates:
+ * @window: a #GdkWindow
+ * @update_children: whether to also process updates for child windows
+ *
+ * Sends one or more expose events to @window. The areas in each
+ * expose event will cover the entire update area for the window (see
+ * gdk_window_invalidate_region() for details). Normally GDK calls
+ * gdk_window_process_all_updates() on your behalf, so there's no
+ * need to call this function unless you want to force expose events
+ * to be delivered immediately and synchronously (vs. the usual
+ * case, where GDK delivers them in an idle handler). Occasionally
+ * this is useful to produce nicer scrolling behavior, for example.
+ *
+ **/
+void
+gdk_window_process_updates (GdkWindow *window,
+			    gboolean   update_children)
+{
+  g_return_if_fail (GDK_IS_WINDOW (window));
+
+  return gdk_window_process_updates_with_mode (window,
+                                               update_children ?
+                                               PROCESS_UPDATES_WITH_ALL_CHILDREN :
+                                               PROCESS_UPDATES_NO_RECURSE);
+}
+
 static void
 gdk_window_invalidate_rect_full (GdkWindow          *window,
 				  const GdkRectangle *rect,
@@ -11362,3 +11426,113 @@ gdk_property_delete (GdkWindow *window,
 {
   GDK_WINDOW_IMPL_GET_CLASS (window->impl)->delete_property (window, property);
 }
+
+static void
+gdk_window_paint_on_clock (GdkPaintClock *clock,
+			   void          *data)
+{
+  GdkWindow *window;
+
+  window = GDK_WINDOW (data);
+
+  /* Update window and any children on the same clock.
+   */
+  gdk_window_process_updates_with_mode (window, PROCESS_UPDATES_WITH_SAME_CLOCK_CHILDREN);
+}
+
+/**
+ * gdk_window_set_paint_clock:
+ * @window: window to set paint clock on
+ * @clock: the clock
+ *
+ * Sets the paint clock for the window. The paint clock for a window
+ * cannot be changed while the window is mapped. Set the paint
+ * clock to #NULL to use the default paint clock. (By default the
+ * paint clock comes from the window's parent or is a global default
+ * paint clock.)
+ *
+ * Since: 3.0
+ */
+void
+gdk_window_set_paint_clock (GdkWindow     *window,
+                            GdkPaintClock *clock)
+{
+  g_return_if_fail (GDK_IS_WINDOW (window));
+  g_return_if_fail (clock == NULL || GDK_IS_PAINT_CLOCK (clock));
+  g_return_if_fail (!GDK_WINDOW_IS_MAPPED (window));
+
+  if (clock == window->paint_clock)
+    return;
+
+  /* If we are using our parent's clock, then the parent will repaint
+   * us when that clock fires. If we are using the default clock, then
+   * it does a gdk_window_process_all_updates() which will repaint us
+   * when the clock fires. If we are using our own clock, then we have
+   * to connect to "paint" on it ourselves and paint ourselves and
+   * any child windows.
+   */
+
+  if (clock)
+    {
+      g_object_ref (clock);
+      g_signal_connect (G_OBJECT (clock),
+                        "paint",
+                        G_CALLBACK (gdk_window_paint_on_clock),
+                        window);
+    }
+
+  if (window->paint_clock)
+    {
+      g_signal_handlers_disconnect_by_func (G_OBJECT (window->paint_clock),
+                                            G_CALLBACK (gdk_window_paint_on_clock),
+                                            window);
+      g_object_unref (window->paint_clock);
+    }
+
+  window->paint_clock = clock;
+  g_object_notify (G_OBJECT (window), "paint-clock");
+
+  /* We probably should recurse child windows and emit notify on their
+   * paint-clock properties also, and we should emit notify when a
+   * window is first parented.
+   */
+}
+
+/**
+ * gdk_window_get_paint_clock:
+ * @window: window to get paint clock for
+ *
+ * Gets the paint clock for the window. The paint clock for a window
+ * never changes while the window is mapped. It may be changed at
+ * other times.
+ *
+ * Since: 3.0
+ * Return value: (transfer none): the paint clock
+ */
+GdkPaintClock*
+gdk_window_get_paint_clock (GdkWindow *window)
+{
+  g_return_val_if_fail (GDK_IS_WINDOW (window), NULL);
+
+  if (window->paint_clock != NULL)
+    {
+      /* Paint clock set explicitly on this window */
+      return window->paint_clock;
+    }
+  else
+    {
+      GdkWindow *parent;
+
+      /* parent's paint clock or default */
+      parent = gdk_window_get_effective_parent (window);
+      if (parent != NULL)
+        {
+          return gdk_window_get_paint_clock (parent);
+        }
+      else
+        {
+          gdk_ensure_default_paint_clock ();
+          return _gdk_default_paint_clock;
+        }
+    }
+}
diff --git a/gdk/gdkwindow.h b/gdk/gdkwindow.h
index fded029..5626db8 100644
--- a/gdk/gdkwindow.h
+++ b/gdk/gdkwindow.h
@@ -32,6 +32,7 @@
 #include <gdk/gdkversionmacros.h>
 #include <gdk/gdktypes.h>
 #include <gdk/gdkevents.h>
+#include <gdk/gdkpaintclock.h>
 
 G_BEGIN_DECLS
 
@@ -879,6 +880,11 @@ void       gdk_window_set_support_multidevice (GdkWindow *window,
                                                gboolean   support_multidevice);
 gboolean   gdk_window_get_support_multidevice (GdkWindow *window);
 
+/* Paint clock */
+void           gdk_window_set_paint_clock      (GdkWindow     *window,
+                                                GdkPaintClock *clock);
+GdkPaintClock* gdk_window_get_paint_clock      (GdkWindow     *window);
+
 G_END_DECLS
 
 #endif /* __GDK_WINDOW_H__ */
diff --git a/gtk/gtkwidget.c b/gtk/gtkwidget.c
index 58ad57a..6c01efb 100644
--- a/gtk/gtkwidget.c
+++ b/gtk/gtkwidget.c
@@ -4676,6 +4676,54 @@ gtk_widget_queue_resize_no_redraw (GtkWidget *widget)
 }
 
 /**
+ * gtk_widget_get_paint_clock:
+ * @widget: a #GtkWidget
+ *
+ * Obtains the paint clock for a widget. The paint clock is a global
+ * "ticker" that can be used to drive animations and repaints.  The
+ * most common reason to get the paint clock is to call
+ * gdk_paint_clock_get_frame_time(), in order to get a time to use for
+ * animating. For example you might record the start of the animation
+ * with an initial value from gdk_paint_clock_get_frame_time(), and
+ * then update the animation by calling
+ * gdk_paint_clock_get_frame_time() again during each repaint.
+ *
+ * gdk_paint_clock_request_frame() will result in a new frame on the
+ * clock, but won't necessarily repaint any widgets. To repaint a
+ * widget, you have to use gtk_widget_queue_draw() which invalidates
+ * the widget (thus scheduling it to receive a draw on the next
+ * frame). gtk_widget_queue_draw() will also end up requesting a frame
+ * on the appropriate paint clock.
+ *
+ * A widget's paint clock will not change while the widget is
+ * mapped. Reparenting a widget (which implies a temporary unmap) can
+ * change the widget's paint clock.
+ *
+ * Unrealized widgets do not have a paint clock.
+ *
+ * Since: 3.0
+ * Return value: a #GdkPaintClock (or #NULL if widget is unrealized)
+ */
+GdkPaintClock*
+gtk_widget_get_paint_clock (GtkWidget *widget)
+{
+  g_return_val_if_fail (GTK_IS_WIDGET (widget), 0);
+
+  if (widget->priv->realized)
+    {
+      GdkWindow *window;
+
+      window = gtk_widget_get_window (widget);
+      g_assert (window != NULL);
+      return gdk_window_get_paint_clock (window);
+    }
+  else
+    {
+      return NULL;
+    }
+}
+
+/**
  * gtk_widget_size_request:
  * @widget: a #GtkWidget
  * @requisition: (out): a #GtkRequisition to be filled in
diff --git a/gtk/gtkwidget.h b/gtk/gtkwidget.h
index c178478..2f35600 100644
--- a/gtk/gtkwidget.h
+++ b/gtk/gtkwidget.h
@@ -473,6 +473,8 @@ void	   gtk_widget_queue_draw_region   (GtkWidget	       *widget,
                                            const cairo_region_t*region);
 void	   gtk_widget_queue_resize	  (GtkWidget	       *widget);
 void	   gtk_widget_queue_resize_no_redraw (GtkWidget *widget);
+GdkPaintClock* gtk_widget_get_paint_clock (GtkWidget           *widget);
+
 GDK_DEPRECATED_IN_3_0_FOR(gtk_widget_get_preferred_size)
 void       gtk_widget_size_request        (GtkWidget           *widget,
                                            GtkRequisition      *requisition);



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]