[Xfce4-commits] <xfce4-settings:bluesabre/display-widget> Incomplete custom display dialog widget.

Sean Davis noreply at xfce.org
Tue Nov 13 03:00:02 CET 2012


Updating branch refs/heads/bluesabre/display-widget
         to 0ed0a5aef5a464eff908aea5f259afb68dfcb562 (commit)
       from 367f4941ea1ac683b02c93a07703a92fca6dc782 (commit)

commit 0ed0a5aef5a464eff908aea5f259afb68dfcb562
Author: Sean Davis <smd.seandavis at gmail.com>
Date:   Mon Nov 12 20:58:19 2012 -0500

    Incomplete custom display dialog widget.

 dialogs/display-settings/Makefile.am          |    6 +-
 dialogs/display-settings/display-dialog.glade |   45 +-
 dialogs/display-settings/foo-marshal.c        |  279 ++++
 dialogs/display-settings/foo-marshal.h        |   67 +
 dialogs/display-settings/main.c               |  506 +++++++-
 dialogs/display-settings/scrollarea.c         | 1944 +++++++++++++++++++++++++
 dialogs/display-settings/scrollarea.h         |  124 ++
 7 files changed, 2946 insertions(+), 25 deletions(-)

diff --git a/dialogs/display-settings/Makefile.am b/dialogs/display-settings/Makefile.am
index 2a5abc2..e72a8b9 100644
--- a/dialogs/display-settings/Makefile.am
+++ b/dialogs/display-settings/Makefile.am
@@ -22,7 +22,11 @@ xfce4_display_settings_SOURCES = \
 	identity-popup_ui.h \
 	display-name.c \
 	edid-parse.c \
-	edid.h
+	edid.h \
+	scrollarea.c			\
+	foo-marshal.c			\
+	scrollarea.h			\
+	foo-marshal.h
 
 xfce4_display_settings_CFLAGS = \
 	$(GTK_CFLAGS) \
diff --git a/dialogs/display-settings/display-dialog.glade b/dialogs/display-settings/display-dialog.glade
index c7357a3..352d885 100644
--- a/dialogs/display-settings/display-dialog.glade
+++ b/dialogs/display-settings/display-dialog.glade
@@ -68,21 +68,46 @@
             <property name="border_width">12</property>
             <property name="spacing">12</property>
             <child>
-              <object class="GtkScrolledWindow" id="scrolledwindow1">
-                <property name="width_request">200</property>
+              <object class="GtkVBox" id="vbox1">
                 <property name="visible">True</property>
-                <property name="can_focus">True</property>
-                <property name="hscrollbar_policy">automatic</property>
-                <property name="vscrollbar_policy">automatic</property>
-                <property name="shadow_type">etched-in</property>
+                <property name="can_focus">False</property>
                 <child>
-                  <object class="GtkTreeView" id="randr-outputs">
+                  <object class="GtkAlignment" id="display-widget">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                    <child>
+                      <placeholder/>
+                    </child>
+                  </object>
+                  <packing>
+                    <property name="expand">True</property>
+                    <property name="fill">True</property>
+                    <property name="position">0</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkScrolledWindow" id="scrolledwindow1">
+                    <property name="width_request">200</property>
                     <property name="visible">True</property>
                     <property name="can_focus">True</property>
-                    <property name="headers_visible">False</property>
-                    <property name="enable_search">False</property>
-                    <property name="show_expanders">False</property>
+                    <property name="hscrollbar_policy">automatic</property>
+                    <property name="vscrollbar_policy">automatic</property>
+                    <property name="shadow_type">etched-in</property>
+                    <child>
+                      <object class="GtkTreeView" id="randr-outputs">
+                        <property name="visible">True</property>
+                        <property name="can_focus">True</property>
+                        <property name="headers_visible">False</property>
+                        <property name="enable_search">False</property>
+                        <property name="show_expanders">False</property>
+                      </object>
+                    </child>
                   </object>
+                  <packing>
+                    <property name="expand">True</property>
+                    <property name="fill">True</property>
+                    <property name="position">1</property>
+                  </packing>
                 </child>
               </object>
               <packing>
diff --git a/dialogs/display-settings/foo-marshal.c b/dialogs/display-settings/foo-marshal.c
new file mode 100644
index 0000000..86eea22
--- /dev/null
+++ b/dialogs/display-settings/foo-marshal.c
@@ -0,0 +1,279 @@
+
+#include	<glib-object.h>
+#include    "foo-marshal.h"
+
+#ifdef G_ENABLE_DEBUG
+#define g_marshal_value_peek_boolean(v)  g_value_get_boolean (v)
+#define g_marshal_value_peek_char(v)     g_value_get_char (v)
+#define g_marshal_value_peek_uchar(v)    g_value_get_uchar (v)
+#define g_marshal_value_peek_int(v)      g_value_get_int (v)
+#define g_marshal_value_peek_uint(v)     g_value_get_uint (v)
+#define g_marshal_value_peek_long(v)     g_value_get_long (v)
+#define g_marshal_value_peek_ulong(v)    g_value_get_ulong (v)
+#define g_marshal_value_peek_int64(v)    g_value_get_int64 (v)
+#define g_marshal_value_peek_uint64(v)   g_value_get_uint64 (v)
+#define g_marshal_value_peek_enum(v)     g_value_get_enum (v)
+#define g_marshal_value_peek_flags(v)    g_value_get_flags (v)
+#define g_marshal_value_peek_float(v)    g_value_get_float (v)
+#define g_marshal_value_peek_double(v)   g_value_get_double (v)
+#define g_marshal_value_peek_string(v)   (char*) g_value_get_string (v)
+#define g_marshal_value_peek_param(v)    g_value_get_param (v)
+#define g_marshal_value_peek_boxed(v)    g_value_get_boxed (v)
+#define g_marshal_value_peek_pointer(v)  g_value_get_pointer (v)
+#define g_marshal_value_peek_object(v)   g_value_get_object (v)
+#else /* !G_ENABLE_DEBUG */
+/* WARNING: This code accesses GValues directly, which is UNSUPPORTED API.
+ *          Do not access GValues directly in your code. Instead, use the
+ *          g_value_get_*() functions
+ */
+#define g_marshal_value_peek_boolean(v)  (v)->data[0].v_int
+#define g_marshal_value_peek_char(v)     (v)->data[0].v_int
+#define g_marshal_value_peek_uchar(v)    (v)->data[0].v_uint
+#define g_marshal_value_peek_int(v)      (v)->data[0].v_int
+#define g_marshal_value_peek_uint(v)     (v)->data[0].v_uint
+#define g_marshal_value_peek_long(v)     (v)->data[0].v_long
+#define g_marshal_value_peek_ulong(v)    (v)->data[0].v_ulong
+#define g_marshal_value_peek_int64(v)    (v)->data[0].v_int64
+#define g_marshal_value_peek_uint64(v)   (v)->data[0].v_uint64
+#define g_marshal_value_peek_enum(v)     (v)->data[0].v_long
+#define g_marshal_value_peek_flags(v)    (v)->data[0].v_ulong
+#define g_marshal_value_peek_float(v)    (v)->data[0].v_float
+#define g_marshal_value_peek_double(v)   (v)->data[0].v_double
+#define g_marshal_value_peek_string(v)   (v)->data[0].v_pointer
+#define g_marshal_value_peek_param(v)    (v)->data[0].v_pointer
+#define g_marshal_value_peek_boxed(v)    (v)->data[0].v_pointer
+#define g_marshal_value_peek_pointer(v)  (v)->data[0].v_pointer
+#define g_marshal_value_peek_object(v)   (v)->data[0].v_pointer
+#endif /* !G_ENABLE_DEBUG */
+
+
+/* VOID:OBJECT,OBJECT (marshal.list:1) */
+void
+foo_marshal_VOID__OBJECT_OBJECT (GClosure     *closure,
+                                 GValue       *return_value G_GNUC_UNUSED,
+                                 guint         n_param_values,
+                                 const GValue *param_values,
+                                 gpointer      invocation_hint G_GNUC_UNUSED,
+                                 gpointer      marshal_data)
+{
+  typedef void (*GMarshalFunc_VOID__OBJECT_OBJECT) (gpointer     data1,
+                                                    gpointer     arg_1,
+                                                    gpointer     arg_2,
+                                                    gpointer     data2);
+  register GMarshalFunc_VOID__OBJECT_OBJECT callback;
+  register GCClosure *cc = (GCClosure*) closure;
+  register gpointer data1, data2;
+
+  g_return_if_fail (n_param_values == 3);
+
+  if (G_CCLOSURE_SWAP_DATA (closure))
+    {
+      data1 = closure->data;
+      data2 = g_value_peek_pointer (param_values + 0);
+    }
+  else
+    {
+      data1 = g_value_peek_pointer (param_values + 0);
+      data2 = closure->data;
+    }
+  callback = (GMarshalFunc_VOID__OBJECT_OBJECT) (marshal_data ? marshal_data : cc->callback);
+
+  callback (data1,
+            g_marshal_value_peek_object (param_values + 1),
+            g_marshal_value_peek_object (param_values + 2),
+            data2);
+}
+
+/* VOID:UINT,UINT,UINT,UINT (marshal.list:2) */
+void
+foo_marshal_VOID__UINT_UINT_UINT_UINT (GClosure     *closure,
+                                       GValue       *return_value G_GNUC_UNUSED,
+                                       guint         n_param_values,
+                                       const GValue *param_values,
+                                       gpointer      invocation_hint G_GNUC_UNUSED,
+                                       gpointer      marshal_data)
+{
+  typedef void (*GMarshalFunc_VOID__UINT_UINT_UINT_UINT) (gpointer     data1,
+                                                          guint        arg_1,
+                                                          guint        arg_2,
+                                                          guint        arg_3,
+                                                          guint        arg_4,
+                                                          gpointer     data2);
+  register GMarshalFunc_VOID__UINT_UINT_UINT_UINT callback;
+  register GCClosure *cc = (GCClosure*) closure;
+  register gpointer data1, data2;
+
+  g_return_if_fail (n_param_values == 5);
+
+  if (G_CCLOSURE_SWAP_DATA (closure))
+    {
+      data1 = closure->data;
+      data2 = g_value_peek_pointer (param_values + 0);
+    }
+  else
+    {
+      data1 = g_value_peek_pointer (param_values + 0);
+      data2 = closure->data;
+    }
+  callback = (GMarshalFunc_VOID__UINT_UINT_UINT_UINT) (marshal_data ? marshal_data : cc->callback);
+
+  callback (data1,
+            g_marshal_value_peek_uint (param_values + 1),
+            g_marshal_value_peek_uint (param_values + 2),
+            g_marshal_value_peek_uint (param_values + 3),
+            g_marshal_value_peek_uint (param_values + 4),
+            data2);
+}
+
+/* VOID:UINT,UINT (marshal.list:3) */
+void
+foo_marshal_VOID__UINT_UINT (GClosure     *closure,
+                             GValue       *return_value G_GNUC_UNUSED,
+                             guint         n_param_values,
+                             const GValue *param_values,
+                             gpointer      invocation_hint G_GNUC_UNUSED,
+                             gpointer      marshal_data)
+{
+  typedef void (*GMarshalFunc_VOID__UINT_UINT) (gpointer     data1,
+                                                guint        arg_1,
+                                                guint        arg_2,
+                                                gpointer     data2);
+  register GMarshalFunc_VOID__UINT_UINT callback;
+  register GCClosure *cc = (GCClosure*) closure;
+  register gpointer data1, data2;
+
+  g_return_if_fail (n_param_values == 3);
+
+  if (G_CCLOSURE_SWAP_DATA (closure))
+    {
+      data1 = closure->data;
+      data2 = g_value_peek_pointer (param_values + 0);
+    }
+  else
+    {
+      data1 = g_value_peek_pointer (param_values + 0);
+      data2 = closure->data;
+    }
+  callback = (GMarshalFunc_VOID__UINT_UINT) (marshal_data ? marshal_data : cc->callback);
+
+  callback (data1,
+            g_marshal_value_peek_uint (param_values + 1),
+            g_marshal_value_peek_uint (param_values + 2),
+            data2);
+}
+
+/* VOID:BOXED (marshal.list:4) */
+
+/* VOID:BOXED,BOXED (marshal.list:5) */
+void
+foo_marshal_VOID__BOXED_BOXED (GClosure     *closure,
+                               GValue       *return_value G_GNUC_UNUSED,
+                               guint         n_param_values,
+                               const GValue *param_values,
+                               gpointer      invocation_hint G_GNUC_UNUSED,
+                               gpointer      marshal_data)
+{
+  typedef void (*GMarshalFunc_VOID__BOXED_BOXED) (gpointer     data1,
+                                                  gpointer     arg_1,
+                                                  gpointer     arg_2,
+                                                  gpointer     data2);
+  register GMarshalFunc_VOID__BOXED_BOXED callback;
+  register GCClosure *cc = (GCClosure*) closure;
+  register gpointer data1, data2;
+
+  g_return_if_fail (n_param_values == 3);
+
+  if (G_CCLOSURE_SWAP_DATA (closure))
+    {
+      data1 = closure->data;
+      data2 = g_value_peek_pointer (param_values + 0);
+    }
+  else
+    {
+      data1 = g_value_peek_pointer (param_values + 0);
+      data2 = closure->data;
+    }
+  callback = (GMarshalFunc_VOID__BOXED_BOXED) (marshal_data ? marshal_data : cc->callback);
+
+  callback (data1,
+            g_marshal_value_peek_boxed (param_values + 1),
+            g_marshal_value_peek_boxed (param_values + 2),
+            data2);
+}
+
+/* VOID:POINTER,BOXED,POINTER (marshal.list:6) */
+void
+foo_marshal_VOID__POINTER_BOXED_POINTER (GClosure     *closure,
+                                         GValue       *return_value G_GNUC_UNUSED,
+                                         guint         n_param_values,
+                                         const GValue *param_values,
+                                         gpointer      invocation_hint G_GNUC_UNUSED,
+                                         gpointer      marshal_data)
+{
+  typedef void (*GMarshalFunc_VOID__POINTER_BOXED_POINTER) (gpointer     data1,
+                                                            gpointer     arg_1,
+                                                            gpointer     arg_2,
+                                                            gpointer     arg_3,
+                                                            gpointer     data2);
+  register GMarshalFunc_VOID__POINTER_BOXED_POINTER callback;
+  register GCClosure *cc = (GCClosure*) closure;
+  register gpointer data1, data2;
+
+  g_return_if_fail (n_param_values == 4);
+
+  if (G_CCLOSURE_SWAP_DATA (closure))
+    {
+      data1 = closure->data;
+      data2 = g_value_peek_pointer (param_values + 0);
+    }
+  else
+    {
+      data1 = g_value_peek_pointer (param_values + 0);
+      data2 = closure->data;
+    }
+  callback = (GMarshalFunc_VOID__POINTER_BOXED_POINTER) (marshal_data ? marshal_data : cc->callback);
+
+  callback (data1,
+            g_marshal_value_peek_pointer (param_values + 1),
+            g_marshal_value_peek_boxed (param_values + 2),
+            g_marshal_value_peek_pointer (param_values + 3),
+            data2);
+}
+
+/* VOID:POINTER,POINTER (marshal.list:7) */
+void
+foo_marshal_VOID__POINTER_POINTER (GClosure     *closure,
+                                   GValue       *return_value G_GNUC_UNUSED,
+                                   guint         n_param_values,
+                                   const GValue *param_values,
+                                   gpointer      invocation_hint G_GNUC_UNUSED,
+                                   gpointer      marshal_data)
+{
+  typedef void (*GMarshalFunc_VOID__POINTER_POINTER) (gpointer     data1,
+                                                      gpointer     arg_1,
+                                                      gpointer     arg_2,
+                                                      gpointer     data2);
+  register GMarshalFunc_VOID__POINTER_POINTER callback;
+  register GCClosure *cc = (GCClosure*) closure;
+  register gpointer data1, data2;
+
+  g_return_if_fail (n_param_values == 3);
+
+  if (G_CCLOSURE_SWAP_DATA (closure))
+    {
+      data1 = closure->data;
+      data2 = g_value_peek_pointer (param_values + 0);
+    }
+  else
+    {
+      data1 = g_value_peek_pointer (param_values + 0);
+      data2 = closure->data;
+    }
+  callback = (GMarshalFunc_VOID__POINTER_POINTER) (marshal_data ? marshal_data : cc->callback);
+
+  callback (data1,
+            g_marshal_value_peek_pointer (param_values + 1),
+            g_marshal_value_peek_pointer (param_values + 2),
+            data2);
+}
+
diff --git a/dialogs/display-settings/foo-marshal.h b/dialogs/display-settings/foo-marshal.h
new file mode 100644
index 0000000..4bef795
--- /dev/null
+++ b/dialogs/display-settings/foo-marshal.h
@@ -0,0 +1,67 @@
+
+#ifndef __foo_marshal_MARSHAL_H__
+#define __foo_marshal_MARSHAL_H__
+
+#include	<glib-object.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* VOID:OBJECT,OBJECT (marshal.list:1) */
+extern void foo_marshal_VOID__OBJECT_OBJECT (GClosure     *closure,
+                                             GValue       *return_value,
+                                             guint         n_param_values,
+                                             const GValue *param_values,
+                                             gpointer      invocation_hint,
+                                             gpointer      marshal_data);
+
+/* VOID:UINT,UINT,UINT,UINT (marshal.list:2) */
+extern void foo_marshal_VOID__UINT_UINT_UINT_UINT (GClosure     *closure,
+                                                   GValue       *return_value,
+                                                   guint         n_param_values,
+                                                   const GValue *param_values,
+                                                   gpointer      invocation_hint,
+                                                   gpointer      marshal_data);
+
+/* VOID:UINT,UINT (marshal.list:3) */
+extern void foo_marshal_VOID__UINT_UINT (GClosure     *closure,
+                                         GValue       *return_value,
+                                         guint         n_param_values,
+                                         const GValue *param_values,
+                                         gpointer      invocation_hint,
+                                         gpointer      marshal_data);
+
+/* VOID:BOXED (marshal.list:4) */
+#define foo_marshal_VOID__BOXED	g_cclosure_marshal_VOID__BOXED
+
+/* VOID:BOXED,BOXED (marshal.list:5) */
+extern void foo_marshal_VOID__BOXED_BOXED (GClosure     *closure,
+                                           GValue       *return_value,
+                                           guint         n_param_values,
+                                           const GValue *param_values,
+                                           gpointer      invocation_hint,
+                                           gpointer      marshal_data);
+
+/* VOID:POINTER,BOXED,POINTER (marshal.list:6) */
+extern void foo_marshal_VOID__POINTER_BOXED_POINTER (GClosure     *closure,
+                                                     GValue       *return_value,
+                                                     guint         n_param_values,
+                                                     const GValue *param_values,
+                                                     gpointer      invocation_hint,
+                                                     gpointer      marshal_data);
+
+/* VOID:POINTER,POINTER (marshal.list:7) */
+extern void foo_marshal_VOID__POINTER_POINTER (GClosure     *closure,
+                                               GValue       *return_value,
+                                               guint         n_param_values,
+                                               const GValue *param_values,
+                                               gpointer      invocation_hint,
+                                               gpointer      marshal_data);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __foo_marshal_MARSHAL_H__ */
+
diff --git a/dialogs/display-settings/main.c b/dialogs/display-settings/main.c
index 3bc1d67..493d96e 100644
--- a/dialogs/display-settings/main.c
+++ b/dialogs/display-settings/main.c
@@ -45,6 +45,10 @@
 #include "confirmation-dialog_ui.h"
 #include "minimal-display-dialog_ui.h"
 #include "identity-popup_ui.h"
+#include "scrollarea.h"
+
+#define SPACE 15
+#define MARGIN  15
 
 enum
 {
@@ -1148,13 +1152,38 @@ display_setting_mirror_displays_toggled (GtkToggleButton *togglebutton,
     }
 }
 
+static gint
+display_setting_get_displays_cloned (GtkBuilder *builder)
+{
+    RRMode   mode = None;
+    guint    n;
+    gint     cloned = TRUE;
+
+    /* Can outputs be cloned? */
+    if (display_settings_get_n_active_outputs () > 1)
+        mode = xfce_randr_clonable_mode (xfce_randr);
+    
+    /* Check if mirror settings are on */
+    for (n = 0; n < xfce_randr->noutput; n++)
+    {
+        if (xfce_randr->mode[n] == None)
+            continue;
+
+        cloned &= (xfce_randr->mode[n] == mode &&
+                   xfce_randr->relation[n] == XFCE_RANDR_PLACEMENT_MIRROR);
+
+        if (!cloned)
+            break;
+    }
+    
+    return cloned;
+}
 
 static void
 display_setting_mirror_displays_populate (GtkBuilder *builder)
 {
     GObject *check;
     RRMode   mode = None;
-    guint    n;
     gint     cloned = TRUE;
 
     if (!xfce_randr)
@@ -1186,17 +1215,7 @@ display_setting_mirror_displays_populate (GtkBuilder *builder)
                                      builder);
 
     /* Check if mirror settings are on */
-    for (n = 0; n < xfce_randr->noutput; n++)
-    {
-        if (xfce_randr->mode[n] == None)
-            continue;
-
-        cloned &= (xfce_randr->mode[n] == mode &&
-                   xfce_randr->relation[n] == XFCE_RANDR_PLACEMENT_MIRROR);
-
-        if (!cloned)
-            break;
-    }
+    cloned = display_setting_get_displays_cloned(builder);
 
     gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (check), cloned);
 
@@ -1701,12 +1720,456 @@ screen_on_event (GdkXEvent *xevent,
     return GDK_FILTER_CONTINUE;
 }
 
+
+static void
+layout_set_font (PangoLayout *layout, const char *font)
+{
+    PangoFontDescription *desc =
+	pango_font_description_from_string (font);
+
+    if (desc)
+    {
+	pango_layout_set_font_description (layout, desc);
+
+	pango_font_description_free (desc);
+    }
+}
+
+static void
+get_geometry (int display_id, int *w, int *h)
+{
+    /*
+    if (output->on)
+    {
+	*h = output->height;
+	*w = output->width;
+    }
+    else
+    {
+	*h = output->pref_height;
+	*w = output->pref_width;
+    }
+   if ((output->rotation & MATE_RR_ROTATION_90) || (output->rotation & MATE_RR_ROTATION_270))
+   {
+        int tmp;
+        tmp = *h;
+        *h = *w;
+        *w = tmp;
+   }*/
+   const XfceRRMode *output = xfce_randr_find_mode_by_id (xfce_randr, display_id,
+                                                       xfce_randr->mode[display_id]);
+   *w = output->width;
+   *h = output->height;
+}
+
+
+
+/* Sets a mouse cursor for a widget's window.  As a hack, you can pass
+ * GDK_BLANK_CURSOR to mean "set the cursor to NULL" (i.e. reset the widget's
+ * window's cursor to its default).
+ */
+static void
+set_cursor (GtkWidget *widget, GdkCursorType type)
+{
+	GdkCursor *cursor;
+	GdkWindow *window;
+
+	if (type == GDK_BLANK_CURSOR)
+	    cursor = NULL;
+	else
+	    cursor = gdk_cursor_new_for_display (gtk_widget_get_display (widget), type);
+
+	window = gtk_widget_get_window (widget);
+
+	if (window)
+	    gdk_window_set_cursor (window, cursor);
+
+	if (cursor)
+	    gdk_cursor_unref (cursor);
+}
+
+static double
+compute_scale (FooScrollArea *display_widget)
+{
+    int available_w, available_h;
+    int total_w, total_h;
+    GdkRectangle viewport;
+
+    foo_scroll_area_get_viewport (display_widget, &viewport);
+    
+    available_w = viewport.width - 2 * MARGIN - (xfce_randr->noutput - 1) * SPACE;
+    available_h = viewport.height - 2 * MARGIN - (xfce_randr->noutput - 1) * SPACE;
+
+    return MIN ((double)available_w / total_w, (double)available_h / total_h);
+}
+
+static void
+on_canvas_event (FooScrollArea *area,
+		 FooScrollAreaEvent *event,
+		 gpointer data)
+{
+    /* If the mouse exits the outputs, reset the cursor to the default.  See
+     * on_output_event() for where we set the cursor to the movement cursor if
+     * it is over one of the outputs.
+     */
+    set_cursor (GTK_WIDGET (area), GDK_BLANK_CURSOR);
+}
+
+static void
+paint_background (FooScrollArea *area,
+		  cairo_t       *cr)
+{
+    GdkRectangle viewport;
+    GtkWidget *widget;
+    GtkStyle *widget_style;
+
+    widget = GTK_WIDGET (area);
+
+    foo_scroll_area_get_viewport (area, &viewport);
+    widget_style = gtk_widget_get_style (widget);
+
+    cairo_set_source_rgb (cr,
+                          widget_style->base[GTK_STATE_SELECTED].red / 65535.0,
+                          widget_style->base[GTK_STATE_SELECTED].green / 65535.0,
+                          widget_style->base[GTK_STATE_SELECTED].blue / 65535.0);
+
+    cairo_rectangle (cr,
+		     viewport.x, viewport.y,
+		     viewport.width, viewport.height);
+
+    cairo_fill_preserve (cr);
+
+    foo_scroll_area_add_input_from_fill (area, cr, on_canvas_event, NULL);
+
+    cairo_set_source_rgb (cr,
+                          widget_style->dark[GTK_STATE_SELECTED].red / 65535.0,
+                          widget_style->dark[GTK_STATE_SELECTED].green / 65535.0,
+                          widget_style->dark[GTK_STATE_SELECTED].blue / 65535.0);
+
+    cairo_stroke (cr);
+}
+
+static PangoLayout *
+get_display_name (FooScrollArea *area,
+		  int display_id)
+{
+    const char *text;
+
+    //if (app->current_configuration->clone) {
+	/* Translators:  this is the feature where what you see on your laptop's
+	 * screen is the same as your external monitor.  Here, "Mirror" is being
+	 * used as an adjective, not as a verb.  For example, the Spanish
+	 * translation could be "Pantallas en Espejo", *not* "Espejar Pantallas".
+	 */
+	//text = _("Mirror Screens");
+    //} else
+	text = xfce_randr->friendly_name[display_id];
+
+    return gtk_widget_create_pango_layout (
+	GTK_WIDGET (area), text);
+}
+
+static void
+on_output_event (FooScrollArea *area,
+		 FooScrollAreaEvent *event,
+		 gpointer data)
+{
+    GtkBuilder *builder = GTK_BUILDER(data);
+    gint cloned = display_setting_get_displays_cloned(builder);
+
+    /* If the mouse is inside the outputs, set the cursor to "you can move me".  See
+     * on_canvas_event() for where we reset the cursor to the default if it
+     * exits the outputs' area.
+     */
+    if (!cloned && display_settings_get_n_active_outputs () > 1)
+	set_cursor (GTK_WIDGET (area), GDK_FLEUR);
+
+    if (event->type == FOO_BUTTON_PRESS)
+    {
+	GrabInfo *info;
+
+	app->current_output = output;
+
+	rebuild_gui (app);
+	set_monitors_tooltip (app, TRUE);
+
+	if (!cloned && display_settings_get_n_active_outputs () > 1)
+	{
+	    foo_scroll_area_begin_grab (area, on_output_event, data);
+
+	    info = g_new0 (GrabInfo, 1);
+	    info->grab_x = event->x;
+	    info->grab_y = event->y;
+	    info->output_x = output->x;
+	    info->output_y = output->y;
+
+	    output->user_data = info;
+	}
+
+	foo_scroll_area_invalidate (area);
+    }
+    else
+    {
+	if (foo_scroll_area_is_grabbed (area))
+	{
+	    GrabInfo *info = output->user_data;
+	    double scale = compute_scale (app);
+	    int old_x, old_y;
+	    int new_x, new_y;
+	    int i;
+	    GArray *edges, *snaps, *new_edges;
+
+	    old_x = output->x;
+	    old_y = output->y;
+	    new_x = info->output_x + (event->x - info->grab_x) / scale;
+	    new_y = info->output_y + (event->y - info->grab_y) / scale;
+
+	    output->x = new_x;
+	    output->y = new_y;
+
+	    edges = g_array_new (TRUE, TRUE, sizeof (Edge));
+	    snaps = g_array_new (TRUE, TRUE, sizeof (Snap));
+	    new_edges = g_array_new (TRUE, TRUE, sizeof (Edge));
+
+	    list_edges (app->current_configuration, edges);
+	    list_snaps (output, edges, snaps);
+
+	    g_array_sort (snaps, compare_snaps);
+
+	    output->x = info->output_x;
+	    output->y = info->output_y;
+
+	    for (i = 0; i < snaps->len; ++i)
+	    {
+		Snap *snap = &(g_array_index (snaps, Snap, i));
+		GArray *new_edges = g_array_new (TRUE, TRUE, sizeof (Edge));
+
+		output->x = new_x + snap->dx;
+		output->y = new_y + snap->dy;
+
+		g_array_set_size (new_edges, 0);
+		list_edges (app->current_configuration, new_edges);
+
+		if (mate_rr_config_is_aligned (app->current_configuration, new_edges))
+		{
+		    g_array_free (new_edges, TRUE);
+		    break;
+		}
+		else
+		{
+		    output->x = info->output_x;
+		    output->y = info->output_y;
+		}
+	    }
+
+	    g_array_free (new_edges, TRUE);
+	    g_array_free (snaps, TRUE);
+	    g_array_free (edges, TRUE);
+
+	    if (event->type == FOO_BUTTON_RELEASE)
+	    {
+		foo_scroll_area_end_grab (area);
+		set_monitors_tooltip (app, FALSE);
+
+		g_free (output->user_data);
+		output->user_data = NULL;
+
+#if 0
+		g_debug ("new position: %d %d %d %d", output->x, output->y, output->width, output->height);
+#endif
+	    }
+
+	    foo_scroll_area_invalidate (area);
+	}
+    }
+}
+
+static void
+paint_output (FooScrollArea *area, cairo_t *cr, int i)
+{
+    int w, h;
+    double scale = compute_scale (area);
+    double x, y;
+    int total_w, total_h;
+    
+    const XfceRRMode *output = xfce_randr_find_mode_by_id (xfce_randr, i,
+                                                       xfce_randr->mode[i]);
+    
+    PangoLayout *layout = get_display_name (area, i);
+    PangoRectangle ink_extent, log_extent;
+    GdkRectangle viewport;
+    GdkColor output_color;
+    double r, g, b;
+    double available_w;
+    double factor;
+
+    cairo_save (cr);
+
+    foo_scroll_area_get_viewport (FOO_SCROLL_AREA (area), &viewport);
+
+    get_geometry (i, &w, &h);
+
+#if 0
+    g_debug ("%s (%p) geometry %d %d %d", output->name, output,
+	     w, h, output->rate);
+#endif
+
+    viewport.height -= 2 * MARGIN;
+    viewport.width -= 2 * MARGIN;
+
+    x = output->width * scale + MARGIN + (viewport.width - total_w * scale) / 2.0;
+    y = output->height * scale + MARGIN + (viewport.height - total_h * scale) / 2.0;
+
+#if 0
+    g_debug ("scaled: %f %f", x, y);
+
+    g_debug ("scale: %f", scale);
+
+    g_debug ("%f %f %f %f", x, y, w * scale + 0.5, h * scale + 0.5);
+#endif
+
+    cairo_save (cr);
+
+    cairo_translate (cr,
+		     x + (w * scale + 0.5) / 2,
+		     y + (h * scale + 0.5) / 2);
+
+    /* rotation is already applied in get_geometry */
+
+/*    if (output->rotation & MATE_RR_REFLECT_X)
+	cairo_scale (cr, -1, 1);
+
+    if (output->rotation & MATE_RR_REFLECT_Y)
+	cairo_scale (cr, 1, -1); */
+
+    cairo_translate (cr,
+		     - x - (w * scale + 0.5) / 2,
+		     - y - (h * scale + 0.5) / 2);
+
+
+    cairo_rectangle (cr, x, y, w * scale + 0.5, h * scale + 0.5);
+    cairo_clip_preserve (cr);
+
+/*
+    mate_rr_labeler_get_color_for_output (app->labeler, output, &output_color);
+    r = output_color.red / 65535.0;
+    g = output_color.green / 65535.0;
+    b = output_color.blue / 65535.0;
+*/
+    r = 255.0;
+    g = 255.0;
+    b = 255.0;
+
+/*
+    if (!output->on)
+    {*/
+	/* If the output is turned off, just darken the selected color */
+	/*r *= 0.2;
+	g *= 0.2;
+	b *= 0.2;
+    }*/
+
+    cairo_set_source_rgba (cr, r, g, b, 1.0);
+
+    foo_scroll_area_add_input_from_fill (area,
+					 cr, on_output_event, output);
+    cairo_fill (cr);
+
+    if (output == app->current_output)
+    {
+	cairo_rectangle (cr, x + 2, y + 2, w * scale + 0.5 - 4, h * scale + 0.5 - 4);
+
+	cairo_set_line_width (cr, 4);
+	cairo_set_source_rgba (cr, 0.33, 0.43, 0.57, 1.0);
+	cairo_stroke (cr);
+    }
+
+    cairo_rectangle (cr, x + 0.5, y + 0.5, w * scale + 0.5 - 1, h * scale + 0.5 - 1);
+
+    cairo_set_line_width (cr, 1);
+    cairo_set_source_rgba (cr, 0.0, 0.0, 0.0, 1.0);
+
+    cairo_stroke (cr);
+    cairo_set_line_width (cr, 2);
+
+    layout_set_font (layout, "Sans Bold 12");
+    pango_layout_get_pixel_extents (layout, &ink_extent, &log_extent);
+
+    available_w = w * scale + 0.5 - 6; /* Same as the inner rectangle's width, minus 1 pixel of padding on each side */
+    if (available_w < ink_extent.width)
+	factor = available_w / ink_extent.width;
+    else
+	factor = 1.0;
+
+    cairo_move_to (cr,
+		   x + ((w * scale + 0.5) - factor * log_extent.width) / 2,
+		   y + ((h * scale + 0.5) - factor * log_extent.height) / 2);
+
+    cairo_scale (cr, factor, factor);
+
+    if (output->on)
+	cairo_set_source_rgb (cr, 0.0, 0.0, 0.0);
+    else
+	cairo_set_source_rgb (cr, 1.0, 1.0, 1.0);
+
+    pango_cairo_show_layout (cr, layout);
+
+    cairo_restore (cr);
+
+    g_object_unref (layout);
+}
+
+static void
+on_area_paint (FooScrollArea *area,
+	       cairo_t	     *cr,
+	       GdkRectangle  *extent,
+	       GdkRegion     *region,
+	       gpointer	      data)
+{
+    App *app = data;
+    double scale;
+    GList *connected_outputs = NULL;
+    GList *list;
+
+    paint_background (area, cr);
+
+    if (!app->current_configuration)
+	return;
+
+    scale = compute_scale (area);
+    connected_outputs = list_connected_outputs (app, NULL, NULL);
+    
+#if 0
+    g_debug ("scale: %f", scale);
+#endif
+
+    for (list = connected_outputs; list != NULL; list = list->next)
+    {
+	paint_output (app, cr, g_list_position (connected_outputs, list));
+
+	if (app->current_configuration->clone)
+	    break;
+    }
+}
+
+static void
+on_viewport_changed (FooScrollArea *scroll_area,
+		     GdkRectangle  *old_viewport,
+		     GdkRectangle  *new_viewport)
+{
+    foo_scroll_area_set_size (scroll_area,
+			      new_viewport->width,
+			      new_viewport->height);
+
+    foo_scroll_area_invalidate (scroll_area);
+}
+
 static void
 display_settings_show_main_dialog (GdkDisplay *display)
 {
     GtkBuilder  *builder;
-    GtkWidget   *dialog, *plug;
-    GObject     *plug_child;
+    GtkWidget   *dialog, *plug, *display_widget;
+    GObject     *plug_child, *display_widget_container;
     GError      *error = NULL;
 
     /* Load the Gtk user-interface file */
@@ -1743,6 +2206,21 @@ display_settings_show_main_dialog (GdkDisplay *display)
             /* Get plug child widget */
             plug_child = gtk_builder_get_object (builder, "plug-child");
             gtk_widget_reparent (GTK_WIDGET (plug_child), plug);
+            
+            
+            display_widget = (GtkWidget *)foo_scroll_area_new ();
+            
+            /* FIXME: this should be computed dynamically */
+            foo_scroll_area_set_min_size (FOO_SCROLL_AREA (display_widget), -1, 200);
+            gtk_widget_show (display_widget);
+            g_signal_connect (display_widget, "paint",
+		      G_CALLBACK (on_area_paint), app);
+            g_signal_connect (display_widget, "viewport_changed",
+		      G_CALLBACK (on_viewport_changed), app);
+            
+            display_widget_container = gtk_builder_get_object(builder, "display-widget");
+            gtk_container_add (GTK_CONTAINER (display_widget_container), display_widget);
+            
             gtk_widget_show (GTK_WIDGET (plug_child));
         }
 
