Code

Split SPCanvasItem and SPCanvasGroup to individual .h files. Removed forward header.
[inkscape.git] / src / display / sp-canvas.cpp
index 6bd88613240b22fc6ab08be4ba64e8e08cbda1b1..fc68bcfc00a3c6ac304033832b183ab925738eab 100644 (file)
@@ -1,5 +1,3 @@
-#define __SP_CANVAS_C__
-
 /** \file
  * Port of GnomeCanvas for Inkscape needs
  *
 
 #include <gtk/gtkmain.h>
 #include <gtk/gtksignal.h>
+#include <gtk/gtkversion.h>
 
 #include <gtkmm.h>
 
-#include <helper/sp-marshal.h>
-#include <display/sp-canvas.h>
-#include "display-forward.h"
-#include <libnr/nr-matrix-fns.h>
-#include <libnr/nr-matrix-ops.h>
-#include <libnr/nr-convex-hull.h>
-
-enum {
-       RENDERMODE_NORMAL,
-       RENDERMODE_NOAA,
-       RENDERMODE_OUTLINE
-};
-
-const gint sp_canvas_update_priority = G_PRIORITY_HIGH_IDLE;
+#include "helper/sp-marshal.h"
+#include <helper/recthull.h>
+#include "display/sp-canvas.h"
+#include "display/sp-canvas-group.h"
+#include <2geom/matrix.h>
+#include "libnr/nr-convex-hull.h"
+#include "preferences.h"
+#include "inkscape.h"
+#include "sodipodi-ctrlrect.h"
+#if ENABLE_LCMS
+#include "color-profile-fns.h"
+#endif // ENABLE_LCMS
+#include "display/rendermode.h"
+#include "libnr/nr-blit.h"
+#include "display/inkscape-cairo.h"
+#include "debug/gdk-event-latency-tracker.h"
+#include "desktop.h"
+#include "sp-namedview.h"
+
+using Inkscape::Debug::GdkEventLatencyTracker;
+
+// GTK_CHECK_VERSION returns false on failure
+#define HAS_GDK_EVENT_REQUEST_MOTIONS GTK_CHECK_VERSION(2, 12, 0)
+
+// gtk_check_version returns non-NULL on failure
+static bool const HAS_BROKEN_MOTION_HINTS =
+  true || gtk_check_version(2, 12, 0) != NULL || !HAS_GDK_EVENT_REQUEST_MOTIONS;
+
+// Define this to visualize the regions to be redrawn
+//#define DEBUG_REDRAW 1;
+
+// Tiles are a way to minimize the number of redraws, eliminating too small redraws.
+// The canvas stores a 2D array of ints, each representing a TILE_SIZExTILE_SIZE pixels tile.
+// If any part of it is dirtied, the entire tile is dirtied (its int is nonzero) and repainted.
+#define TILE_SIZE 16
+
+static gint const sp_canvas_update_priority = G_PRIORITY_HIGH_IDLE;
 
 #define SP_CANVAS_WINDOW(c) (((GtkWidget *) (c))->window)
 
@@ -79,21 +101,22 @@ static void group_remove (SPCanvasGroup *group, SPCanvasItem *item);
 /* SPCanvasItem */
 
 enum {ITEM_EVENT, ITEM_LAST_SIGNAL};
+enum {PROP_0, PROP_VISIBLE};
 
 
 static void sp_canvas_request_update (SPCanvas *canvas);
 
+static void track_latency(GdkEvent const *event);
 static void sp_canvas_item_class_init (SPCanvasItemClass *klass);
 static void sp_canvas_item_init (SPCanvasItem *item);
 static void sp_canvas_item_dispose (GObject *object);
-static void sp_canvas_item_construct (SPCanvasItem *item, SPCanvasGroup *parent, const gchar *first_arg_name, va_list args);
+static void sp_canvas_item_construct (SPCanvasItem *item, SPCanvasGroup *parent, gchar const *first_arg_name, va_list args);
 
 static int emit_event (SPCanvas *canvas, GdkEvent *event);
+static int pick_current_item (SPCanvas *canvas, GdkEvent *event);
 
 static guint item_signals[ITEM_LAST_SIGNAL] = { 0 };
 
-static GtkObjectClass *item_parent_class;
-
 /**
  * Registers the SPCanvasItem class with Glib and returns its type number.
  */
