X-Git-Url: https://git.tokkee.org/?a=blobdiff_plain;f=src%2Fdisplay%2Fsp-canvas.cpp;h=fc68bcfc00a3c6ac304033832b183ab925738eab;hb=0dc33d4ce43e0bb49c63aa53b826ec4a1ff68e28;hp=6bd88613240b22fc6ab08be4ba64e8e08cbda1b1;hpb=f7c0e9e8ec67fe896289e0434cee2f6dae571ce7;p=inkscape.git diff --git a/src/display/sp-canvas.cpp b/src/display/sp-canvas.cpp index 6bd886132..fc68bcfc0 100644 --- a/src/display/sp-canvas.cpp +++ b/src/display/sp-canvas.cpp @@ -1,5 +1,3 @@ -#define __SP_CANVAS_C__ - /** \file * Port of GnomeCanvas for Inkscape needs * @@ -24,23 +22,47 @@ #include #include +#include #include -#include -#include -#include "display-forward.h" -#include -#include -#include - -enum { - RENDERMODE_NORMAL, - RENDERMODE_NOAA, - RENDERMODE_OUTLINE -}; - -const gint sp_canvas_update_priority = G_PRIORITY_HIGH_IDLE; +#include "helper/sp-marshal.h" +#include +#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(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(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 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 = ▭ - second = &aborted; - } else { - second = ▭ - first = &aborted; - } - } else if (rect.y0 < aborted.y0) { - first = ▭ - second = &aborted; - } else { - second = ▭ - 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);jtBottom;j+=4) { - for (int i=canvas->tLeft&(~3);itRight;i+=4) { - int mode=0; - - int pl=i+1,pr=i,pt=j+4,pb=j; - for (int l=MAX(j,canvas->tTop);ltBottom);l++) { - for (int k=MAX(i,canvas->tLeft);ktRight);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; jtBottom; j++) { + for (int i=canvas->tLeft; itRight; 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 rect = to_paint.get_rectangles(); + typedef Glib::ArrayHandle::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= 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;itiles[(i-canvas->tLeft)+(j-canvas->tTop)*canvas->tileH]=1; + for (int i=tl; itiles[(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 :