diff --git a/dialogs/display-settings/scrollarea.c b/dialogs/display-settings/scrollarea.c
new file mode 100644
index 0000000..77f7a66
--- /dev/null
+++ b/dialogs/display-settings/scrollarea.c
@@ -0,0 +1,1944 @@
+/* Copyright 2006, 2007, 2008, Soren Sandmann <sandmann at daimi.au.dk>
+ *
+ * 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., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#include <gdk/gdkprivate.h> /* For GDK_PARENT_RELATIVE_BG */
+#include "scrollarea.h"
+#include "foo-marshal.h"
+
+G_DEFINE_TYPE (FooScrollArea, foo_scroll_area, GTK_TYPE_CONTAINER);
+
+static GtkWidgetClass *parent_class;
+
+typedef struct BackingStore BackingStore;
+
+typedef void (* ExposeFunc) (cairo_t *cr, GdkRegion *region, gpointer data);
+
+#if 0
+static void          backing_store_draw (BackingStore *store, 
+					 GdkDrawable *dest,
+					 GdkRegion *clip,
+					 int dest_x,
+					 int dest_y);
+static void          backing_store_scroll (BackingStore *store,
+					   int dx, int dy);
+static void          backing_store_invalidate_rect (BackingStore *store,
+						    GdkRectangle *rect);
+static void          backing_store_invalidate_region (BackingStore *store,
+						      GdkRegion *region);
+static void          backing_store_invalidate_all (BackingStore *store);
+static BackingStore *backing_store_new (GdkWindow *window,
+					int width, int height);
+static void          backing_store_resize (BackingStore *store,
+					   int width, int height);
+static void          backing_store_process_updates (BackingStore *store,
+						    ExposeFunc func,
+						    gpointer data);
+static void	     backing_store_free (BackingStore *store);
+#endif
+
+typedef struct InputPath InputPath;
+typedef struct InputRegion InputRegion;
+typedef struct AutoScrollInfo AutoScrollInfo;
+
+struct InputPath
+{
+    gboolean			is_stroke;
+    cairo_fill_rule_t		fill_rule;
+    double			line_width;
+    cairo_path_t	       *path;		/* In canvas coordinates */
+
+    FooScrollAreaEventFunc	func;
+    gpointer			data;
+
+    InputPath		       *next;
+};
+
+/* InputRegions are mutually disjoint */
+struct InputRegion
+{
+    GdkRegion *region;		/* the boundary of this area in canvas coordinates */
+    InputPath *paths;
+};
+
+struct AutoScrollInfo
+{
+    int				dx;
+    int				dy;
+    int				timeout_id;
+    int				begin_x;
+    int				begin_y;
+    double			res_x;
+    double			res_y;
+    GTimer		       *timer;
+};
+
+struct FooScrollAreaPrivate
+{
+    GdkWindow		       *input_window;
+    
+    int				width;
+    int				height;
+    
+    GtkAdjustment	       *hadj;
+    GtkAdjustment	       *vadj;
+    int			        x_offset;
+    int				y_offset;
+    
+    int				min_width;
+    int				min_height;
+
+    GPtrArray		       *input_regions;
+    
+    AutoScrollInfo	       *auto_scroll_info;
+    
+    /* During expose, this region is set to the region
+     * being exposed. At other times, it is NULL
+     *
+     * It is used for clipping of input areas
+     */
+    GdkRegion		       *expose_region;
+    InputRegion		       *current_input;
+    
+    gboolean			grabbed;
+    FooScrollAreaEventFunc	grab_func;
+    gpointer			grab_data;
+
+    GdkPixmap		       *pixmap;
+    GdkRegion		       *update_region;		/* In canvas coordinates */
+};
+
+enum
+{
+    VIEWPORT_CHANGED,
+    PAINT,
+    INPUT,
+    LAST_SIGNAL,
+};
+
+static guint signals [LAST_SIGNAL] = { 0 };
+
+static void foo_scroll_area_size_request (GtkWidget *widget,
+					  GtkRequisition *requisition);
+static gboolean foo_scroll_area_expose (GtkWidget *widget,
+					GdkEventExpose *expose);
+static void foo_scroll_area_size_allocate (GtkWidget *widget,
+					   GtkAllocation *allocation);
+static void foo_scroll_area_set_scroll_adjustments (FooScrollArea *scroll_area,
+						    GtkAdjustment    *hadjustment,
+						    GtkAdjustment    *vadjustment);
+static void foo_scroll_area_realize (GtkWidget *widget);
+static void foo_scroll_area_unrealize (GtkWidget *widget);
+static void foo_scroll_area_map (GtkWidget *widget);
+static void foo_scroll_area_unmap (GtkWidget *widget);
+static gboolean foo_scroll_area_button_press (GtkWidget *widget,
+					      GdkEventButton *event);
+static gboolean foo_scroll_area_button_release (GtkWidget *widget,
+						GdkEventButton *event);
+static gboolean foo_scroll_area_motion (GtkWidget *widget,
+					GdkEventMotion *event);
+
+static void
+foo_scroll_area_map (GtkWidget *widget)
+{
+    FooScrollArea *area = FOO_SCROLL_AREA (widget);
+    
+    GTK_WIDGET_CLASS (parent_class)->map (widget);
+    
+    if (area->priv->input_window)
+	gdk_window_show (area->priv->input_window);
+}
+
+static void
+foo_scroll_area_unmap (GtkWidget *widget)
+{
+    FooScrollArea *area = FOO_SCROLL_AREA (widget);
+    
+    if (area->priv->input_window)
+	gdk_window_hide (area->priv->input_window);
+    
+    GTK_WIDGET_CLASS (parent_class)->unmap (widget);
+}
+
+static void
+foo_scroll_area_finalize (GObject *object)
+{
+    FooScrollArea *scroll_area = FOO_SCROLL_AREA (object);
+    
+    g_object_unref (scroll_area->priv->hadj);
+    g_object_unref (scroll_area->priv->vadj);
+    
+    g_ptr_array_free (scroll_area->priv->input_regions, TRUE);
+    
+    g_free (scroll_area->priv);
+
+    G_OBJECT_CLASS (foo_scroll_area_parent_class)->finalize (object);
+}
+
+static void
+foo_scroll_area_class_init (FooScrollAreaClass *class)
+{
+    GObjectClass *object_class = G_OBJECT_CLASS (class);
+    GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class);
+    
+    object_class->finalize = foo_scroll_area_finalize;
+    widget_class->size_request = foo_scroll_area_size_request;
+    widget_class->expose_event = foo_scroll_area_expose;
+    widget_class->size_allocate = foo_scroll_area_size_allocate;
+    widget_class->realize = foo_scroll_area_realize;
+    widget_class->unrealize = foo_scroll_area_unrealize;
+    widget_class->button_press_event = foo_scroll_area_button_press;
+    widget_class->button_release_event = foo_scroll_area_button_release;
+    widget_class->motion_notify_event = foo_scroll_area_motion;
+    widget_class->map = foo_scroll_area_map;
+    widget_class->unmap = foo_scroll_area_unmap;
+    
+    class->set_scroll_adjustments = foo_scroll_area_set_scroll_adjustments;
+    
+    parent_class = g_type_class_peek_parent (class);
+    
+    signals[VIEWPORT_CHANGED] =
+	g_signal_new ("viewport_changed",
+		      G_OBJECT_CLASS_TYPE (object_class),
+		      G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+		      G_STRUCT_OFFSET (FooScrollAreaClass,
+				       viewport_changed),
+		      NULL, NULL,
+		      foo_marshal_VOID__BOXED_BOXED,
+		      G_TYPE_NONE, 2,
+		      GDK_TYPE_RECTANGLE,
+		      GDK_TYPE_RECTANGLE);
+    
+    signals[PAINT] =
+	g_signal_new ("paint",
+		      G_OBJECT_CLASS_TYPE (object_class),
+		      G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+		      G_STRUCT_OFFSET (FooScrollAreaClass,
+				       paint),
+		      NULL, NULL,
+		      foo_marshal_VOID__POINTER_BOXED_POINTER,
+		      G_TYPE_NONE, 3,
+		      G_TYPE_POINTER,
+		      GDK_TYPE_RECTANGLE, 
+		      G_TYPE_POINTER);
+    
+    widget_class->set_scroll_adjustments_signal =
+	g_signal_new ("set_scroll_adjustments",
+		      G_OBJECT_CLASS_TYPE (object_class),
+		      G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+		      G_STRUCT_OFFSET (FooScrollAreaClass,
+				       set_scroll_adjustments),
+		      NULL, NULL,
+		      foo_marshal_VOID__OBJECT_OBJECT,
+		      G_TYPE_NONE, 2,
+		      GTK_TYPE_ADJUSTMENT,
+		      GTK_TYPE_ADJUSTMENT);
+}
+
+static GtkAdjustment *
+new_adjustment (void)
+{
+    return GTK_ADJUSTMENT (gtk_adjustment_new (0.0, 0.0, 0.0, 0.0, 0.0, 0.0));
+}
+
+static void
+foo_scroll_area_init (FooScrollArea *scroll_area)
+{
+    GtkWidget *widget;
+
+    widget = GTK_WIDGET (scroll_area);
+
+    gtk_widget_set_has_window (widget, FALSE);
+    gtk_widget_set_redraw_on_allocate (widget, FALSE);
+    
+    scroll_area->priv = g_new0 (FooScrollAreaPrivate, 1);
+    scroll_area->priv->width = 0;
+    scroll_area->priv->height = 0;
+    scroll_area->priv->hadj = g_object_ref_sink (new_adjustment());
+    scroll_area->priv->vadj = g_object_ref_sink (new_adjustment());
+    scroll_area->priv->x_offset = 0.0;
+    scroll_area->priv->y_offset = 0.0;
+    scroll_area->priv->min_width = -1;
+    scroll_area->priv->min_height = -1;
+    scroll_area->priv->auto_scroll_info = NULL;
+    scroll_area->priv->input_regions = g_ptr_array_new ();
+    scroll_area->priv->pixmap = NULL;
+    scroll_area->priv->update_region = gdk_region_new ();
+
+    gtk_widget_set_double_buffered (widget, FALSE);
+}
+
+static void
+translate_cairo_device (cairo_t       *cr,
+			int            x_offset,
+			int            y_offset)
+{
+    cairo_surface_t *surface = cairo_get_target (cr);
+    double dev_x;
+    double dev_y;
+    
+    cairo_surface_get_device_offset (surface, &dev_x, &dev_y);
+    dev_x += x_offset;
+    dev_y += y_offset;
+    cairo_surface_set_device_offset (surface, dev_x, dev_y);
+}
+
+#if 0
+static void
+print_region (const char *header, GdkRegion *region)
+{
+    GdkRectangle *rects;
+    int n_rects;
+    int i;
+    
+    g_print ("%s\n", header);
+    
+    gdk_region_get_rectangles (region, &rects, &n_rects);
+    for (i = 0; i < n_rects; ++i)
+    {
+	GdkRectangle *rect = &(rects[i]);
+	g_print ("  %d %d %d %d\n",
+		 rect->x, rect->y, rect->width, rect->height);
+    }
+}
+#endif
+
+typedef void (* PathForeachFunc) (double  *x,
+				  double  *y,
+				  gpointer data);
+
+static void
+path_foreach_point (cairo_path_t     *path,
+		    PathForeachFunc   func,
+		    gpointer	      user_data)
+{
+    int i;
+    
+    for (i = 0; i < path->num_data; i += path->data[i].header.length)
+    {
+	cairo_path_data_t *data = &(path->data[i]);
+	
+	switch (data->header.type)
+	{
+	case CAIRO_PATH_MOVE_TO:
+	case CAIRO_PATH_LINE_TO:
+	    func (&(data[1].point.x), &(data[1].point.y), user_data);
+	    break;
+	    
+	case CAIRO_PATH_CURVE_TO:
+	    func (&(data[1].point.x), &(data[1].point.y), user_data);
+	    func (&(data[2].point.x), &(data[2].point.y), user_data);
+	    func (&(data[3].point.x), &(data[3].point.y), user_data);
+	    break;
+	    
+	case CAIRO_PATH_CLOSE_PATH:
+	    break;
+	}
+    }
+}
+
+typedef struct
+{
+    double x1, y1, x2, y2;
+} Box;
+
+#if 0
+static void
+update_box (double *x, double *y, gpointer data)
+{
+    Box *box = data;
+    
+    if (*x < box->x1)
+	box->x1 = *x;
+
+    if (*y < box->y1)
+	box->y1 = *y;
+
+    if (*y > box->y2)
+	box->y2 = *y;
+    
+    if (*x > box->x2)
+	box->x2 = *x;
+}
+#endif
+
+#if 0
+static void
+path_compute_extents (cairo_path_t *path,
+		      GdkRectangle *rect)
+{
+    if (rect)
+    {
+	Box box = { G_MAXDOUBLE, G_MAXDOUBLE, G_MINDOUBLE, G_MINDOUBLE };
+
+	path_foreach_point (path, update_box, &box);
+
+	rect->x = box.x1;
+	rect->y = box.y1;
+	rect->width = box.x2 - box.x1;
+	rect->height = box.y2 - box.y1;
+    }
+}
+#endif
+
+static void
+input_path_free_list (InputPath *paths)
+{
+    if (!paths)
+	return;
+
+    input_path_free_list (paths->next);
+    cairo_path_destroy (paths->path);
+    g_free (paths);
+}
+
+static void
+input_region_free (InputRegion *region)
+{
+    input_path_free_list (region->paths);
+    gdk_region_destroy (region->region);
+
+    g_free (region);
+}
+
+static void
+get_viewport (FooScrollArea *scroll_area,
+	      GdkRectangle  *viewport)
+{
+    GtkAllocation allocation;
+    GtkWidget *widget = GTK_WIDGET (scroll_area);
+
+    gtk_widget_get_allocation (widget, &allocation);
+
+    viewport->x = scroll_area->priv->x_offset;
+    viewport->y = scroll_area->priv->y_offset;
+    viewport->width = allocation.width;
+    viewport->height = allocation.height;
+}
+
+static void
+allocation_to_canvas (FooScrollArea *area,
+		      int           *x,
+		      int           *y)
+{
+    *x += area->priv->x_offset;
+    *y += area->priv->y_offset;
+}
+
+static void
+clear_exposed_input_region (FooScrollArea *area,
+			    GdkRegion *exposed)	/* in canvas coordinates */
+{
+    guint i;
+    GdkRegion *viewport;
+    GdkRectangle allocation;
+
+    gtk_widget_get_allocation (GTK_WIDGET (area), &allocation);
+    allocation.x = 0;
+    allocation.y = 0;
+    allocation_to_canvas (area, &allocation.x, &allocation.y);
+    viewport = gdk_region_rectangle (&allocation);
+    gdk_region_subtract (viewport, exposed);
+    
+    for (i = 0; i < area->priv->input_regions->len; ++i)
+    {
+	InputRegion *region = area->priv->input_regions->pdata[i];
+
+	gdk_region_intersect (region->region, viewport);
+
+	if (gdk_region_empty (region->region))
+	{
+	    input_region_free (region);
+	    g_ptr_array_remove_index_fast (area->priv->input_regions, i--);
+	}
+    }
+
+    gdk_region_destroy (viewport);
+    
+#if 0
+	path = region->paths;
+	while (path != NULL)
+	{
+	    GdkRectangle rect;
+
+	    path_compute_extents (path->path, &rect);
+
+	    if (gdk_region_rect_in (area->priv->expose_region, &rect) == GDK_OVERLAP_RECTANGLE_IN)
+		g_print ("we would have deleted it\n");
+#if 0
+	    else
+		g_print ("nope (%d %d %d %d)\n", );
+#endif
+	    
+	    path = path->next;
+	}
+	
+	/* FIXME: we should also delete paths (and path segments)
+	 * completely contained in the expose_region
+	 */
+    }
+#endif
+}
+
+static void
+setup_background_cr (GdkWindow *window,
+		     cairo_t   *cr,
+		     int        x_offset,
+		     int        y_offset)
+{
+    GdkWindowObject *private = (GdkWindowObject *)window;
+    
+    if (private->bg_pixmap == GDK_PARENT_RELATIVE_BG && private->parent)
+    {
+	x_offset += private->x;
+	y_offset += private->y;
+	
+	setup_background_cr (GDK_WINDOW (private->parent), cr, x_offset, y_offset);
+    }
+    else if (private->bg_pixmap &&
+	     private->bg_pixmap != GDK_PARENT_RELATIVE_BG &&
+	     private->bg_pixmap != GDK_NO_BG)
+    {
+	gdk_cairo_set_source_pixmap (cr, private->bg_pixmap, -x_offset, -y_offset);
+    }
+    else
+    {
+	gdk_cairo_set_source_color (cr, &private->bg_color);
+    }
+}
+
+static void
+initialize_background (GtkWidget *widget,
+		       cairo_t   *cr)
+{
+    setup_background_cr (gtk_widget_get_window (widget), cr, 0, 0);
+
+    cairo_paint (cr);
+}
+
+static void
+clip_to_region (cairo_t *cr, GdkRegion *region)
+{
+    int n_rects;
+    GdkRectangle *rects;
+
+    gdk_region_get_rectangles (region, &rects, &n_rects);
+
+    cairo_new_path (cr);
+    while (n_rects--)
+    {
+	GdkRectangle *rect = &(rects[n_rects]);
+
+	cairo_rectangle (cr, rect->x, rect->y, rect->width, rect->height);
+    }
+    cairo_clip (cr);
+
+    g_free (rects);
+}
+
+static void
+simple_draw_drawable (GdkDrawable *dst,
+		      GdkDrawable *src,
+		      int	   src_x,
+		      int	   src_y,
+		      int          dst_x,
+		      int          dst_y,
+		      int          width,
+		      int          height)
+{
+    GdkGC *gc = gdk_gc_new (dst);
+
+    gdk_draw_drawable (dst, gc, src, src_x, src_y, dst_x, dst_y, width, height);
+
+    g_object_unref (gc);
+}
+
+static gboolean
+foo_scroll_area_expose (GtkWidget *widget,
+			GdkEventExpose *expose)
+{
+    FooScrollArea *scroll_area = FOO_SCROLL_AREA (widget);
+    cairo_t *cr;
+    GdkRectangle extents;
+    GdkRegion *region;
+    int x_offset, y_offset;
+    GdkGC *gc;
+    GtkAllocation widget_allocation;
+    GdkWindow *window = gtk_widget_get_window (widget);
+
+    /* I don't think expose can ever recurse for the same area */
+    g_assert (!scroll_area->priv->expose_region);
+    
+    /* Note that this function can be called at a time
+     * where the adj->value is different from x_offset. 
+     * Ie., the GtkScrolledWindow changed the adj->value
+     * without emitting the value_changed signal. 
+     *
+     * Hence we must always use the value we got 
+     * the last time the signal was emitted, ie.,
+     * priv->{x,y}_offset.
+     */
+    
+    x_offset = scroll_area->priv->x_offset;
+    y_offset = scroll_area->priv->y_offset;
+    
+    scroll_area->priv->expose_region = expose->region;
+
+    /* Setup input areas */
+    clear_exposed_input_region (scroll_area, scroll_area->priv->update_region);
+    
+    scroll_area->priv->current_input = g_new0 (InputRegion, 1);
+    scroll_area->priv->current_input->region = gdk_region_copy (scroll_area->priv->update_region);
+    scroll_area->priv->current_input->paths = NULL;
+    g_ptr_array_add (scroll_area->priv->input_regions,
+		     scroll_area->priv->current_input);
+
+    region = scroll_area->priv->update_region;
+    scroll_area->priv->update_region = gdk_region_new ();
+    
+    /* Create cairo context */
+    cr = gdk_cairo_create (scroll_area->priv->pixmap);
+    translate_cairo_device (cr, -x_offset, -y_offset);
+    clip_to_region (cr, region);
+    initialize_background (widget, cr);
+
+    /* Create regions */
+    gdk_region_get_clipbox (region, &extents);
+
+    g_signal_emit (widget, signals[PAINT], 0, cr, &extents, region);
+
+    /* Destroy stuff */
+    cairo_destroy (cr);
+    
+    scroll_area->priv->expose_region = NULL;
+    scroll_area->priv->current_input = NULL;
+
+    /* Finally draw the backing pixmap */
+    gc = gdk_gc_new (window);
+
+    gdk_gc_set_clip_region (gc, expose->region);
+
+    gtk_widget_get_allocation (widget, &widget_allocation);
+    gdk_draw_drawable (window, gc, scroll_area->priv->pixmap,
+		       0, 0, widget_allocation.x, widget_allocation.y,
+		       widget_allocation.width, widget_allocation.height);
+
+    g_object_unref (gc);
+    gdk_region_destroy (region);
+    
+    return TRUE;
+}
+
+void
+foo_scroll_area_get_viewport (FooScrollArea *scroll_area,
+			      GdkRectangle  *viewport)
+{
+    g_return_if_fail (FOO_IS_SCROLL_AREA (scroll_area));
+    
+    if (!viewport)
+	return;
+    
+    get_viewport (scroll_area, viewport);
+}
+
+static void
+process_event (FooScrollArea	       *scroll_area,
+	       FooScrollAreaEventType	input_type,
+	       int			x,
+	       int			y);
+
+static void
+emit_viewport_changed (FooScrollArea *scroll_area,
+		       GdkRectangle  *new_viewport,
+		       GdkRectangle  *old_viewport)
+{
+    int px, py;
+    g_signal_emit (scroll_area, signals[VIEWPORT_CHANGED], 0, 
+		   new_viewport, old_viewport);
+    
+    gdk_window_get_pointer (scroll_area->priv->input_window, &px, &py, NULL);
+    
+#if 0
+    g_print ("procc\n");
+#endif
+    
+    process_event (scroll_area, FOO_MOTION, px, py);
+}
+
+static void
+clamp_adjustment (GtkAdjustment *adj)
+{
+    if (gtk_adjustment_get_upper (adj) >= gtk_adjustment_get_page_size (adj))
+	gtk_adjustment_set_value (adj, CLAMP (gtk_adjustment_get_value (adj), 0.0,
+					      gtk_adjustment_get_upper (adj)
+					       - gtk_adjustment_get_page_size (adj)));
+    else
+	gtk_adjustment_set_value (adj, 0.0);
+    
+    gtk_adjustment_changed (adj);
+}
+
+static gboolean
+set_adjustment_values (FooScrollArea *scroll_area)
+{
+    GtkAllocation allocation;
+
+    GtkAdjustment *hadj = scroll_area->priv->hadj;
+    GtkAdjustment *vadj = scroll_area->priv->vadj;
+    
+    /* Horizontal */
+    gtk_widget_get_allocation (GTK_WIDGET (scroll_area), &allocation);
+    g_object_freeze_notify (G_OBJECT (hadj));
+    gtk_adjustment_set_page_size (hadj, allocation.width);
+    gtk_adjustment_set_step_increment (hadj, 0.1 * allocation.width);
+    gtk_adjustment_set_page_increment (hadj, 0.9 * allocation.width);
+    gtk_adjustment_set_lower (hadj, 0.0);
+    gtk_adjustment_set_upper (hadj, scroll_area->priv->width);
+    g_object_thaw_notify (G_OBJECT (hadj));
+    
+    /* Vertical */
+    g_object_freeze_notify (G_OBJECT (vadj));
+    gtk_adjustment_set_page_size (vadj, allocation.height);
+    gtk_adjustment_set_step_increment (vadj, 0.1 * allocation.height);
+    gtk_adjustment_set_page_increment (vadj, 0.9 * allocation.height);
+    gtk_adjustment_set_lower (vadj, 0.0);
+    gtk_adjustment_set_upper (vadj, scroll_area->priv->height);
+    g_object_thaw_notify (G_OBJECT (vadj));
+
+    clamp_adjustment (hadj);
+    clamp_adjustment (vadj);
+    
+    return TRUE;
+}
+
+static void
+foo_scroll_area_realize (GtkWidget *widget)
+{
+    FooScrollArea *area = FOO_SCROLL_AREA (widget);
+    GdkWindowAttr attributes;
+    GtkAllocation widget_allocation;
+    GdkWindow *window;
+    gint attributes_mask;
+
+    gtk_widget_get_allocation (widget, &widget_allocation);
+    gtk_widget_set_realized (widget, TRUE);
+    
+    attributes.window_type = GDK_WINDOW_CHILD;
+    attributes.x = widget_allocation.x;
+    attributes.y = widget_allocation.y;
+    attributes.width = widget_allocation.width;
+    attributes.height = widget_allocation.height;
+    attributes.wclass = GDK_INPUT_ONLY;
+    attributes.event_mask = gtk_widget_get_events (widget);
+    attributes.event_mask |= (GDK_BUTTON_PRESS_MASK |
+			      GDK_BUTTON_RELEASE_MASK |
+			      GDK_BUTTON1_MOTION_MASK |
+			      GDK_BUTTON2_MOTION_MASK |
+			      GDK_BUTTON3_MOTION_MASK |
+			      GDK_POINTER_MOTION_MASK |
+			      GDK_ENTER_NOTIFY_MASK |
+			      GDK_LEAVE_NOTIFY_MASK);
+    
+    attributes_mask = GDK_WA_X | GDK_WA_Y;
+
+    window = gtk_widget_get_parent_window (widget);
+    gtk_widget_set_window (widget, window);
+    g_object_ref (window);
+    
+    area->priv->input_window = gdk_window_new (window,
+					       &attributes, attributes_mask);
+    area->priv->pixmap = gdk_pixmap_new (window,
+					 widget_allocation.width,
+					 widget_allocation.height,
+					 -1);
+    gdk_window_set_user_data (area->priv->input_window, area);
+    
+    gtk_widget_style_attach (widget);
+}
+
+static void
+foo_scroll_area_unrealize (GtkWidget *widget)
+{
+    FooScrollArea *area = FOO_SCROLL_AREA (widget);
+    
+    if (area->priv->input_window)
+    {
+	gdk_window_set_user_data (area->priv->input_window, NULL);
+	gdk_window_destroy (area->priv->input_window);
+	area->priv->input_window = NULL;
+    }
+    
+    GTK_WIDGET_CLASS (parent_class)->unrealize (widget);
+}
+
+static GdkPixmap *
+create_new_pixmap (GtkWidget *widget,
+		   GdkPixmap *old)
+{
+    GtkAllocation widget_allocation;
+    GdkPixmap *new;
+
+    gtk_widget_get_allocation (widget, &widget_allocation);
+    new = gdk_pixmap_new (gtk_widget_get_window (widget),
+			  widget_allocation.width,
+			  widget_allocation.height,
+			  -1);
+
+    /* Unfortunately we don't know in which direction we were resized,
+     * so we just assume we were dragged from the south-east corner.
+     *
+     * Although, maybe we could get the root coordinates of the input-window?
+     * That might just work, actually. We need to make sure marco uses
+     * static gravity for the window before this will be useful.
+     */
+    simple_draw_drawable (new, old, 0, 0, 0, 0, -1, -1);
+
+    return new;
+}
+		   
+static void
+allocation_to_canvas_region (FooScrollArea *area,
+			     GdkRegion *region)
+{
+    gdk_region_offset (region, area->priv->x_offset, area->priv->y_offset);
+}
+			     
+
+static void
+foo_scroll_area_size_allocate (GtkWidget     *widget,
+			       GtkAllocation *allocation)
+{
+    FooScrollArea *scroll_area = FOO_SCROLL_AREA (widget);
+    GdkRectangle new_viewport;
+    GdkRectangle old_viewport;
+    GdkRegion *old_allocation;
+    GdkRegion *invalid;
+    GtkAllocation widget_allocation;
+
+    get_viewport (scroll_area, &old_viewport);
+
+    gtk_widget_get_allocation (widget, &widget_allocation);
+    old_allocation = gdk_region_rectangle (&widget_allocation);
+    gdk_region_offset (old_allocation,
+		       -widget_allocation.x, -widget_allocation.y);
+    invalid = gdk_region_rectangle (allocation);
+    gdk_region_offset (invalid, -allocation->x, -allocation->y);
+    gdk_region_xor (invalid, old_allocation);
+    allocation_to_canvas_region (scroll_area, invalid);
+    foo_scroll_area_invalidate_region (scroll_area, invalid);
+    gdk_region_destroy (old_allocation);
+    gdk_region_destroy (invalid);
+
+    gtk_widget_set_allocation (widget, allocation);
+    
+    if (scroll_area->priv->input_window)
+    {
+	GdkPixmap *new_pixmap;
+	
+	gdk_window_move_resize (scroll_area->priv->input_window,
+				allocation->x, allocation->y,
+				allocation->width, allocation->height);
+
+	new_pixmap = create_new_pixmap (widget, scroll_area->priv->pixmap);
+
+	g_object_unref (scroll_area->priv->pixmap);
+
+	scroll_area->priv->pixmap = new_pixmap;
+    }
+    
+    get_viewport (scroll_area, &new_viewport);
+    
+    emit_viewport_changed (scroll_area, &new_viewport, &old_viewport);
+}
+
+static void
+emit_input (FooScrollArea *scroll_area,
+	    FooScrollAreaEventType type,
+	    int			   x,
+	    int			   y,
+	    FooScrollAreaEventFunc func,
+	    gpointer		data)
+{
+    FooScrollAreaEvent event;
+    
+    if (!func)
+	return;
+
+    if (type != FOO_MOTION)
+	emit_input (scroll_area, FOO_MOTION, x, y, func, data);
+    
+#if 0
+    x += scroll_area->priv->x_offset;
+    y += scroll_area->priv->y_offset;
+#endif
+    
+    event.type = type;
+    event.x = x;
+    event.y = y;
+    
+    func (scroll_area, &event, data);
+}
+
+#if 0
+static void
+print_path (const char *header,
+	    cairo_path_t *path)
+{
+    int i;
+
+    g_print ("%s\n", header);
+
+    for (i=0; i < path->num_data; i += path->data[i].header.length)
+    {
+	cairo_path_data_t *data = &(path->data[i]);
+	
+	switch (data->header.type)
+	{
+	case CAIRO_PATH_MOVE_TO:
+	    g_print ("move to:    %f, %f\n", data[1].point.x, data[1].point.y);
+	    break;
+	    
+	case CAIRO_PATH_LINE_TO:
+	    g_print ("line to:    %f, %f\n", data[1].point.x, data[1].point.y);
+	    break;
+	    
+	case CAIRO_PATH_CURVE_TO:
+	    g_print ("curve to:   %f, %f\n", data[1].point.x, data[1].point.y);
+	    g_print ("            %f, %f\n", data[1].point.x, data[1].point.y);
+	    g_print ("            %f, %f\n", data[1].point.x, data[1].point.y);
+	    break;
+	    
+	case CAIRO_PATH_CLOSE_PATH:
+	    break;
+	}
+    }
+}
+#endif
+
+static void
+process_event (FooScrollArea	       *scroll_area,
+	       FooScrollAreaEventType	input_type,
+	       int			x,
+	       int			y)
+{
+    GtkWidget *widget = GTK_WIDGET (scroll_area);
+    guint i;
+
+    allocation_to_canvas (scroll_area, &x, &y);
+    
+    if (scroll_area->priv->grabbed)
+    {
+	emit_input (scroll_area, input_type, x, y,
+		    scroll_area->priv->grab_func,
+		    scroll_area->priv->grab_data);
+	return;
+    }
+
+    
+#if 0
+    x += widget->allocation.x;
+    y += widget->allocation.y;
+#endif
+
+#if 0
+    g_print ("number of input regions: %d\n", scroll_area->priv->input_regions->len);
+#endif
+    
+    for (i = 0; i < scroll_area->priv->input_regions->len; ++i)
+    {
+	InputRegion *region = scroll_area->priv->input_regions->pdata[i];
+
+#if 0
+	g_print ("%d ", i);
+	print_region ("region:", region->region);
+#endif
+	
+	if (gdk_region_point_in (region->region, x, y))
+	{
+	    InputPath *path;
+
+	    path = region->paths;
+	    while (path)
+	    {
+		cairo_t *cr;
+		gboolean inside;
+
+		cr = gdk_cairo_create (gtk_widget_get_window (widget));
+		cairo_set_fill_rule (cr, path->fill_rule);
+		cairo_set_line_width (cr, path->line_width);
+		cairo_append_path (cr, path->path);
+
+		if (path->is_stroke)
+		    inside = cairo_in_stroke (cr, x, y);
+		else
+		    inside = cairo_in_fill (cr, x, y);
+
+		cairo_destroy (cr);
+		
+		if (inside)
+		{
+		    emit_input (scroll_area, input_type,
+				x, y,
+				path->func,
+				path->data);
+		    return;
+		}
+		
+		path = path->next;
+	    }
+
+	    /* Since the regions are all disjoint, no other region
+	     * can match. Of course we could be clever and try and
+	     * sort the regions, but so far I have been unable to
+	     * make this loop show up on a profile.
+	     */
+	    return;
+	}
+    }
+}
+
+static void
+process_gdk_event (FooScrollArea *scroll_area,
+		   int		  x,
+		   int	          y,
+		   GdkEvent      *event)
+{
+    FooScrollAreaEventType input_type;
+    
+    if (event->type == GDK_BUTTON_PRESS)
+	input_type = FOO_BUTTON_PRESS;
+    else if (event->type == GDK_BUTTON_RELEASE)
+	input_type = FOO_BUTTON_RELEASE;
+    else if (event->type == GDK_MOTION_NOTIFY)
+	input_type = FOO_MOTION;
+    else
+	return;
+    
+    process_event (scroll_area, input_type, x, y);
+}
+
+static gboolean
+foo_scroll_area_button_press (GtkWidget *widget,
+			      GdkEventButton *event)
+{
+    FooScrollArea *area = FOO_SCROLL_AREA (widget);
+    
+    process_gdk_event (area, event->x, event->y, (GdkEvent *)event);
+    
+    return TRUE;
+}
+
+static gboolean
+foo_scroll_area_button_release (GtkWidget *widget,
+				GdkEventButton *event)
+{
+    FooScrollArea *area = FOO_SCROLL_AREA (widget);
+    
+    process_gdk_event (area, event->x, event->y, (GdkEvent *)event);
+    
+    return FALSE;
+}
+
+static gboolean
+foo_scroll_area_motion (GtkWidget *widget,
+			GdkEventMotion *event)
+{
+    FooScrollArea *area = FOO_SCROLL_AREA (widget);
+    
+    process_gdk_event (area, event->x, event->y, (GdkEvent *)event);
+    return TRUE;
+}
+
+void
+foo_scroll_area_set_size_fixed_y (FooScrollArea	       *scroll_area,
+				  int			width,
+				  int			height,
+				  int			old_y,
+				  int			new_y)
+{
+    scroll_area->priv->width = width;
+    scroll_area->priv->height = height;
+    
+#if 0
+    g_print ("diff: %d\n", new_y - old_y);
+#endif
+    g_object_thaw_notify (G_OBJECT (scroll_area->priv->vadj));
+    gtk_adjustment_set_value (scroll_area->priv->vadj, new_y);
+    
+    set_adjustment_values (scroll_area);
+    g_object_thaw_notify (G_OBJECT (scroll_area->priv->vadj));
+}
+
+void
+foo_scroll_area_set_size (FooScrollArea	       *scroll_area,
+			  int			width,
+			  int			height)
+{
+    g_return_if_fail (FOO_IS_SCROLL_AREA (scroll_area));
+    
+    /* FIXME: Default scroll algorithm should probably be to
+     * keep the same *area* outside the screen as before.
+     *
+     * For wrapper widgets that will do something roughly
+     * right. For widgets that don't change size, it
+     * will do the right thing. Except for idle-layouting
+     * widgets.
+     *
+     * Maybe there should be some generic support for those
+     * widgets. Can that even be done?
+     *
+     * Should we have a version of this function using 
+     * fixed points?
+     */
+    
+    scroll_area->priv->width = width;
+    scroll_area->priv->height = height;
+    
+    set_adjustment_values (scroll_area);
+}
+
+static void
+foo_scroll_area_size_request (GtkWidget      *widget,
+			      GtkRequisition *requisition)
+{
+    FooScrollArea *scroll_area = FOO_SCROLL_AREA (widget);
+    
+    requisition->width = scroll_area->priv->min_width;
+    requisition->height = scroll_area->priv->min_height;
+    
+#if 0
+    g_print ("request %d %d\n", requisition->width, requisition->height);
+#endif
+}
+
+#if 0
+static void
+translate_point (double *x, double *y, gpointer data)
+{
+    int *translation = data;
+
+    *x += translation[0];
+    *y += translation[1];
+}
+#endif
+
+#if 0
+static void
+path_translate (cairo_path_t  *path,
+		int	       dx,
+		int	       dy)
+{
+    int translation[2] = {dx, dy};
+    
+    path_foreach_point (path, translate_point, translation);
+}
+#endif
+
+static void
+translate_input_regions (FooScrollArea *scroll_area,
+			 int		dx,
+			 int		dy)
+{
+#if 0
+    int i;
+
+    for (i = 0; i < scroll_area->priv->input_regions->len; ++i)
+    {
+	InputRegion *region = scroll_area->priv->input_regions->pdata[i];
+	InputPath *path;
+	
+	gdk_region_offset (region->region, dx, dy);
+
+	path = region->paths;
+	while (path != NULL)
+	{
+	    path_translate (path->path, dx, dy);
+	    path = path->next;
+	}
+    }
+#endif
+}
+
+#if 0
+static void
+paint_region (FooScrollArea *area, GdkRegion *region)
+{
+    int n_rects;
+    GdkRectangle *rects;
+    region = gdk_region_copy (region);
+    
+    gdk_region_get_rectangles (region, &rects, &n_rects);
+
+    gdk_region_offset (region,
+		       GTK_WIDGET (area)->allocation.x,
+		       GTK_WIDGET (area)->allocation.y);
+
+    GdkGC *gc = gdk_gc_new (GTK_WIDGET (area)->window);
+    gdk_gc_set_clip_region (gc, region);
+    gdk_draw_rectangle (GTK_WIDGET (area)->window, gc, TRUE, 0, 0, -1, -1);
+    g_object_unref (gc);
+    g_free (rects);
+}
+#endif
+
+static void
+foo_scroll_area_scroll (FooScrollArea *area,
+			gint dx, 
+			gint dy)
+{
+    GdkRectangle allocation;
+    GdkRectangle src_area;
+    GdkRectangle move_area;
+    GdkRegion *invalid_region;
+
+    gtk_widget_get_allocation (GTK_WIDGET (area), &allocation);
+    allocation.x = 0;
+    allocation.y = 0;
+
+    src_area = allocation;
+    src_area.x -= dx;
+    src_area.y -= dy;
+
+    invalid_region = gdk_region_rectangle (&allocation);
+    
+    if (gdk_rectangle_intersect (&allocation, &src_area, &move_area))
+    {
+	GdkRegion *move_region;
+
+#if 0
+	g_print ("scrolling %d %d %d %d (%d %d)\n",
+		 move_area.x, move_area.y,
+		 move_area.width, move_area.height,
+		 dx, dy);
+#endif
+	
+	simple_draw_drawable (area->priv->pixmap, area->priv->pixmap,
+			      move_area.x, move_area.y,
+			      move_area.x + dx, move_area.y + dy,
+			      move_area.width, move_area.height);
+	gtk_widget_queue_draw (GTK_WIDGET (area));
+	
+	move_region = gdk_region_rectangle (&move_area);
+	gdk_region_offset (move_region, dx, dy);
+	gdk_region_subtract (invalid_region, move_region);
+	gdk_region_destroy (move_region);
+    }
+
+#if 0
+    paint_region (area, invalid_region);
+#endif
+    
+    allocation_to_canvas_region (area, invalid_region);
+
+    foo_scroll_area_invalidate_region (area, invalid_region);
+    
+    gdk_region_destroy (invalid_region);
+}
+
+static void
+foo_scrollbar_adjustment_changed (GtkAdjustment *adj,
+				  FooScrollArea *scroll_area)
+{
+    GtkWidget *widget = GTK_WIDGET (scroll_area);
+    gint dx = 0;
+    gint dy = 0;
+    GdkRectangle old_viewport, new_viewport;
+    
+    get_viewport (scroll_area, &old_viewport);
+    
+    if (adj == scroll_area->priv->hadj)
+    {
+	/* FIXME: do we treat the offset as int or double, and,
+	 * if int, how do we round?
+	 */
+	dx = (int)gtk_adjustment_get_value (adj) - scroll_area->priv->x_offset;
+	scroll_area->priv->x_offset = gtk_adjustment_get_value (adj);
+    }
+    else if (adj == scroll_area->priv->vadj)
+    {
+	dy = (int)gtk_adjustment_get_value (adj) - scroll_area->priv->y_offset;
+	scroll_area->priv->y_offset = gtk_adjustment_get_value (adj);
+    }
+    else
+    {
+	g_assert_not_reached ();
+    }
+    
+    if (gtk_widget_get_realized (widget))
+    {
+	foo_scroll_area_scroll (scroll_area, -dx, -dy);
+    
+#if 0
+	window_scroll_area (widget->window, &widget->allocation, -dx, -dy);
+#endif
+	translate_input_regions (scroll_area, -dx, -dy);
+
+#if 0
+	gdk_window_process_updates (widget->window, TRUE);
+#endif
+    }
+    
+    get_viewport (scroll_area, &new_viewport);
+    
+    emit_viewport_changed (scroll_area, &new_viewport, &old_viewport);
+}
+
+static void
+set_one_adjustment (FooScrollArea *scroll_area,
+		    GtkAdjustment *adjustment,
+		    GtkAdjustment **location)
+{
+    g_return_if_fail (location != NULL);
+    
+    if (adjustment == *location)
+	return;
+    
+    if (!adjustment)
+	adjustment = new_adjustment ();
+    
+    g_return_if_fail (GTK_IS_ADJUSTMENT (adjustment));
+    
+    if (*location)
+    {
+	g_signal_handlers_disconnect_by_func (
+	    *location, foo_scrollbar_adjustment_changed, scroll_area);
+	
+	g_object_unref (*location);
+    }
+    
+    *location = adjustment;
+    
+    g_object_ref_sink (*location);
+    
+    g_signal_connect (*location, "value_changed",
+		      G_CALLBACK (foo_scrollbar_adjustment_changed),
+		      scroll_area);
+}
+
+static void
+foo_scroll_area_set_scroll_adjustments (FooScrollArea *scroll_area,
+					GtkAdjustment *hadjustment,
+					GtkAdjustment *vadjustment)
+{
+    set_one_adjustment (scroll_area, hadjustment, &scroll_area->priv->hadj);
+    set_one_adjustment (scroll_area, vadjustment, &scroll_area->priv->vadj);
+    
+    set_adjustment_values (scroll_area);
+}
+
+FooScrollArea *
+foo_scroll_area_new (void)
+{
+    return g_object_new (FOO_TYPE_SCROLL_AREA, NULL);
+}
+
+void
+foo_scroll_area_set_min_size (FooScrollArea *scroll_area,
+			      int		   min_width,
+			      int            min_height)
+{
+    scroll_area->priv->min_width = min_width;
+    scroll_area->priv->min_height = min_height;
+    
+    /* FIXME: think through invalidation.
+     *
+     * Goals: - no repainting everything on size_allocate(),
+     *        - make sure input boxes are invalidated when
+     *          needed
+     */
+    gtk_widget_queue_resize (GTK_WIDGET (scroll_area));
+}
+
+#if 0
+static void
+warn_about_adding_input_outside_expose (const char *func)
+{
+    static gboolean warned = FALSE;
+    
+    if (!warned)
+    {
+	g_warning ("%s() can only be called "
+		   "from the paint handler for the FooScrollArea\n", func);
+	
+	warned = TRUE;
+    }
+}
+#endif
+
+static void
+user_to_device (double *x, double *y,
+		gpointer data)
+{
+    cairo_t *cr = data;
+    
+    cairo_user_to_device (cr, x, y);
+}
+
+static InputPath *
+make_path (FooScrollArea *area,
+	   cairo_t *cr,
+	   gboolean is_stroke,
+	   FooScrollAreaEventFunc func,
+	   gpointer data)
+{
+    InputPath *path = g_new0 (InputPath, 1);
+
+    path->is_stroke = is_stroke;
+    path->fill_rule = cairo_get_fill_rule (cr);
+    path->line_width = cairo_get_line_width (cr);
+    path->path = cairo_copy_path (cr);
+    path_foreach_point (path->path, user_to_device, cr);
+    path->func = func;
+    path->data = data;
+    path->next = area->priv->current_input->paths;
+    area->priv->current_input->paths = path;
+    return path;
+}
+
+/* FIXME: we probably really want a
+ *
+ *	foo_scroll_area_add_input_from_fill (area, cr, ...);
+ * and
+ *      foo_scroll_area_add_input_from_stroke (area, cr, ...);
+ * as well.
+ */
+void
+foo_scroll_area_add_input_from_fill (FooScrollArea           *scroll_area,
+				     cairo_t	             *cr,
+				     FooScrollAreaEventFunc   func,
+				     gpointer                 data)
+{
+    g_return_if_fail (FOO_IS_SCROLL_AREA (scroll_area));
+    g_return_if_fail (cr != NULL);
+    g_return_if_fail (scroll_area->priv->current_input);
+
+    make_path (scroll_area, cr, FALSE, func, data);
+}
+
+void
+foo_scroll_area_add_input_from_stroke (FooScrollArea           *scroll_area,
+				       cairo_t	                *cr,
+				       FooScrollAreaEventFunc   func,
+				       gpointer                 data)
+{
+    g_return_if_fail (FOO_IS_SCROLL_AREA (scroll_area));
+    g_return_if_fail (cr != NULL);
+    g_return_if_fail (scroll_area->priv->current_input);
+
+    make_path (scroll_area, cr, TRUE, func, data);
+}
+
+void
+foo_scroll_area_invalidate (FooScrollArea *scroll_area)
+{
+    GtkAllocation allocation;
+    GtkWidget *widget = GTK_WIDGET (scroll_area);
+
+    gtk_widget_get_allocation (widget, &allocation);
+    foo_scroll_area_invalidate_rect (scroll_area,
+				     scroll_area->priv->x_offset, scroll_area->priv->y_offset,
+				     allocation.width,
+				     allocation.height);
+}
+
+static void
+canvas_to_window (FooScrollArea *area,
+		  GdkRegion *region)
+{
+    GtkAllocation allocation;
+    GtkWidget *widget = GTK_WIDGET (area);
+    
+    gtk_widget_get_allocation (widget, &allocation);
+    gdk_region_offset (region,
+		       -area->priv->x_offset + allocation.x,
+		       -area->priv->y_offset + allocation.y);
+}
+
+static void
+window_to_canvas (FooScrollArea *area,
+		  GdkRegion *region)
+{
+    GtkAllocation allocation;
+    GtkWidget *widget = GTK_WIDGET (area);
+
+    gtk_widget_get_allocation (widget, &allocation);
+    gdk_region_offset (region,
+		       area->priv->x_offset - allocation.x,
+		       area->priv->y_offset - allocation.y);
+}
+
+void
+foo_scroll_area_invalidate_region (FooScrollArea *area,
+				   GdkRegion     *region)
+{
+    GtkWidget *widget;
+
+    g_return_if_fail (FOO_IS_SCROLL_AREA (area));
+
+    widget = GTK_WIDGET (area);
+
+    gdk_region_union (area->priv->update_region, region);
+
+    if (gtk_widget_get_realized (widget))
+    {
+	canvas_to_window (area, region);
+	
+	gdk_window_invalidate_region (gtk_widget_get_window (widget),
+	                              region, TRUE);
+	
+	window_to_canvas (area, region);
+    }
+}
+
+void
+foo_scroll_area_invalidate_rect (FooScrollArea *scroll_area,
+				 int	        x,
+				 int	        y,
+				 int	        width,
+				 int	        height)
+{
+    GdkRectangle rect = { x, y, width, height };
+    GdkRegion *region;
+    
+    g_return_if_fail (FOO_IS_SCROLL_AREA (scroll_area));
+
+    region = gdk_region_rectangle (&rect);
+
+    foo_scroll_area_invalidate_region (scroll_area, region);
+
+    gdk_region_destroy (region);
+}
+
+void
+foo_scroll_area_begin_grab (FooScrollArea *scroll_area,
+			    FooScrollAreaEventFunc func,
+			    gpointer       input_data)
+{
+    g_return_if_fail (FOO_IS_SCROLL_AREA (scroll_area));
+    g_return_if_fail (!scroll_area->priv->grabbed);
+    
+    scroll_area->priv->grabbed = TRUE;
+    scroll_area->priv->grab_func = func;
+    scroll_area->priv->grab_data = input_data;
+    
+    /* FIXME: we should probably take a server grab */
+    /* Also, maybe there should be support for setting the grab cursor */
+}
+
+void
+foo_scroll_area_end_grab (FooScrollArea *scroll_area)
+{
+    g_return_if_fail (FOO_IS_SCROLL_AREA (scroll_area));
+    
+    scroll_area->priv->grabbed = FALSE;
+    scroll_area->priv->grab_func = NULL;
+    scroll_area->priv->grab_data = NULL;
+}
+
+gboolean
+foo_scroll_area_is_grabbed (FooScrollArea *scroll_area)
+{
+    return scroll_area->priv->grabbed;
+}
+
+void
+foo_scroll_area_set_viewport_pos (FooScrollArea  *scroll_area,
+				  int		  x,
+				  int		  y)
+{
+    g_object_freeze_notify (G_OBJECT (scroll_area->priv->hadj));
+    g_object_freeze_notify (G_OBJECT (scroll_area->priv->vadj));
+    gtk_adjustment_set_value (scroll_area->priv->hadj, x);
+    gtk_adjustment_set_value (scroll_area->priv->vadj, y);
+
+    set_adjustment_values (scroll_area);
+    g_object_thaw_notify (G_OBJECT (scroll_area->priv->hadj));
+    g_object_thaw_notify (G_OBJECT (scroll_area->priv->vadj));
+}
+
+static gboolean
+rect_contains (const GdkRectangle *rect, int x, int y)
+{
+    return (x >= rect->x		&&
+	    y >= rect->y		&&
+	    x  < rect->x + rect->width	&&
+	    y  < rect->y + rect->height);
+}
+
+static void
+stop_scrolling (FooScrollArea *area)
+{
+#if 0
+    g_print ("stop scrolling\n");
+#endif
+    if (area->priv->auto_scroll_info)
+    {
+	g_source_remove (area->priv->auto_scroll_info->timeout_id);
+	g_timer_destroy (area->priv->auto_scroll_info->timer);
+	g_free (area->priv->auto_scroll_info);
+	
+	area->priv->auto_scroll_info = NULL;
+    }
+}
+
+static gboolean
+scroll_idle (gpointer data)
+{
+    GdkRectangle viewport, new_viewport;
+    FooScrollArea *area = data;
+    AutoScrollInfo *info = area->priv->auto_scroll_info;
+#if 0
+    int dx, dy;
+#endif
+    int new_x, new_y;
+    double elapsed;
+    
+    get_viewport (area, &viewport);
+    
+#if 0
+    g_print ("old info: %d %d\n", info->dx, info->dy);
+    
+    g_print ("timeout (%d %d)\n", dx, dy);
+#endif
+    
+#if 0
+    viewport.x += info->dx;
+    viewport.y += info->dy;
+#endif
+    
+#if 0
+    g_print ("new info %d %d\n", info->dx, info->dy);
+#endif
+
+    elapsed = g_timer_elapsed (info->timer, NULL);
+
+    info->res_x = elapsed * info->dx / 0.2;
+    info->res_y = elapsed * info->dy / 0.2;
+
+#if 0
+    g_print ("%f %f\n", info->res_x, info->res_y);
+#endif
+    
+    new_x = viewport.x + info->res_x;
+    new_y = viewport.y + info->res_y;
+
+#if 0
+    g_print ("%f\n", elapsed * (info->dx / 0.2));
+#endif
+    
+#if 0
+    g_print ("new_x, new_y\n: %d %d\n", new_x, new_y);
+#endif
+    
+    foo_scroll_area_set_viewport_pos (area, new_x, new_y);
+#if 0
+				      viewport.x + info->dx,
+				      viewport.y + info->dy);
+#endif
+
+    get_viewport (area, &new_viewport);
+
+    if (viewport.x == new_viewport.x		&&
+	viewport.y == new_viewport.y		&&
+	(info->res_x > 1.0			||
+	 info->res_y > 1.0			||
+	 info->res_x < -1.0			||
+	 info->res_y < -1.0))
+    {
+	stop_scrolling (area);
+	
+	/* stop scrolling if it didn't have an effect */
+	return FALSE;
+    }
+    
+    return TRUE;
+}
+
+static void
+ensure_scrolling (FooScrollArea *area,
+		  int		 dx,
+		  int		 dy)
+{
+    if (!area->priv->auto_scroll_info)
+    {
+#if 0
+	g_print ("start scrolling\n");
+#endif
+	area->priv->auto_scroll_info = g_new0 (AutoScrollInfo, 1);
+	area->priv->auto_scroll_info->timeout_id =
+	    g_idle_add (scroll_idle, area);
+	area->priv->auto_scroll_info->timer = g_timer_new ();
+    }
+    
+#if 0
+    g_print ("setting scrolling to %d %d\n", dx, dy);
+#endif
+
+#if 0
+    g_print ("dx, dy: %d %d\n", dx, dy);
+#endif
+    
+    area->priv->auto_scroll_info->dx = dx;
+    area->priv->auto_scroll_info->dy = dy;
+}
+
+void
+foo_scroll_area_auto_scroll (FooScrollArea *scroll_area,
+			     FooScrollAreaEvent *event)
+{
+    GdkRectangle viewport;
+    
+    get_viewport (scroll_area, &viewport);
+    
+    if (rect_contains (&viewport, event->x, event->y))
+    {
+	stop_scrolling (scroll_area);
+    }
+    else
+    {
+	int dx, dy;
+	
+	dx = dy = 0;
+	
+	if (event->y < viewport.y)
+	{
+	    dy = event->y - viewport.y;
+	    dy = MIN (dy + 2, 0);
+	}
+	else if (event->y >= viewport.y + viewport.height)
+	{
+	    dy = event->y - (viewport.y + viewport.height - 1);
+	    dy = MAX (dy - 2, 0);
+	}
+	
+	if (event->x < viewport.x)
+	{
+	    dx = event->x - viewport.x;
+	    dx = MIN (dx + 2, 0);
+	}
+	else if (event->x >= viewport.x + viewport.width)
+	{
+	    dx = event->x - (viewport.x + viewport.width - 1);
+	    dx = MAX (dx - 2, 0);
+	}
+
+#if 0
+	g_print ("dx, dy: %d %d\n", dx, dy);
+#endif
+	
+	ensure_scrolling (scroll_area, dx, dy);
+    }
+}
+
+void
+foo_scroll_area_begin_auto_scroll (FooScrollArea *scroll_area)
+{
+    /* noop  for now */
+}
+
+void
+foo_scroll_area_end_auto_scroll (FooScrollArea *scroll_area)
+{
+    stop_scrolling (scroll_area);
+}
+
+
+
+#if 0
+/*
+ * Backing Store
+ */
+struct BackingStore
+{
+    GdkPixmap *pixmap;
+    GdkRegion *update_region;
+    int width;
+    int height;
+};
+
+static BackingStore *
+backing_store_new (GdkWindow *window,
+		   int width, int height)
+{
+    BackingStore *store = g_new0 (BackingStore, 1);
+    GdkRectangle rect = { 0, 0, width, height };
+    
+    store->pixmap = gdk_pixmap_new (window, width, height, -1);
+    store->update_region = gdk_region_rectangle (&rect);
+    store->width = width;
+    store->height = height;
+
+    return store;
+}
+
+static void
+backing_store_free (BackingStore *store)
+{
+    g_object_unref (store->pixmap);
+    gdk_region_destroy (store->update_region);
+    g_free (store);
+}
+
+static void
+backing_store_draw (BackingStore  *store, 
+		    GdkDrawable   *dest,
+		    GdkRegion     *clip,
+		    int		   x,
+		    int            y)
+{
+    GdkGC *gc = gdk_gc_new (dest);
+    
+    gdk_gc_set_clip_region (gc, clip);
+
+    gdk_draw_drawable (dest, gc, store->pixmap,
+		       0, 0, x, y, store->width, store->height);
+
+    g_object_unref (gc);
+}
+
+static void
+backing_store_scroll (BackingStore *store,
+		      int           dx,
+		      int           dy)
+{
+    GdkGC *gc = gdk_gc_new (store->pixmap);
+    GdkRectangle rect;
+    
+    gdk_draw_drawable (store->pixmap, gc, store->pixmap,
+		       0, 0, dx, dy,
+		       store->width, store->height);
+
+    /* Invalidate vertically */
+    rect.x = 0;
+    rect.width = store->width;
+
+    if (dy > 0)
+    {
+	rect.y = 0;
+	rect.height = dy;
+    }
+    else
+    {
+	rect.y = store->height + dy;
+	rect.y = -dy;
+    }
+
+    gdk_region_union_with_rect (store->update_region, &rect);
+    
+    /* Invalidate horizontally */
+    rect.y = 0;
+    rect.height = store->height;
+
+    if (dx > 0)
+    {
+	rect.x = 0;
+	rect.width = dx;
+    }
+    else
+    {
+	rect.x = store->width + dx;
+	rect.width = -dx;
+    }
+
+    gdk_region_union_with_rect (store->update_region, &rect);
+}
+
+static void
+backing_store_invalidate_rect (BackingStore *store,
+			       GdkRectangle *rect)
+{
+    gdk_region_union_with_rect (store->update_region, rect);
+}
+
+static void
+backing_store_invalidate_region (BackingStore *store,
+				 GdkRegion *region)
+{
+    gdk_region_union (store->update_region, region);
+}
+
+static void
+backing_store_invalidate_all (BackingStore *store)
+{
+    GdkRectangle rect = { 0, 0, store->width, store->height };
+    gdk_region_destroy (store->update_region);
+    store->update_region = gdk_region_rectangle (&rect);
+}
+
+static void
+backing_store_resize (BackingStore *store,
+		      int           width,
+		      int           height)
+{
+    GdkPixmap *pixmap = gdk_pixmap_new (store->pixmap, width, height, -1);
+
+    /* Unfortunately we don't know in which direction we were resized,
+     * so we just assume we were dragged from the south-east corner.
+     *
+     * Although, maybe we could get the root coordinates of the input-window?
+     * That might just work, actually. We need to make sure marco uses
+     * static gravity for the window before this will be useful.
+     */
+    simple_draw_drawable (pixmap, store->pixmap, 0, 0, 0, 0, -1, -1);
+
+    g_object_unref (store->pixmap);
+
+    store->pixmap = pixmap;
+
+    /* FIXME: invalidate uncovered strip only */
+
+    backing_store_invalidate_all (store);
+}
+
+static void
+cclip_to_region (cairo_t *cr, GdkRegion *region)
+{
+    int n_rects;
+    GdkRectangle *rects;
+
+    gdk_region_get_rectangles (region, &rects, &n_rects);
+
+    cairo_new_path (cr);
+    while (n_rects--)
+    {
+	GdkRectangle *rect = &(rects[n_rects]);
+
+	cairo_rectangle (cr, rect->x, rect->y, rect->width, rect->height);
+    }
+    cairo_clip (cr);
+
+    g_free (rects);
+}
+
+static void
+backing_store_process_updates (BackingStore *store,
+			       ExposeFunc    func,
+			       gpointer      data)
+{
+    cairo_t *cr = gdk_cairo_create (store->pixmap);
+    GdkRegion *region = store->update_region;
+    store->update_region = gdk_region_new ();
+
+    cclip_to_region (cr, store->update_region);
+
+    func (cr, store->update_region, data);
+
+    gdk_region_destroy (region);
+    cairo_destroy (cr);
+}
+
+#endif
diff --git a/dialogs/display-settings/scrollarea.h b/dialogs/display-settings/scrollarea.h
new file mode 100644
index 0000000..93ff00d
--- /dev/null
+++ b/dialogs/display-settings/scrollarea.h
@@ -0,0 +1,124 @@
+/* Copyright 2006, 2007, 2008, Soren Sandmann <sandmann at daimi.au.dk>
+ *
+ * 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., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+#include <cairo.h>
+#include <gtk/gtk.h>
+
+#define FOO_TYPE_SCROLL_AREA            (foo_scroll_area_get_type ())
+#define FOO_SCROLL_AREA(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), FOO_TYPE_SCROLL_AREA, FooScrollArea))
+#define FOO_SCROLL_AREA_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass),  FOO_TYPE_SCROLL_AREA, FooScrollAreaClass))
+#define FOO_IS_SCROLL_AREA(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), FOO_TYPE_SCROLL_AREA))
+#define FOO_IS_SCROLL_AREA_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass),  FOO_TYPE_SCROLL_AREA))
+#define FOO_SCROLL_AREA_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj),  FOO_TYPE_SCROLL_AREA, FooScrollAreaClass))
+
+typedef struct FooScrollArea FooScrollArea;
+typedef struct FooScrollAreaClass FooScrollAreaClass;
+typedef struct FooScrollAreaPrivate FooScrollAreaPrivate;
+typedef struct FooScrollAreaEvent FooScrollAreaEvent;
+
+typedef enum
+{
+    FOO_BUTTON_PRESS,
+    FOO_BUTTON_RELEASE,
+    FOO_MOTION
+} FooScrollAreaEventType;
+
+struct FooScrollAreaEvent
+{
+    FooScrollAreaEventType	type;
+    int				x;
+    int				y;
+};
+
+typedef void (* FooScrollAreaEventFunc) (FooScrollArea      *area,
+					 FooScrollAreaEvent *event,
+					 gpointer            data);
+
+struct FooScrollArea
+{
+    GtkContainer parent_instance;
+
+    FooScrollAreaPrivate *priv;
+};
+
+struct FooScrollAreaClass
+{
+    GtkContainerClass parent_class;
+
+    void (*set_scroll_adjustments) (FooScrollArea *scroll_area,
+				    GtkAdjustment *hadjustment,
+				    GtkAdjustment *vadjustment);
+
+    void (*viewport_changed) (FooScrollArea *scroll_area,
+			      GdkRectangle  *old_viewport,
+			      GdkRectangle  *new_viewport);
+
+    void (*paint) (FooScrollArea *scroll_area,
+		   cairo_t       *cr,
+		   GdkRectangle  *extents,
+		   GdkRegion     *region);
+};
+
+GType foo_scroll_area_get_type (void);
+
+FooScrollArea *foo_scroll_area_new (void);
+
+/* Set the requisition for the widget. */
+void	      foo_scroll_area_set_min_size (FooScrollArea *scroll_area,
+					    int		   min_width,
+					    int            min_height);
+
+/* Set how much of the canvas can be scrolled into view */
+void	      foo_scroll_area_set_size (FooScrollArea	       *scroll_area,
+					int			width,
+					int			height);
+void	      foo_scroll_area_set_size_fixed_y (FooScrollArea  *scroll_area,
+						int		width,
+						int		height,
+						int		old_y,
+						int		new_y);
+void	      foo_scroll_area_set_viewport_pos (FooScrollArea  *scroll_area,
+						int		x,
+						int		y);
+void	      foo_scroll_area_get_viewport (FooScrollArea *scroll_area,
+					    GdkRectangle  *viewport);
+void          foo_scroll_area_add_input_from_stroke (FooScrollArea           *scroll_area,
+						     cairo_t	                *cr,
+						     FooScrollAreaEventFunc   func,
+						     gpointer                 data);
+void          foo_scroll_area_add_input_from_fill (FooScrollArea *scroll_area,
+						      cairo_t	      *cr,
+						      FooScrollAreaEventFunc func,
+						      gpointer       data);
+void          foo_scroll_area_invalidate_region (FooScrollArea *area,
+						 GdkRegion     *region);
+void	      foo_scroll_area_invalidate (FooScrollArea *scroll_area);
+void	      foo_scroll_area_invalidate_rect (FooScrollArea *scroll_area,
+					       int	      x,
+					       int	      y,
+					       int	      width,
+					       int	      height);
+void foo_scroll_area_begin_grab (FooScrollArea *scroll_area,
+				 FooScrollAreaEventFunc func,
+				 gpointer       input_data);
+void foo_scroll_area_end_grab (FooScrollArea *scroll_area);
+gboolean foo_scroll_area_is_grabbed (FooScrollArea *scroll_area);
+
+void foo_scroll_area_begin_auto_scroll (FooScrollArea *scroll_area);
+void foo_scroll_area_auto_scroll (FooScrollArea *scroll_area,
+				  FooScrollAreaEvent *event);
+void foo_scroll_area_end_auto_scroll (FooScrollArea *scroll_area);


More information about the Xfce4-commits mailing list