@@ -102,7 +125,7 @@ sp_canvas_item_get_type (void)
 {
     static GType type = 0;
     if (!type) {
-        static const GTypeInfo info = {
+        static GTypeInfo const info = {
             sizeof (SPCanvasItemClass),
             NULL, NULL,
             (GClassInitFunc) sp_canvas_item_class_init,
@@ -126,13 +149,10 @@ sp_canvas_item_class_init (SPCanvasItemClass *klass)
 {
     GObjectClass *object_class = (GObjectClass *) klass;
 
-    /* fixme: Derive from GObject */
-    item_parent_class = (GtkObjectClass*)gtk_type_class (GTK_TYPE_OBJECT);
-
     item_signals[ITEM_EVENT] = g_signal_new ("event",
                                              G_TYPE_FROM_CLASS (klass),
                                              G_SIGNAL_RUN_LAST,
-                                             G_STRUCT_OFFSET (SPCanvasItemClass, event),
+                                             ((glong)((guint8*)&(klass->event) - (guint8*)klass)),
                                              NULL, NULL,
                                              sp_marshal_BOOLEAN__POINTER,
                                              G_TYPE_BOOLEAN, 1,
@@ -147,15 +167,18 @@ sp_canvas_item_class_init (SPCanvasItemClass *klass)
 static void
 sp_canvas_item_init (SPCanvasItem *item)
 {
+    // TODO items should not be visible on creation - this causes kludges with items
+    // that should be initially invisible; examples of such items: node handles, the CtrlRect
+    // used for rubberbanding, path outline, etc.
     item->flags |= SP_CANVAS_ITEM_VISIBLE;
-    item->xform = NR::Matrix(NR::identity());
+    item->xform = Geom::Matrix(Geom::identity());
 }
 
 /**
  * Constructs new SPCanvasItem on SPCanvasGroup.
  */
 SPCanvasItem *
-sp_canvas_item_new (SPCanvasGroup *parent, GtkType type, const gchar *first_arg_name, ...)
+sp_canvas_item_new (SPCanvasGroup *parent, GtkType type, gchar const *first_arg_name, ...)
 {
     va_list args;
 
@@ -178,7 +201,7 @@ sp_canvas_item_new (SPCanvasGroup *parent, GtkType type, const gchar *first_arg_
  * We make it static for encapsulation reasons since it was nowhere used.
  */
 static void
-sp_canvas_item_construct (SPCanvasItem *item, SPCanvasGroup *parent, const gchar *first_arg_name, va_list args)
+sp_canvas_item_construct (SPCanvasItem *item, SPCanvasGroup *parent, gchar const *first_arg_name, va_list args)
 {
     g_return_if_fail (SP_IS_CANVAS_GROUP (parent));
     g_return_if_fail (SP_IS_CANVAS_ITEM (item));
@@ -191,8 +214,6 @@ sp_canvas_item_construct (SPCanvasItem *item, SPCanvasGroup *parent, const gchar
     group_add (SP_CANVAS_GROUP (item->parent), item);
 
     sp_canvas_item_request_update (item);
-    sp_canvas_request_redraw (item->canvas, (int)(item->x1), (int)(item->y1), (int)(item->x2 + 1), (int)(item->y2 + 1));
-    item->canvas->need_repick = TRUE;
 }
 
 /**
@@ -202,7 +223,14 @@ static void
 redraw_if_visible (SPCanvasItem *item)
 {
     if (item->flags & SP_CANVAS_ITEM_VISIBLE) {
-        sp_canvas_request_redraw (item->canvas, (int)(item->x1), (int)(item->y1), (int)(item->x2 + 1), (int)(item->y2 + 1));
+        int x0 = (int)(item->x1);
+        int x1 = (int)(item->x2);
+        int y0 = (int)(item->y1);
+        int y1 = (int)(item->y2);
+
+        if (x0 !=0 || x1 !=0 || y0 !=0 || y1 !=0) {
+            sp_canvas_request_redraw (item->canvas, (int)(item->x1), (int)(item->y1), (int)(item->x2 + 1), (int)(item->y2 + 1));
+        }
     }
 }
 
@@ -214,7 +242,15 @@ sp_canvas_item_dispose (GObject *object)
 {
     SPCanvasItem *item = SP_CANVAS_ITEM (object);
 
-    redraw_if_visible (item);
+    // Hack: if this is a ctrlrect, move it to 0,0;
+    // this redraws only the stroke of the rect to be deleted,
+    // avoiding redraw of the entire area
+    if (SP_IS_CTRLRECT(item)) {
+        SP_CTRLRECT(object)->setRectangle(Geom::Rect(Geom::Point(0,0),Geom::Point(0,0)));
+        SP_CTRLRECT(object)->update(item->xform, 0);
+    } else {
+        redraw_if_visible (item);
+    }
     item->flags &= ~SP_CANVAS_ITEM_VISIBLE;
 
     if (item == item->canvas->current_item) {
@@ -239,7 +275,7 @@ sp_canvas_item_dispose (GObject *object)
         group_remove (SP_CANVAS_GROUP (item->parent), item);
     }
 
-    G_OBJECT_CLASS (item_parent_class)->dispose (object);
+    G_OBJECT_CLASS (g_type_class_peek(g_type_parent(sp_canvas_item_get_type())))->dispose (object);
 }
 
 /**
@@ -248,10 +284,10 @@ sp_canvas_item_dispose (GObject *object)
  * NB! affine is parent2canvas.
  */
 static void
-sp_canvas_item_invoke_update (SPCanvasItem *item, NR::Matrix const &affine, unsigned int flags)
+sp_canvas_item_invoke_update (SPCanvasItem *item, Geom::Matrix const &affine, unsigned int flags)
 {
     /* Apply the child item's transform */
-    NR::Matrix child_affine = item->xform * affine;
+    Geom::Matrix child_affine = item->xform * affine;
 
     /* apply object flags to child flags */
     int child_flags = flags & ~SP_CANVAS_UPDATE_REQUESTED;
@@ -271,15 +307,15 @@ sp_canvas_item_invoke_update (SPCanvasItem *item, NR::Matrix const &affine, unsi
     GTK_OBJECT_UNSET_FLAGS (item, SP_CANVAS_ITEM_NEED_AFFINE);
 }
 
-/** 
- * Helper function to invoke the point method of the item.  
+/**
+ * Helper function to invoke the point method of the item.
  *
- * The argument x, y should be in the parent's item-relative coordinate 
- * system.  This routine applies the inverse of the item's transform, 
+ * The argument x, y should be in the parent's item-relative coordinate
+ * system.  This routine applies the inverse of the item's transform,
  * maintaining the affine invariant.
  */
 static double
-sp_canvas_item_invoke_point (SPCanvasItem *item, NR::Point p, SPCanvasItem **actual_item)
+sp_canvas_item_invoke_point (SPCanvasItem *item, Geom::Point p, SPCanvasItem **actual_item)
 {
     if (SP_CANVAS_ITEM_GET_CLASS (item)->point)
         return SP_CANVAS_ITEM_GET_CLASS (item)->point (item, p, actual_item);
@@ -290,12 +326,12 @@ sp_canvas_item_invoke_point (SPCanvasItem *item, NR::Point p, SPCanvasItem **act
 /**
  * Makes the item's affine transformation matrix be equal to the specified
  * matrix.
- * 
+ *
  * @item: A canvas item.
  * @affine: An affine transformation matrix.
  */
 void
-sp_canvas_item_affine_absolute (SPCanvasItem *item, NR::Matrix const& affine)
+sp_canvas_item_affine_absolute (SPCanvasItem *item, Geom::Matrix const &affine)
 {
     item->xform = affine;
 
@@ -312,7 +348,7 @@ sp_canvas_item_affine_absolute (SPCanvasItem *item, NR::Matrix const& affine)
 }
 
 /**
- * Convenience function to reorder items in a group's child list.  
+ * Convenience function to reorder items in a group's child list.
  *
  * This puts the specified link after the "before" link.
  */
@@ -368,7 +404,7 @@ put_item_after (GList *link, GList *before)
 
 /**
  * Raises the item in its parent's stack by the specified number of positions.
- * 
+ *
  * \param item A canvas item.
  * \param positions Number of steps to raise the item.
  *
@@ -439,6 +475,13 @@ sp_canvas_item_lower (SPCanvasItem *item, int positions)
     item->canvas->need_repick = TRUE;
 }
 
+bool
+sp_canvas_item_is_visible (SPCanvasItem *item)
+{
+    return item->flags & SP_CANVAS_ITEM_VISIBLE;
+}
+
+
 /**
  * Sets visible flag on item and requests a redraw.
  */
@@ -453,8 +496,15 @@ sp_canvas_item_show (SPCanvasItem *item)
 
     item->flags |= SP_CANVAS_ITEM_VISIBLE;
 
-    sp_canvas_request_redraw (item->canvas, (int)(item->x1), (int)(item->y1), (int)(item->x2 + 1), (int)(item->y2 + 1));
-    item->canvas->need_repick = TRUE;
+    int x0 = (int)(item->x1);
+    int x1 = (int)(item->x2);
+    int y0 = (int)(item->y1);
+    int y1 = (int)(item->y2);
+
+    if (x0 !=0 || x1 !=0 || y0 !=0 || y1 !=0) {
+        sp_canvas_request_redraw (item->canvas, (int)(item->x1), (int)(item->y1), (int)(item->x2 + 1), (int)(item->y2 + 1));
+        item->canvas->need_repick = TRUE;
+    }
 }
 
 /**
@@ -471,13 +521,20 @@ sp_canvas_item_hide (SPCanvasItem *item)
 
     item->flags &= ~SP_CANVAS_ITEM_VISIBLE;
 
-    sp_canvas_request_redraw (item->canvas, (int)item->x1, (int)item->y1, (int)(item->x2 + 1), (int)(item->y2 + 1));
-    item->canvas->need_repick = TRUE;
+    int x0 = (int)(item->x1);
+    int x1 = (int)(item->x2);
+    int y0 = (int)(item->y1);
+    int y1 = (int)(item->y2);
+
+    if (x0 !=0 || x1 !=0 || y0 !=0 || y1 !=0) {
+        sp_canvas_request_redraw (item->canvas, (int)item->x1, (int)item->y1, (int)(item->x2 + 1), (int)(item->y2 + 1));
+        item->canvas->need_repick = TRUE;
+    }
 }
 
 /**
  * Grab item under cursor.
- * 
+ *
  * \pre !canvas->grabbed_item && item->flags & SP_CANVAS_ITEM_VISIBLE
  */
 int
@@ -490,8 +547,17 @@ sp_canvas_item_grab (SPCanvasItem *item, guint event_mask, GdkCursor *cursor, gu
     if (item->canvas->grabbed_item)
         return -1;
 
-    if (!(item->flags & SP_CANVAS_ITEM_VISIBLE))
-        return -1;
+    // This test disallows grabbing events by an invisible item, which may be useful
+    // sometimes. An example is the hidden control point used for the selector component,
+    // where it is used for object selection and rubberbanding. There seems to be nothing
+    // preventing this except this test, so I removed it.
+    // -- Krzysztof KosiƄski, 2009.08.12
+    //if (!(item->flags & SP_CANVAS_ITEM_VISIBLE))
+    //    return -1;
+
+    if (HAS_BROKEN_MOTION_HINTS) {
+        event_mask &= ~GDK_POINTER_MOTION_HINT_MASK;
+    }
 
     /* fixme: Top hack (Lauris) */
     /* fixme: If we add key masks to event mask, Gdk will abort (Lauris) */
@@ -510,7 +576,7 @@ sp_canvas_item_grab (SPCanvasItem *item, guint event_mask, GdkCursor *cursor, gu
 /**
  * Ungrabs the item, which must have been grabbed in the canvas, and ungrabs the
  * mouse.
- * 
+ *
  * \param item A canvas item that holds a grab.
  * \param etime The timestamp for ungrabbing the mouse.
  */
@@ -532,11 +598,11 @@ sp_canvas_item_ungrab (SPCanvasItem *item, guint32 etime)
  * Returns the product of all transformation matrices from the root item down
  * to the item.
  */
-NR::Matrix sp_canvas_item_i2w_affine(SPCanvasItem const *item)
+Geom::Matrix sp_canvas_item_i2w_affine(SPCanvasItem const *item)
 {
     g_assert (SP_IS_CANVAS_ITEM (item)); // should we get this?
 
-    NR::Matrix affine = NR::identity();
+    Geom::Matrix affine = Geom::identity();
 
     while (item) {
         affine *= item->xform;
@@ -597,7 +663,7 @@ sp_canvas_item_grab_focus (SPCanvasItem *item)
 
 /**
  * Requests that the canvas queue an update for the specified item.
- * 
+ *
  * To be used only by item implementations.
  */
 void
@@ -631,8 +697,8 @@ static void sp_canvas_group_class_init (SPCanvasGroupClass *klass);
 static void sp_canvas_group_init (SPCanvasGroup *group);
 static void sp_canvas_group_destroy (GtkObject *object);
 
-static void sp_canvas_group_update (SPCanvasItem *item, NR::Matrix const &affine, unsigned int flags);
-static double sp_canvas_group_point (SPCanvasItem *item, NR::Point p, SPCanvasItem **actual_item);
+static void sp_canvas_group_update (SPCanvasItem *item, Geom::Matrix const &affine, unsigned int flags);
+static double sp_canvas_group_point (SPCanvasItem *item, Geom::Point p, SPCanvasItem **actual_item);
 static void sp_canvas_group_render (SPCanvasItem *item, SPCanvasBuf *buf);
 
 static SPCanvasItemClass *group_parent_class;
@@ -640,25 +706,25 @@ static SPCanvasItemClass *group_parent_class;
 /**
  * Registers SPCanvasGroup class with Gtk and returns its type number.
  */
-GtkType
-sp_canvas_group_get_type (void)
+GType sp_canvas_group_get_type(void)
 {
-    static GtkType group_type = 0;
-
-    if (!group_type) {
-        static const GtkTypeInfo group_info = {
-            "SPCanvasGroup",
-            sizeof (SPCanvasGroup),
-            sizeof (SPCanvasGroupClass),
-            (GtkClassInitFunc) sp_canvas_group_class_init,
-            (GtkObjectInitFunc) sp_canvas_group_init,
-            NULL, NULL, NULL
+    static GType type = 0;
+    if (!type) {
+        GTypeInfo info = {
+            sizeof(SPCanvasGroupClass),
+            0, // base_init
+            0, // base_finalize
+            (GClassInitFunc)sp_canvas_group_class_init,
+            0, // class_finalize
+            0, // class_data
+            sizeof(SPCanvasGroup),
+            0, // n_preallocs
+            (GInstanceInitFunc)sp_canvas_group_init,
+            0 // value_table
         };
-
-        group_type = gtk_type_unique (sp_canvas_item_get_type (), &group_info);
+        type = g_type_register_static(sp_canvas_item_get_type(), "SPCanvasGroup", &info, static_cast<GTypeFlags>(0));
     }
-
-    return group_type;
+    return type;
 }
 
 /**
@@ -689,7 +755,7 @@ sp_canvas_group_init (SPCanvasGroup */*group*/)
 }
 
 /**
- * Callback that destroys all items in group and calls group's virtual 
+ * Callback that destroys all items in group and calls group's virtual
  * destroy() function.
  */
 static void
@@ -698,7 +764,7 @@ sp_canvas_group_destroy (GtkObject *object)
     g_return_if_fail (object != NULL);
     g_return_if_fail (SP_IS_CANVAS_GROUP (object));
 
-    const SPCanvasGroup *group = SP_CANVAS_GROUP (object);
+    SPCanvasGroup const *group = SP_CANVAS_GROUP (object);
 
     GList *list = group->items;
     while (list) {
@@ -716,10 +782,10 @@ sp_canvas_group_destroy (GtkObject *object)
  * Update handler for canvas groups
  */
 static void
-sp_canvas_group_update (SPCanvasItem *item, NR::Matrix const &affine, unsigned int flags)
+sp_canvas_group_update (SPCanvasItem *item, Geom::Matrix const &affine, unsigned int flags)
 {
-    const SPCanvasGroup *group = SP_CANVAS_GROUP (item);
-    NR::ConvexHull corners(NR::Point(0, 0));
+    SPCanvasGroup const *group = SP_CANVAS_GROUP (item);
+    Geom::RectHull corners(Geom::Point(0, 0));
     bool empty=true;
 
     for (GList *list = group->items; list; list = list->next) {
@@ -729,31 +795,36 @@ sp_canvas_group_update (SPCanvasItem *item, NR::Matrix const &affine, unsigned i
 
         if ( i->x2 > i->x1 && i->y2 > i->y1 ) {
             if (empty) {
-                corners = NR::ConvexHull(NR::Point(i->x1, i->y1));
+                corners = Geom::RectHull(Geom::Point(i->x1, i->y1));
                 empty = false;
             } else {
-                corners.add(NR::Point(i->x1, i->y1));
+                corners.add(Geom::Point(i->x1, i->y1));
             }
-            corners.add(NR::Point(i->x2, i->y2));
+            corners.add(Geom::Point(i->x2, i->y2));
         }
     }
 
-    NR::Rect const &bounds = corners.bounds();
-    item->x1 = bounds.min()[NR::X];
-    item->y1 = bounds.min()[NR::Y];
-    item->x2 = bounds.max()[NR::X];
-    item->y2 = bounds.max()[NR::Y];
+    Geom::OptRect const bounds = corners.bounds();
+    if (bounds) {
+        item->x1 = bounds->min()[Geom::X];
+        item->y1 = bounds->min()[Geom::Y];
+        item->x2 = bounds->max()[Geom::X];
+        item->y2 = bounds->max()[Geom::Y];
+    } else {
+        // FIXME ?
+        item->x1 = item->x2 = item->y1 = item->y2 = 0;
+    }
 }
 
 /**
  * Point handler for canvas groups.
  */
 static double
-sp_canvas_group_point (SPCanvasItem *item, NR::Point p, SPCanvasItem **actual_item)
+sp_canvas_group_point (SPCanvasItem *item, Geom::Point p, SPCanvasItem **actual_item)
 {
-    const SPCanvasGroup *group = SP_CANVAS_GROUP (item);
-    const double x = p[NR::X];
-    const double y = p[NR::Y];
+    SPCanvasGroup const *group = SP_CANVAS_GROUP (item);
+    double const x = p[Geom::X];
+    double const y = p[Geom::Y];
     int x1 = (int)(x - item->canvas->close_enough);
     int y1 = (int)(y - item->canvas->close_enough);
     int x2 = (int)(x + item->canvas->close_enough);
@@ -793,7 +864,7 @@ sp_canvas_group_point (SPCanvasItem *item, NR::Point p, SPCanvasItem **actual_it
 static void
 sp_canvas_group_render (SPCanvasItem *item, SPCanvasBuf *buf)
 {
-    const SPCanvasGroup *group = SP_CANVAS_GROUP (item);
+    SPCanvasGroup const *group = SP_CANVAS_GROUP (item);
 
     for (GList *list = group->items; list; list = list->next) {
         SPCanvasItem *child = (SPCanvasItem *)list->data;
@@ -828,7 +899,7 @@ group_add (SPCanvasGroup *group, SPCanvasItem *item)
     sp_canvas_item_request_update (item);
 }
 
-/** 
+/**
  * Removes an item from a canvas group
  */
 static void
@@ -880,8 +951,9 @@ static gint sp_canvas_focus_out (GtkWidget *widget, GdkEventFocus *event);
 
 static GtkWidgetClass *canvas_parent_class;
 
-void sp_canvas_resize_tiles(SPCanvas* canvas,int nl,int nt,int nr,int nb);
-void sp_canvas_dirty_rect(SPCanvas* canvas,int nl,int nt,int nr,int nb);
+static void sp_canvas_resize_tiles(SPCanvas* canvas, int nl, int nt, int nr, int nb);
+static void sp_canvas_dirty_rect(SPCanvas* canvas, int nl, int nt, int nr, int nb);
+static void sp_canvas_mark_rect(SPCanvas* canvas, int nl, int nt, int nr, int nb, uint8_t val);
 static int do_update (SPCanvas *canvas);
 
 /**
@@ -890,25 +962,25 @@ static int do_update (SPCanvas *canvas);
  *
  * \return The type ID of the SPCanvas class.
  **/
-GtkType
-sp_canvas_get_type (void)
-{
-    static GtkType canvas_type = 0;
-
-    if (!canvas_type) {
-        static const GtkTypeInfo canvas_info = {
-            "SPCanvas",
-            sizeof (SPCanvas),
-            sizeof (SPCanvasClass),
-            (GtkClassInitFunc) sp_canvas_class_init,
-            (GtkObjectInitFunc) sp_canvas_init,
-            NULL, NULL, NULL
+GType sp_canvas_get_type(void)
+{
+    static GType type = 0;
+    if (!type) {
+        GTypeInfo info = {
+            sizeof(SPCanvasClass),
+            0, // base_init
+            0, // base_finalize
+            (GClassInitFunc)sp_canvas_class_init,
+            0, // class_finalize
+            0, // class_data
+            sizeof(SPCanvas),
+            0, // n_preallocs
+            (GInstanceInitFunc)sp_canvas_init,
+            0 // value_table
         };
-
-        canvas_type = gtk_type_unique (GTK_TYPE_WIDGET, &canvas_info);
+        type = g_type_register_static(GTK_TYPE_WIDGET, "SPCanvas", &info, static_cast<GTypeFlags>(0));
     }
-
-    return canvas_type;
+    return type;
 }
 
 /**
@@ -941,7 +1013,7 @@ sp_canvas_class_init (SPCanvasClass *klass)
     widget_class->focus_out_event = sp_canvas_focus_out;
 }
 
-/** 
+/**
  * Callback: object initialization for SPCanvas.
  */
 static void
@@ -966,22 +1038,22 @@ sp_canvas_init (SPCanvas *canvas)
 
     // See comment at in sp-canvas.h.
     canvas->gen_all_enter_events = false;
+    
+    canvas->drawing_disabled = false;
 
     canvas->tiles=NULL;
     canvas->tLeft=canvas->tTop=canvas->tRight=canvas->tBottom=0;
     canvas->tileH=canvas->tileV=0;
 
-    canvas->redraw_aborted.x0 = NR_HUGE_L;
-    canvas->redraw_aborted.x1 = -NR_HUGE_L;
-    canvas->redraw_aborted.y0 = NR_HUGE_L;
-    canvas->redraw_aborted.y1 = -NR_HUGE_L;
-    
-    canvas->redraw_count = 0;
-    
     canvas->forced_redraw_count = 0;
-    canvas->forced_redraw_limit = 0;
+    canvas->forced_redraw_limit = -1;
 
-    canvas->slowest_buffer = 0;
+#if ENABLE_LCMS
+    canvas->enable_cms_display_adj = false;
+    canvas->cms_key = new Glib::ustring("");
+#endif // ENABLE_LCMS
+
+    canvas->is_scrolling = false;
 }
 
 /**
@@ -1042,6 +1114,14 @@ sp_canvas_destroy (GtkObject *object)
         (* GTK_OBJECT_CLASS (canvas_parent_class)->destroy) (object);
 }
 
+static void track_latency(GdkEvent const *event) {
+    GdkEventLatencyTracker &tracker = GdkEventLatencyTracker::default_tracker();
+    boost::optional<double> latency = tracker.process(event);
+    if (latency && *latency > 2.0) {
+        //g_warning("Event latency reached %f sec (%1.4f)", *latency, tracker.getSkew());
+    }
+}
+
 /**
  * Returns new canvas as widget.
  */
@@ -1075,6 +1155,8 @@ sp_canvas_realize (GtkWidget *widget)
                              GDK_BUTTON_PRESS_MASK |
                              GDK_BUTTON_RELEASE_MASK |
                              GDK_POINTER_MOTION_MASK |
+                             ( HAS_BROKEN_MOTION_HINTS ?
+                               0 : GDK_POINTER_MOTION_HINT_MASK ) |
                              GDK_PROXIMITY_IN_MASK |
                              GDK_PROXIMITY_OUT_MASK |
                              GDK_KEY_PRESS_MASK |
@@ -1086,7 +1168,12 @@ sp_canvas_realize (GtkWidget *widget)
 
     widget->window = gdk_window_new (gtk_widget_get_parent_window (widget), &attributes, attributes_mask);
     gdk_window_set_user_data (widget->window, widget);
-    gtk_widget_set_events(widget, attributes.event_mask);
+
+    Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+    if ( prefs->getBool("/options/useextinput/value", true) )
+        gtk_widget_set_events(widget, attributes.event_mask);
+
+    widget->style = gtk_style_attach (widget->style, widget->window);
 
     GTK_WIDGET_SET_FLAGS (widget, GTK_REALIZED);
 
@@ -1101,6 +1188,10 @@ sp_canvas_unrealize (GtkWidget *widget)
 {
     SPCanvas *canvas = SP_CANVAS (widget);
 
+    canvas->current_item = NULL;
+    canvas->grabbed_item = NULL;
+    canvas->focused_item = NULL;
+
     shutdown_transients (canvas);
 
     gdk_gc_destroy (canvas->pixmap_gc);
@@ -1157,7 +1248,7 @@ sp_canvas_size_allocate (GtkWidget *widget, GtkAllocation *allocation)
 }
 
 /**
- * Helper that emits an event for an item in the canvas, be it the current 
+ * Helper that emits an event for an item in the canvas, be it the current
  * item, grabbed item, or focused item, as appropriate.
  */
 static int
@@ -1201,7 +1292,7 @@ emit_event (SPCanvas *canvas, GdkEvent *event)
         if (!(mask & canvas->grabbed_event_mask)) return FALSE;
     }
 
-    /* Convert to world coordinates -- we have two cases because of diferent
+    /* Convert to world coordinates -- we have two cases because of different
      * offsets of the fields in the event structures.
      */
 
@@ -1235,6 +1326,20 @@ emit_event (SPCanvas *canvas, GdkEvent *event)
     if (canvas->grabbed_item && !is_descendant (canvas->current_item, canvas->grabbed_item)) {
         item = canvas->grabbed_item;
     } else {
+        // Make sure that current_item is up-to-date. If a snap indicator was just deleted, then
+        // sp_canvas_item_dispose has been called and there is no current_item specified. We need
+        // that though because otherwise we don't know where to send this event to, leading to a
+        // lost event. We can't wait for idle events to have current_item updated, we need it now!
+        // Otherwise, scrolling when hovering above a pre-snap indicator won't work (for example)
+        // See this bug report: https://bugs.launchpad.net/inkscape/+bug/522335/comments/8
+        if (canvas->need_repick && !canvas->in_repick && event->type == GDK_SCROLL) {
+            // To avoid side effects, we'll only do this for scroll events, because this is the
+            // only thing we want to fix here. An example of a reported side effect is that
+            // otherwise selection of nodes in the node editor by dragging a rectangle using a
+            // tablet will break
+            canvas->need_repick = FALSE;
+            pick_current_item (canvas, (GdkEvent *) event);
+        }
         item = canvas->current_item;
     }
 
@@ -1264,7 +1369,7 @@ emit_event (SPCanvas *canvas, GdkEvent *event)
 }
 
 /**
- * Helper that re-picks the current item in the canvas, based on the event's 
+ * Helper that re-picks the current item in the canvas, based on the event's
  * coordinates and emits enter/leave events for items as appropriate.
  */
 static int
@@ -1273,6 +1378,9 @@ pick_current_item (SPCanvas *canvas, GdkEvent *event)
     int button_down = 0;
     double x, y;
 
+    if (!canvas->root) // canvas may have already be destroyed by closing desktop durring interrupted display!
+        return FALSE;
+
     int retval = FALSE;
 
     if (canvas->gen_all_enter_events == false) {
@@ -1341,7 +1449,7 @@ pick_current_item (SPCanvas *canvas, GdkEvent *event)
 
         /* find the closest item */
         if (canvas->root->flags & SP_CANVAS_ITEM_VISIBLE) {
-            sp_canvas_item_invoke_point (canvas->root, NR::Point(x, y), &canvas->new_current_item);
+            sp_canvas_item_invoke_point (canvas->root, Geom::Point(x, y), &canvas->new_current_item);
         } else {
             canvas->new_current_item = NULL;
         }
@@ -1397,6 +1505,8 @@ pick_current_item (SPCanvas *canvas, GdkEvent *event)
         retval = emit_event (canvas, &new_event);
     }
 
+
+
     return retval;
 }
 
@@ -1410,9 +1520,9 @@ sp_canvas_button (GtkWidget *widget, GdkEventButton *event)
 
     int retval = FALSE;
 
-    /* dispatch normally regardless of the event's window if an item has
+    /* dispatch normally regardless of the event's window if an item
        has a pointer grab in effect */
-    if (!canvas->grabbed_item && 
+    if (!canvas->grabbed_item &&
         event->window != SP_CANVAS_WINDOW (canvas))
         return retval;
 
@@ -1441,7 +1551,7 @@ sp_canvas_button (GtkWidget *widget, GdkEventButton *event)
     case GDK_BUTTON_PRESS:
     case GDK_2BUTTON_PRESS:
     case GDK_3BUTTON_PRESS:
-        /* Pick the current item as if the button were not pressed, and
+       /* Pick the current item as if the button were not pressed, and
          * then process the event.
          */
         canvas->state = event->state;
@@ -1451,7 +1561,7 @@ sp_canvas_button (GtkWidget *widget, GdkEventButton *event)
         break;
 
     case GDK_BUTTON_RELEASE:
-        /* Process the event as if the button were pressed, then repick
+               /* Process the event as if the button were pressed, then repick
          * after the button has been released
          */
         canvas->state = event->state;
@@ -1460,6 +1570,7 @@ sp_canvas_button (GtkWidget *widget, GdkEventButton *event)
         canvas->state = event->state;
         pick_current_item (canvas, (GdkEvent *) event);
         event->state ^= mask;
+
         break;
 
     default:
@@ -1480,28 +1591,38 @@ sp_canvas_scroll (GtkWidget *widget, GdkEventScroll *event)
     return emit_event (SP_CANVAS (widget), (GdkEvent *) event);
 }
 
+static inline void request_motions(GdkWindow *w, GdkEventMotion *event) {
+    gdk_window_get_pointer(w, NULL, NULL, NULL);
+#if HAS_GDK_EVENT_REQUEST_MOTIONS
+    gdk_event_request_motions(event);
+#endif
+}
+
 /**
  * Motion event handler for the canvas.
  */
 static int
 sp_canvas_motion (GtkWidget *widget, GdkEventMotion *event)
 {
+    int status;
     SPCanvas *canvas = SP_CANVAS (widget);
 
+    track_latency((GdkEvent *)event);
+
     if (event->window != SP_CANVAS_WINDOW (canvas))
         return FALSE;
 
-    if (canvas->grabbed_event_mask & GDK_POINTER_MOTION_HINT_MASK) {
-        gint x, y;
-        gdk_window_get_pointer (widget->window, &x, &y, NULL);
-        event->x = x;
-        event->y = y;
-    }
+    if (canvas->pixmap_gc == NULL) // canvas being deleted
+        return FALSE;
 
     canvas->state = event->state;
     pick_current_item (canvas, (GdkEvent *) event);
+    status = emit_event (canvas, (GdkEvent *) event);
+    if (event->is_hint) {
+        request_motions(widget->window, event);
+    }
 
-    return emit_event (canvas, (GdkEvent *) event);
+    return status;
 }
 
 static void
@@ -1510,13 +1631,16 @@ sp_canvas_paint_single_buffer (SPCanvas *canvas, int x0, int y0, int x1, int y1,
     GtkWidget *widget = GTK_WIDGET (canvas);
 
     SPCanvasBuf buf;
-    if (canvas->rendermode != RENDERMODE_OUTLINE) {
+    if (canvas->rendermode != Inkscape::RENDERMODE_OUTLINE) {
         buf.buf = nr_pixelstore_256K_new (FALSE, 0);
     } else {
         buf.buf = nr_pixelstore_1M_new (FALSE, 0);
     }
 
-    buf.buf_rowstride = sw * 3;
+    // Mark the region clean
+    sp_canvas_mark_rect(canvas, x0, y0, x1, y1, 0);
+
+    buf.buf_rowstride = sw * 4;
     buf.rect.x0 = x0;
     buf.rect.y0 = y0;
     buf.rect.x1 = x1;
@@ -1530,12 +1654,30 @@ sp_canvas_paint_single_buffer (SPCanvas *canvas, int x0, int y0, int x1, int y1,
                     | (color->green & 0xff00)
                     | (color->blue >> 8));
     buf.is_empty = true;
-      
+
+    buf.ct = nr_create_cairo_context_canvasbuf (&(buf.visible_rect), &buf);
+
     if (canvas->root->flags & SP_CANVAS_ITEM_VISIBLE) {
         SP_CANVAS_ITEM_GET_CLASS (canvas->root)->render (canvas->root, &buf);
     }
-      
+
+#if ENABLE_LCMS
+    cmsHTRANSFORM transf = 0;
+    Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+    bool fromDisplay = prefs->getBool( "/options/displayprofile/from_display");
+    if ( fromDisplay ) {
+        transf = Inkscape::colorprofile_get_display_per( canvas->cms_key ? *(canvas->cms_key) : "" );
+    } else {
+        transf = Inkscape::colorprofile_get_display_transform();
+    }
+#endif // ENABLE_LCMS
+
     if (buf.is_empty) {
+#if ENABLE_LCMS
+        if ( transf && canvas->enable_cms_display_adj ) {
+            cmsDoTransform( transf, &buf.bg_color, &buf.bg_color, 1 );
+        }
+#endif // ENABLE_LCMS
         gdk_rgb_gc_set_foreground (canvas->pixmap_gc, buf.bg_color);
         gdk_draw_rectangle (SP_CANVAS_WINDOW (canvas),
                             canvas->pixmap_gc,
@@ -1543,178 +1685,210 @@ sp_canvas_paint_single_buffer (SPCanvas *canvas, int x0, int y0, int x1, int y1,
                             x0 - canvas->x0, y0 - canvas->y0,
                             x1 - x0, y1 - y0);
     } else {
+
+#if ENABLE_LCMS
+        if ( transf && canvas->enable_cms_display_adj ) {
+            for ( gint yy = 0; yy < (y1 - y0); yy++ ) {
+                guchar* p = buf.buf + (buf.buf_rowstride * yy);
+                cmsDoTransform( transf, p, p, (x1 - x0) );
+            }
+        }
+#endif // ENABLE_LCMS
+
+// Now we only need to output the prepared pixmap to the actual screen, and this define chooses one
+// of the two ways to do it. The cairo way is direct and straightforward, but unfortunately
+// noticeably slower. I asked Carl Worth but he was unable so far to suggest any specific reason
+// for this slowness. So, for now we use the oldish method: squeeze out 32bpp buffer to 24bpp and
+// use gdk_draw_rgb_image_dithalign, for unfortunately gdk can only handle 24 bpp, which cairo
+// cannot handle at all. Still, this way is currently faster even despite the blit with squeeze.
+
+///#define CANVAS_OUTPUT_VIA_CAIRO
+
+#ifdef CANVAS_OUTPUT_VIA_CAIRO
+
+        buf.cst = cairo_image_surface_create_for_data (
+            buf.buf,
+            CAIRO_FORMAT_ARGB32,  // unpacked, i.e. 32 bits! one byte is unused
+            x1 - x0, y1 - y0,
+            buf.buf_rowstride
+            );
+        cairo_t *window_ct = gdk_cairo_create(SP_CANVAS_WINDOW (canvas));
+        cairo_set_source_surface (window_ct, buf.cst, x0 - canvas->x0, y0 - canvas->y0);
+        cairo_paint (window_ct);
+        cairo_destroy (window_ct);
+        cairo_surface_finish (buf.cst);
+        cairo_surface_destroy (buf.cst);
+
+#else
+
+        NRPixBlock b3;
+        nr_pixblock_setup_fast (&b3, NR_PIXBLOCK_MODE_R8G8B8, x0, y0, x1, y1, TRUE);
+
+        NRPixBlock b4;
+        nr_pixblock_setup_extern (&b4, NR_PIXBLOCK_MODE_R8G8B8A8P, x0, y0, x1, y1,
+                                  buf.buf,
+                                  buf.buf_rowstride,
+                                  FALSE, FALSE);
+
+        // this does the 32->24 squishing, using an assembler routine:
+        nr_blit_pixblock_pixblock (&b3, &b4);
+
         gdk_draw_rgb_image_dithalign (SP_CANVAS_WINDOW (canvas),
                                       canvas->pixmap_gc,
                                       x0 - canvas->x0, y0 - canvas->y0,
                                       x1 - x0, y1 - y0,
                                       GDK_RGB_DITHER_MAX,
-                                      buf.buf,
+                                      NR_PIXBLOCK_PX(&b3),
                                       sw * 3,
                                       x0 - canvas->x0, y0 - canvas->y0);
+
+        nr_pixblock_release (&b3);
+        nr_pixblock_release (&b4);
+#endif
     }
 
-    if (canvas->rendermode != RENDERMODE_OUTLINE) {
+    cairo_surface_t *cst = cairo_get_target(buf.ct);
+    cairo_destroy (buf.ct);
+    cairo_surface_finish (cst);
+    cairo_surface_destroy (cst);
+
+    if (canvas->rendermode != Inkscape::RENDERMODE_OUTLINE) {
         nr_pixelstore_256K_free (buf.buf);
     } else {
         nr_pixelstore_1M_free (buf.buf);
     }
 }
 
-/* Paint the given rect, while updating canvas->redraw_aborted and running iterations after each
- * buffer; make sure canvas->redraw_aborted never goes past aborted_limit (used for 2-rect
- * optimized repaint)
+struct PaintRectSetup {
+    SPCanvas* canvas;
+    NRRectL big_rect;
+    GTimeVal start_time;
+    int max_pixels;
+    Geom::Point mouse_loc;
+};
+
+/**
+ * Paint the given rect, recursively subdividing the region until it is the size of a single
+ * buffer.
+ *
+ * @return true if the drawing completes
  */
 static int
-sp_canvas_paint_rect_internal (SPCanvas *canvas, NRRectL *rect, NR::ICoord *x_aborted_limit, NR::ICoord *y_aborted_limit)
-{
-    int draw_x1 = rect->x0;
-    int draw_x2 = rect->x1;
-    int draw_y1 = rect->y0;
-    int draw_y2 = rect->y1;
+sp_canvas_paint_rect_internal (PaintRectSetup const *setup, NRRectL this_rect)
+{
+    GTimeVal now;
+    g_get_current_time (&now);
+
+    glong elapsed = (now.tv_sec - setup->start_time.tv_sec) * 1000000
+        + (now.tv_usec - setup->start_time.tv_usec);
+
+    // Allow only very fast buffers to be run together;
+    // as soon as the total redraw time exceeds 1ms, cancel;
+    // this returns control to the idle loop and allows Inkscape to process user input
+    // (potentially interrupting the redraw); as soon as Inkscape has some more idle time,
+    // it will get back and finish painting what remains to paint.
+    if (elapsed > 1000) {
+
+        // Interrupting redraw isn't always good.
+        // For example, when you drag one node of a big path, only the buffer containing
+        // the mouse cursor will be redrawn again and again, and the rest of the path
+        // will remain stale because Inkscape never has enough idle time to redraw all
+        // of the screen. To work around this, such operations set a forced_redraw_limit > 0.
+        // If this limit is set, and if we have aborted redraw more times than is allowed,
+        // interrupting is blocked and we're forced to redraw full screen once
+        // (after which we can again interrupt forced_redraw_limit times).
+        if (setup->canvas->forced_redraw_limit < 0 ||
+            setup->canvas->forced_redraw_count < setup->canvas->forced_redraw_limit) {
+
+            if (setup->canvas->forced_redraw_limit != -1) {
+                setup->canvas->forced_redraw_count++;
+            }
 
-    // Here we'll store the time it took to draw the slowest buffer of this paint. 
-    glong slowest_buffer = 0;
+            return false;
+        }
+    }
 
     // Find the optimal buffer dimensions
-    int bw = draw_x2 - draw_x1;
-    int bh = draw_y2 - draw_y1;
+    int bw = this_rect.x1 - this_rect.x0;
+    int bh = this_rect.y1 - this_rect.y0;
     if ((bw < 1) || (bh < 1))
         return 0;
-    int sw, sh;
-    if (canvas->rendermode != RENDERMODE_OUTLINE) { // use 256K as a compromise to not slow down gradients
-        /* 256K is the cached buffer and we need 3 channels */
-        if (bw * bh <  87381) { // 256K/3
-            // We can go with single buffer 
-            sw = bw;
-            sh = bh;
-        } else if (bw <= (16 * 341)) {
-            // Go with row buffer 
-            sw = bw;
-            sh =  87381 / bw;
-        } else if (bh <= (16 * 256)) {
-            // Go with column buffer 
-            sw = 87381 / bh;
-            sh = bh;
+
+    if (bw * bh < setup->max_pixels) {
+        // We are small enough
+        sp_canvas_paint_single_buffer (setup->canvas,
+                                       this_rect.x0, this_rect.y0,
+                                       this_rect.x1, this_rect.y1,
+                                       setup->big_rect.x0, setup->big_rect.y0,
+                                       setup->big_rect.x1, setup->big_rect.y1, bw);
+        return 1;
+    }
+
+    NRRectL lo = this_rect;
+    NRRectL hi = this_rect;
+
+/*
+This test determines the redraw strategy:
+
+bw < bh (strips mode) splits across the smaller dimension of the rect and therefore (on
+horizontally-stretched windows) results in redrawing in horizontal strips (from cursor point, in
+both directions if the cursor is in the middle). This is traditional for Inkscape since old days,
+and seems to be faster for drawings with many smaller objects at zoom-out.
+
+bw > bh (chunks mode) splits across the larger dimension of the rect and therefore paints in
+almost-square chunks, again from the cursor point. It's sometimes faster for drawings with few slow
+(e.g. blurred) objects crossing the entire screen. It also appears to be somewhat psychologically
+faster.
+
+The default for now is the strips mode.
+*/
+    if (bw < bh || bh < 2 * TILE_SIZE) {
+        // to correctly calculate the mean of two ints, we need to sum them into a larger int type
+        int mid = ((long long) this_rect.x0 + (long long) this_rect.x1) / 2;
+        // Make sure that mid lies on a tile boundary
+        mid = (mid / TILE_SIZE) * TILE_SIZE;
+
+        lo.x1 = mid;
+        hi.x0 = mid;
+
+        if (setup->mouse_loc[Geom::X] < mid) {
+            // Always paint towards the mouse first
+            return sp_canvas_paint_rect_internal(setup, lo)
+                && sp_canvas_paint_rect_internal(setup, hi);
         } else {
-            sw = 341;
-            sh = 256;
+            return sp_canvas_paint_rect_internal(setup, hi)
+                && sp_canvas_paint_rect_internal(setup, lo);
         }
-    } else {  // paths only, so 1M works faster
-        /* 1M is the cached buffer and we need 3 channels */
-        if (bw * bh <  349525) { // 1M/3
-            // We can go with single buffer 
-            sw = bw;
-            sh = bh;
-        } else if (bw <= (16 * 682)) {
-            // Go with row buffer 
-            sw = bw;
-            sh =  349525 / bw;
-        } else if (bh <= (16 * 512)) {
-            // Go with column buffer 
-            sw = 349525 / bh;
-            sh = bh;
+    } else {
+        // to correctly calculate the mean of two ints, we need to sum them into a larger int type
+        int mid = ((long long) this_rect.y0 + (long long) this_rect.y1) / 2;
+        // Make sure that mid lies on a tile boundary
+        mid = (mid / TILE_SIZE) * TILE_SIZE;
+
+        lo.y1 = mid;
+        hi.y0 = mid;
+
+        if (setup->mouse_loc[Geom::Y] < mid) {
+            // Always paint towards the mouse first
+            return sp_canvas_paint_rect_internal(setup, lo)
+                && sp_canvas_paint_rect_internal(setup, hi);
         } else {
-            sw = 682;
-            sh = 512;
-        }
-    }
-    
-    // Will this paint require more than one buffer?
-    bool multiple_buffers = (((draw_y2 - draw_y1) > sh) || ((draw_x2 - draw_x1) > sw)); // or two_rects
-
-    // remember the counter during this paint
-    long this_count = canvas->redraw_count;
-
-    // Time values to measure each buffer's paint time
-    GTimeVal tstart, tfinish;
-
-    // This is the main loop which corresponds to the visible left-to-right, top-to-bottom drawing
-    // of screen blocks (buffers).
-    for (int y0 = draw_y1; y0 < draw_y2; y0 += sh) {
-        int y1 = MIN (y0 + sh, draw_y2);
-        for (int x0 = draw_x1; x0 < draw_x2; x0 += sw) {
-            int x1 = MIN (x0 + sw, draw_x2);
-
-            // OPTIMIZATION IDEA: if drawing is really slow (as measured by canvas->slowest
-            // buffer), process some events even BEFORE we do any buffers?
-           
-            // Paint one buffer; measure how long it takes.
-            g_get_current_time (&tstart);
-            sp_canvas_paint_single_buffer (canvas, x0, y0, x1, y1, draw_x1, draw_y1, draw_x2, draw_y2, sw);
-            g_get_current_time (&tfinish);
-
-            // Remember the slowest_buffer of this paint.
-            glong this_buffer = (tfinish.tv_sec - tstart.tv_sec) * 1000000 + (tfinish.tv_usec - tstart.tv_usec);
-            if (this_buffer > slowest_buffer) 
-                slowest_buffer = this_buffer;
-
-            // After each successful buffer, reduce the rect remaining to redraw by what is already redrawn
-            if (x1 >= draw_x2 && canvas->redraw_aborted.y0 < y1) 
-                canvas->redraw_aborted.y0 = y1;
-            if (y_aborted_limit != NULL && canvas->redraw_aborted.y0 > *y_aborted_limit)
-                canvas->redraw_aborted.y0 = *y_aborted_limit;
-
-            if (y1 >= draw_y2 && canvas->redraw_aborted.x0 < x1)
-                canvas->redraw_aborted.x0 = x1;
-            if (x_aborted_limit != NULL && canvas->redraw_aborted.x0 > *x_aborted_limit)
-                canvas->redraw_aborted.x0 = *x_aborted_limit;
-
-            // INTERRUPTIBLE DISPLAY:
-            // Process events that may have arrived while we were busy drawing;
-            // only if we're drawing multiple buffers, and only if this one was not very fast,
-            // and only if we're allowed to interrupt this redraw
-            bool ok_to_interrupt = (multiple_buffers && this_buffer > 25000);
-            if (ok_to_interrupt && canvas->forced_redraw_limit) {
-                ok_to_interrupt = (canvas->forced_redraw_count < canvas->forced_redraw_limit);
-            }
-
-            if (ok_to_interrupt) {
-                // Run at most max_iterations of the main loop; we cannot process ALL events
-                // here because some things (e.g. rubberband) flood with dirtying events but will
-                // not redraw themselves
-                int max_iterations = 10;
-                int iterations = 0;
-                while (Gtk::Main::events_pending() && iterations++ < max_iterations) {
-                    Gtk::Main::iteration(false);
-                    // If one of the iterations has redrawn by itself, abort
-                    if (this_count != canvas->redraw_count) {
-                        canvas->slowest_buffer = slowest_buffer;
-                        return 1; // interrupted
-                    }
-                }
-   
-                // If not aborted so far, check if the events set redraw or update flags; 
-                // if so, force update and abort
-                if (canvas->need_redraw || canvas->need_update) {
-                    canvas->slowest_buffer = slowest_buffer;
-                    if (canvas->forced_redraw_limit) {
-                        canvas->forced_redraw_count++;
-                    }
-                    do_update (canvas);
-                    return 1; // interrupted
-                }
-            }
+            return sp_canvas_paint_rect_internal(setup, hi)
+                && sp_canvas_paint_rect_internal(setup, lo);
         }
     }
-
-    // Remember the slowest buffer of this paint in canvas
-    canvas->slowest_buffer = slowest_buffer;
-
-    return 0; // finished
 }
 
 
 /**
  * Helper that draws a specific rectangular part of the canvas.
+ *
+ * @return true if the rectangle painting succeeds.
  */
-static void
+static bool
 sp_canvas_paint_rect (SPCanvas *canvas, int xx0, int yy0, int xx1, int yy1)
 {
-    g_return_if_fail (!canvas->need_update);
-    // Monotonously increment the canvas-global counter on each paint. This will let us find out
-    // when a new paint happened in event processing during this paint, so we can abort it.
-    canvas->redraw_count++;
+    g_return_val_if_fail (!canvas->need_update, false);
 
     NRRectL rect;
     rect.x0 = xx0;
@@ -1728,96 +1902,41 @@ sp_canvas_paint_rect (SPCanvas *canvas, int xx0, int yy0, int xx1, int yy1)
     rect.x1 = MIN (rect.x1, canvas->x0/*draw_x1*/ + GTK_WIDGET (canvas)->allocation.width);
     rect.y1 = MIN (rect.y1, canvas->y0/*draw_y1*/ + GTK_WIDGET (canvas)->allocation.height);
 
-    // Clip rect-aborted-last-time by the current visible area
-    canvas->redraw_aborted.x0 = MAX (canvas->redraw_aborted.x0, canvas->x0);
-    canvas->redraw_aborted.y0 = MAX (canvas->redraw_aborted.y0, canvas->y0);
-    canvas->redraw_aborted.x1 = MIN (canvas->redraw_aborted.x1, canvas->x0/*draw_x1*/ + GTK_WIDGET (canvas)->allocation.width);
-    canvas->redraw_aborted.y1 = MIN (canvas->redraw_aborted.y1, canvas->y0/*draw_y1*/ + GTK_WIDGET (canvas)->allocation.height);
-
-    if (canvas->redraw_aborted.x0 < canvas->redraw_aborted.x1 && canvas->redraw_aborted.y0 < canvas->redraw_aborted.y1) {
-        // There was an aborted redraw last time, now we need to redraw BOTH it and the new rect.
-
-        // save the old aborted rect in case we decide to paint it separately (see below)
-        NRRectL aborted = canvas->redraw_aborted;
-
-        // calculate the rectangle union of the both rects (the smallest rectangle which covers both) 
-        NRRectL nion;
-        nr_rect_l_union (&nion, &rect, &aborted);
-
-        // subtract one of the rects-to-draw from the other (the smallest rectangle which covers
-        // all of the first not covered by the second)
-        NRRectL rect_minus_aborted;
-        nr_rect_l_subtract (&rect_minus_aborted, &rect, &aborted);
-
-        // Initially, the rect to redraw later (in case we're aborted) is the same as the union of both rects
-        canvas->redraw_aborted = nion;
-
-        // calculate areas of the three rects
-        if ((nr_rect_l_area(&rect_minus_aborted) + nr_rect_l_area(&aborted)) * 1.2 < nr_rect_l_area(&nion)) {
-            // If the summary area of the two rects is significantly (at least by 20%) less than
-            // the area of their rectangular union, it makes sense to paint the two rects
-            // separately instead of painting their union. This gives a significant speedup when,
-            // for example, your current canvas is almost painted, with only a strip at bottom
-            // left, and at that moment you abort it by scrolling down which reveals a new strip at
-            // the top. Straightforward painting of the union of the aborted rect and the new rect
-            // will have to repaint the entire canvas! By contrast, the optimized approach below
-            // paints the two narrow strips in order which is much faster.
-
-            // find out which rect to draw first - compare them first by y then by x of the top left corners
-            NRRectL *first;
-            NRRectL *second;
-            if (rect.y0 == aborted.y0) {
-                if (rect.x0 < aborted.x0) {
-                    first = &rect;
-                    second = &aborted;
-                } else {
-                    second = &rect;
-                    first = &aborted;
-                }
-            } else if (rect.y0 < aborted.y0) {
-                first = &rect;
-                second = &aborted;
-            } else {
-                second = &rect;
-                first = &aborted;
-            }
+#ifdef DEBUG_REDRAW
+    // paint the area to redraw yellow
+    gdk_rgb_gc_set_foreground (canvas->pixmap_gc, 0xFFFF00);
+    gdk_draw_rectangle (SP_CANVAS_WINDOW (canvas),
+                        canvas->pixmap_gc,
+                        TRUE,
+                        rect.x0 - canvas->x0, rect.y0 - canvas->y0,
+                        rect.x1 - rect.x0, rect.y1 - rect.y0);
+#endif
 
-            NRRectL second_minus_first;
-            nr_rect_l_subtract (&second_minus_first, second, first);
+    PaintRectSetup setup;
 
-            // paint the first rect;
-            if (sp_canvas_paint_rect_internal (canvas, first, &(second_minus_first.x0), &(second_minus_first.y0))) {
-                // aborted!
-                return;
-            }
+    setup.canvas = canvas;
+    setup.big_rect = rect;
 
-            // if not aborted, assign (second rect minus first) as the new redraw_aborted and paint the same
-            canvas->redraw_aborted = second_minus_first;
-            if (sp_canvas_paint_rect_internal (canvas, &second_minus_first, NULL, NULL)) {
-                return; // aborted
-            }
+    // Save the mouse location
+    gint x, y;
+    gdk_window_get_pointer (GTK_WIDGET(canvas)->window, &x, &y, NULL);
+    setup.mouse_loc = sp_canvas_window_to_world (canvas, Geom::Point(x,y));
 
-        } else {
-            // no need for separate drawing, just draw the union as one rect
-            if (sp_canvas_paint_rect_internal (canvas, &nion, NULL, NULL)) {
-                return; // aborted
-            }
-        }
+    if (canvas->rendermode != Inkscape::RENDERMODE_OUTLINE) {
+        // use 256K as a compromise to not slow down gradients
+        // 256K is the cached buffer and we need 4 channels
+        setup.max_pixels = 65536; // 256K/4
     } else {
-        // Nothing was aborted last time, just draw the rect we're given
-
-        // Initially, the rect to redraw later (in case we're aborted) is the same as the one we're going to draw now.
-        canvas->redraw_aborted = rect;
-
-        if (sp_canvas_paint_rect_internal (canvas, &rect, NULL, NULL)) {
-            return; // aborted
-        }
+        // paths only, so 1M works faster
+        // 1M is the cached buffer and we need 4 channels
+        setup.max_pixels = 262144;
     }
 
-    // we've had a full unaborted redraw, reset the full redraw counter
-    if (canvas->forced_redraw_limit) {
-        canvas->forced_redraw_count = 0;
-    }
+    // Start the clock
+    g_get_current_time(&(setup.start_time));
+
+    // Go
+    return sp_canvas_paint_rect_internal(&setup, rect);
 }
 
 /**
@@ -1826,7 +1945,7 @@ sp_canvas_paint_rect (SPCanvas *canvas, int xx0, int yy0, int xx1, int yy1)
 void
 sp_canvas_force_full_redraw_after_interruptions(SPCanvas *canvas, unsigned int count) {
   g_return_if_fail(canvas != NULL);
-  
+
   canvas->forced_redraw_limit = count;
   canvas->forced_redraw_count = 0;
 }
@@ -1838,7 +1957,7 @@ void
 sp_canvas_end_forced_full_redraws(SPCanvas *canvas) {
   g_return_if_fail(canvas != NULL);
 
-  canvas->forced_redraw_limit = 0;
+  canvas->forced_redraw_limit = -1;
 }
 
 /**
@@ -1849,7 +1968,7 @@ sp_canvas_expose (GtkWidget *widget, GdkEventExpose *event)
 {
     SPCanvas *canvas = SP_CANVAS (widget);
 
-    if (!GTK_WIDGET_DRAWABLE (widget) || 
+    if (!GTK_WIDGET_DRAWABLE (widget) ||
         (event->window != SP_CANVAS_WINDOW (canvas)))
         return FALSE;
 
@@ -1859,7 +1978,7 @@ sp_canvas_expose (GtkWidget *widget, GdkEventExpose *event)
 
     for (int i = 0; i < n_rects; i++) {
         NRRectL rect;
-               
+
         rect.x0 = rects[i].x + canvas->x0;
         rect.y0 = rects[i].y + canvas->y0;
         rect.x1 = rect.x0 + rects[i].width;
@@ -1933,59 +2052,55 @@ sp_canvas_focus_out (GtkWidget *widget, GdkEventFocus *event)
 
 /**
  * Helper that repaints the areas in the canvas that need it.
+ *
+ * @return true if all the dirty parts have been redrawn
  */
 static int
 paint (SPCanvas *canvas)
 {
     if (canvas->need_update) {
-        sp_canvas_item_invoke_update (canvas->root, NR::identity(), 0);
+        sp_canvas_item_invoke_update (canvas->root, Geom::identity(), 0);
         canvas->need_update = FALSE;
     }
 
     if (!canvas->need_redraw)
         return TRUE;
 
-    GtkWidget const *widget = GTK_WIDGET(canvas);
-    int const canvas_x1 = canvas->x0 + widget->allocation.width;
-    int const canvas_y1 = canvas->y0 + widget->allocation.height;
-
-    NRRectL topaint;
-    topaint.x0 = topaint.y0 = topaint.x1 = topaint.y1 = 0;
-
-    for (int j=canvas->tTop&(~3);j<canvas->tBottom;j+=4) {
-        for (int i=canvas->tLeft&(~3);i<canvas->tRight;i+=4) {
-            int  mode=0;
-      
-            int pl=i+1,pr=i,pt=j+4,pb=j;
-            for (int l=MAX(j,canvas->tTop);l<MIN(j+4,canvas->tBottom);l++) {
-                for (int k=MAX(i,canvas->tLeft);k<MIN(i+4,canvas->tRight);k++) {
-                    if ( canvas->tiles[(k-canvas->tLeft)+(l-canvas->tTop)*canvas->tileH] ) {
-                        mode|=1<<((k-i)+(l-j)*4);
-                        if ( k < pl ) pl=k;
-                        if ( k+1 > pr ) pr=k+1;
-                        if ( l < pt ) pt=l;
-                        if ( l+1 > pb ) pb=l+1;
-                    }
-                    canvas->tiles[(k-canvas->tLeft)+(l-canvas->tTop)*canvas->tileH]=0;
-                }
-            }
-      
-            if ( mode ) {
-                NRRectL tile;
-                tile.x0 = MAX (pl*32, canvas->x0);
-                tile.y0 = MAX (pt*32, canvas->y0);
-                tile.x1 = MIN (pr*32, canvas_x1);
-                tile.y1 = MIN (pb*32, canvas_y1);
-                if ((tile.x0 < tile.x1) && (tile.y0 < tile.y1)) {
-                    nr_rect_l_union (&topaint, &topaint, &tile);
-                }
+    Gdk::Region to_paint;
+
+    for (int j=canvas->tTop; j<canvas->tBottom; j++) {
+        for (int i=canvas->tLeft; i<canvas->tRight; i++) {
+            int tile_index = (i - canvas->tLeft) + (j - canvas->tTop)*canvas->tileH;
 
+            if ( canvas->tiles[tile_index] ) { // if this tile is dirtied (nonzero)
+                to_paint.union_with_rect(Gdk::Rectangle(i*TILE_SIZE, j*TILE_SIZE,
+                                   TILE_SIZE, TILE_SIZE));
             }
+
+        }
+    }
+
+    if (!to_paint.empty()) {
+        Glib::ArrayHandle<Gdk::Rectangle> rect = to_paint.get_rectangles();
+        typedef Glib::ArrayHandle<Gdk::Rectangle>::const_iterator Iter;
+        for (Iter i=rect.begin(); i != rect.end(); ++i) {
+            int x0 = (*i).get_x();
+            int y0 = (*i).get_y();
+            int x1 = x0 + (*i).get_width();
+            int y1 = y0 + (*i).get_height();
+            if (!sp_canvas_paint_rect(canvas, x0, y0, x1, y1)) {
+                // Aborted
+                return FALSE;
+            };
         }
     }
 
     canvas->need_redraw = FALSE;
-    sp_canvas_paint_rect (canvas, topaint.x0, topaint.y0, topaint.x1, topaint.y1);
+
+    // we've had a full unaborted redraw, reset the full redraw counter
+    if (canvas->forced_redraw_limit != -1) {
+        canvas->forced_redraw_count = 0;
+    }
 
     return TRUE;
 }
@@ -1996,12 +2111,15 @@ paint (SPCanvas *canvas)
 static int
 do_update (SPCanvas *canvas)
 {
-    if (!canvas->root) // canvas may have already be destroyed by closing desktop durring interrupted display!
+    if (!canvas->root || !canvas->pixmap_gc) // canvas may have already be destroyed by closing desktop during interrupted display!
+        return TRUE;
+        
+    if (canvas->drawing_disabled)
         return TRUE;
 
     /* Cause the update if necessary */
     if (canvas->need_update) {
-        sp_canvas_item_invoke_update (canvas->root, NR::identity(), 0);
+        sp_canvas_item_invoke_update (canvas->root, Geom::identity(), 0);
         canvas->need_update = FALSE;
     }
 
@@ -2029,7 +2147,7 @@ idle_handler (gpointer data)
 
     SPCanvas *canvas = SP_CANVAS (data);
 
-    const int ret = do_update (canvas);
+    int const ret = do_update (canvas);
 
     if (ret) {
         /* Reset idle id */
@@ -2066,53 +2184,41 @@ sp_canvas_root (SPCanvas *canvas)
 }
 
 /**
- * Scrolls canvas to specific position.
+ * Scrolls canvas to specific position (cx and cy are measured in screen pixels)
  */
 void
-sp_canvas_scroll_to (SPCanvas *canvas, double cx, double cy, unsigned int clear)
+sp_canvas_scroll_to (SPCanvas *canvas, double cx, double cy, unsigned int clear, bool is_scrolling)
 {
     g_return_if_fail (canvas != NULL);
     g_return_if_fail (SP_IS_CANVAS (canvas));
 
-    int ix = (int) (cx + 0.5);
-    int iy = (int) (cy + 0.5);
-    int dx = ix - canvas->x0;
-    int dy = iy - canvas->y0;
+    int ix = (int) round(cx); // ix and iy are the new canvas coordinates (integer screen pixels)
+    int iy = (int) round(cy); // cx might be negative, so (int)(cx + 0.5) will not do!
+    int dx = ix - canvas->x0; // dx and dy specify the displacement (scroll) of the
+    int dy = iy - canvas->y0; // canvas w.r.t its previous position
 
-    canvas->dx0 = cx;
+    canvas->dx0 = cx; // here the 'd' stands for double, not delta!
     canvas->dy0 = cy;
     canvas->x0 = ix;
     canvas->y0 = iy;
 
-    sp_canvas_resize_tiles(canvas,canvas->x0,canvas->y0,canvas->x0+canvas->widget.allocation.width,canvas->y0+canvas->widget.allocation.height);
+    sp_canvas_resize_tiles (canvas, canvas->x0, canvas->y0, canvas->x0+canvas->widget.allocation.width, canvas->y0+canvas->widget.allocation.height);
 
     if (!clear) {
         // scrolling without zoom; redraw only the newly exposed areas
         if ((dx != 0) || (dy != 0)) {
-            int width, height;
-            width = canvas->widget.allocation.width;
-            height = canvas->widget.allocation.height;
+            canvas->is_scrolling = is_scrolling;
             if (GTK_WIDGET_REALIZED (canvas)) {
                 gdk_window_scroll (SP_CANVAS_WINDOW (canvas), -dx, -dy);
-                gdk_window_process_updates (SP_CANVAS_WINDOW (canvas), TRUE);
-            }
-            if (dx < 0) {
-                sp_canvas_request_redraw (canvas, ix + 0, iy + 0, ix - dx, iy + height);
-            } else if (dx > 0) {
-                sp_canvas_request_redraw (canvas, ix + width - dx, iy + 0, ix + width, iy + height);
-            }
-            if (dy < 0) {
-                sp_canvas_request_redraw (canvas, ix + 0, iy + 0, ix + width, iy - dy);
-            } else if (dy > 0) {
-                sp_canvas_request_redraw (canvas, ix + 0, iy + height - dy, ix + width, iy + height);
             }
         }
     } else {
         // scrolling as part of zoom; do nothing here - the next do_update will perform full redraw
     }
+
 }
 
-/** 
+/**
  * Updates canvas if necessary.
  */
 void
@@ -2125,7 +2231,6 @@ sp_canvas_update_now (SPCanvas *canvas)
           canvas->need_redraw))
         return;
 
-    remove_idle (canvas);
     do_update (canvas);
 }
 
@@ -2198,67 +2303,74 @@ void sp_canvas_world_to_window(SPCanvas const *canvas, double worldx, double wor
 /**
  * Converts point from win to world coordinates.
  */
-NR::Point sp_canvas_window_to_world(SPCanvas const *canvas, NR::Point const win)
+Geom::Point sp_canvas_window_to_world(SPCanvas const *canvas, Geom::Point const win)
 {
     g_assert (canvas != NULL);
     g_assert (SP_IS_CANVAS (canvas));
 
-    return NR::Point(canvas->x0 + win[0], canvas->y0 + win[1]);
+    return Geom::Point(canvas->x0 + win[0], canvas->y0 + win[1]);
 }
 
 /**
  * Converts point from world to win coordinates.
  */
-NR::Point sp_canvas_world_to_window(SPCanvas const *canvas, NR::Point const world)
+Geom::Point sp_canvas_world_to_window(SPCanvas const *canvas, Geom::Point const world)
 {
     g_assert (canvas != NULL);
     g_assert (SP_IS_CANVAS (canvas));
 
-    return NR::Point(world[0] - canvas->x0, world[1] - canvas->y0);
+    return Geom::Point(world[0] - canvas->x0, world[1] - canvas->y0);
 }
 
 /**
  * Returns true if point given in world coordinates is inside window.
  */
-bool sp_canvas_world_pt_inside_window(SPCanvas const *canvas, NR::Point const &world)
+bool sp_canvas_world_pt_inside_window(SPCanvas const *canvas, Geom::Point const &world)
 {
     g_assert( canvas != NULL );
     g_assert(SP_IS_CANVAS(canvas));
 
-    using NR::X;
-    using NR::Y;
     GtkWidget const &w = *GTK_WIDGET(canvas);
-    return ( ( canvas->x0 <= world[X] )  &&
-             ( canvas->y0 <= world[Y] )  &&
-             ( world[X] < canvas->x0 + w.allocation.width )  &&
-             ( world[Y] < canvas->y0 + w.allocation.height ) );
+    return ( ( canvas->x0 <= world[Geom::X] )  &&
+             ( canvas->y0 <= world[Geom::Y] )  &&
+             ( world[Geom::X] < canvas->x0 + w.allocation.width )  &&
+             ( world[Geom::Y] < canvas->y0 + w.allocation.height ) );
 }
 
 /**
- * Return canvas window coordinates as NRRect.
+ * Return canvas window coordinates as Geom::Rect.
  */
-NR::Rect SPCanvas::getViewbox() const
+Geom::Rect SPCanvas::getViewbox() const
 {
     GtkWidget const *w = GTK_WIDGET(this);
+    return Geom::Rect(Geom::Point(dx0, dy0),
+                      Geom::Point(dx0 + w->allocation.width, dy0 + w->allocation.height));
+}
 
-    return NR::Rect(NR::Point(dx0, dy0),
-                    NR::Point(dx0 + w->allocation.width, dy0 + w->allocation.height));
+/**
+ * Return canvas window coordinates as IRect (a rectangle defined by integers).
+ */
+NR::IRect SPCanvas::getViewboxIntegers() const
+{
+    GtkWidget const *w = GTK_WIDGET(this);
+    return NR::IRect(NR::IPoint(x0, y0),
+                    NR::IPoint(x0 + w->allocation.width, y0 + w->allocation.height));
 }
 
 inline int sp_canvas_tile_floor(int x)
 {
-    return (x&(~31))/32;
+    return (x & (~(TILE_SIZE - 1))) / TILE_SIZE;
 }
 
 inline int sp_canvas_tile_ceil(int x)
 {
-    return ((x+31)&(~31))/32;
+    return ((x + (TILE_SIZE - 1)) & (~(TILE_SIZE - 1))) / TILE_SIZE;
 }
 
 /**
- * Helper that changes tile size for canvas redraw.
+ * Helper that allocates a new tile array for the canvas, copying overlapping tiles from the old array
  */
-void sp_canvas_resize_tiles(SPCanvas* canvas,int nl,int nt,int nr,int nb)
+static void sp_canvas_resize_tiles(SPCanvas* canvas, int nl, int nt, int nr, int nb)
 {
     if ( nl >= nr || nt >= nb ) {
         if ( canvas->tiles ) g_free(canvas->tiles);
@@ -2272,15 +2384,15 @@ void sp_canvas_resize_tiles(SPCanvas* canvas,int nl,int nt,int nr,int nb)
     int tr=sp_canvas_tile_ceil(nr);
     int tb=sp_canvas_tile_ceil(nb);
 
-    int nh=tr-tl,nv=tb-tt;
-    uint8_t* ntiles=(uint8_t*)g_malloc(nh*nv*sizeof(uint8_t));
-    for (int i=tl;i<tr;i++) {
-        for (int j=tt;j<tb;j++) {
-            int ind=(i-tl)+(j-tt)*nh;
+    int nh = tr-tl, nv = tb-tt;
+    uint8_t* ntiles = (uint8_t*)g_malloc(nh*nv*sizeof(uint8_t));
+    for (int i=tl; i<tr; i++) {
+        for (int j=tt; j<tb; j++) {
+            int ind = (i-tl) + (j-tt)*nh;
             if ( i >= canvas->tLeft && i < canvas->tRight && j >= canvas->tTop && j < canvas->tBottom ) {
-                ntiles[ind]=canvas->tiles[(i-canvas->tLeft)+(j-canvas->tTop)*canvas->tileH];
+                ntiles[ind]=canvas->tiles[(i-canvas->tLeft)+(j-canvas->tTop)*canvas->tileH]; // copy from the old tile
             } else {
-                ntiles[ind]=0;
+                ntiles[ind]=0; // newly exposed areas get 0
             }
         }
     }
@@ -2294,10 +2406,19 @@ void sp_canvas_resize_tiles(SPCanvas* canvas,int nl,int nt,int nr,int nb)
     canvas->tileV=nv;
 }
 
+/*
+ * Helper that queues a canvas rectangle for redraw
+ */
+static void sp_canvas_dirty_rect(SPCanvas* canvas, int nl, int nt, int nr, int nb) {
+    canvas->need_redraw = TRUE;
+
+    sp_canvas_mark_rect(canvas, nl, nt, nr, nb, 1);
+}
+
 /**
- * Helper that marks specific canvas rectangle for redraw.
+ * Helper that marks specific canvas rectangle as clean (val == 0) or dirty (otherwise)
  */
-void sp_canvas_dirty_rect(SPCanvas* canvas,int nl,int nt,int nr,int nb)
+void sp_canvas_mark_rect(SPCanvas* canvas, int nl, int nt, int nr, int nb, uint8_t val)
 {
     if ( nl >= nr || nt >= nb ) {
         return;
@@ -2312,11 +2433,9 @@ void sp_canvas_dirty_rect(SPCanvas* canvas,int nl,int nt,int nr,int nb)
     if ( tt < canvas->tTop ) tt=canvas->tTop;
     if ( tb > canvas->tBottom ) tb=canvas->tBottom;
 
-    canvas->need_redraw = TRUE;
-
-    for (int i=tl;i<tr;i++) {
-        for (int j=tt;j<tb;j++) {
-            canvas->tiles[(i-canvas->tLeft)+(j-canvas->tTop)*canvas->tileH]=1;
+    for (int i=tl; i<tr; i++) {
+        for (int j=tt; j<tb; j++) {
+            canvas->tiles[(i-canvas->tLeft)+(j-canvas->tTop)*canvas->tileH] = val;
         }
     }
 }
@@ -2331,4 +2450,4 @@ void sp_canvas_dirty_rect(SPCanvas* canvas,int nl,int nt,int nr,int nb)
   fill-column:99
   End:
 */
-// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :