/*
* Port of GnomeCanvas for Inkscape needs
*
* Authors:
* Federico Mena <federico@nuclecu.unam.mx>
* Raph Levien <raph@gimp.org>
* Lauris Kaplinski <lauris@kaplinski.com>
* fred
* bbyak
* Jon A. Cruz <jon@joncruz.org>
* Krzysztof KosiĆski <tweenk.pl@gmail.com>
*
* Copyright (C) 1998 The Free Software Foundation
* Copyright (C) 2002-2006 authors
* Copyright (C) 2016 Google Inc.
*
* Released under GNU GPL, read the file 'COPYING' for more information
*/
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include <gdkmm/rectangle.h>
#include "helper/sp-marshal.h"
#include "display/cairo-utils.h"
#include "display/sp-canvas.h"
#include "display/sp-canvas-group.h"
#include "preferences.h"
#include "inkscape.h"
#include "sodipodi-ctrlrect.h"
#include "cms-system.h"
#include "display/rendermode.h"
#include "display/cairo-utils.h"
#include "debug/gdk-event-latency-tracker.h"
#include "desktop.h"
#include "color.h"
// gtk_check_version returns non-NULL on failure
static bool const HAS_BROKEN_MOTION_HINTS =
// 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.
/**
* The SPCanvasGroup vtable.
*/
struct SPCanvasGroupClass {
};
/**
* A group of items.
*/
struct SPCanvasGroup {
/**
* Adds an item to a canvas group.
*/
/**
* Removes an item from a canvas group.
*/
/**
* Class initialization function for SPCanvasGroupClass.
*/
/**
* Callback. Empty.
*/
/**
* Callback that destroys all items in group and calls group's virtual
* destroy() function.
*/
/**
* Update handler for canvas groups.
*/
/**
* Point handler for canvas groups.
*/
/**
* Renders all visible canvas group items in buf rectangle.
*/
// Data members: ----------------------------------------------------------
};
/**
* The SPCanvas vtable.
*/
struct SPCanvasClass {
};
namespace {
{
}
// SPCanvasItem
enum {
};
enum {
};
enum {
};
/**
* Callback that removes item from all referers and destroys it.
*/
/**
* Sets up the newly created SPCanvasItem.
*
* We make it static for encapsulation reasons since it was nowhere used.
*/
void sp_canvas_item_construct(SPCanvasItem *item, SPCanvasGroup *parent, gchar const *first_arg_name, va_list args);
/**
* Helper that returns true iff item is descendant of parent.
*/
} // namespace
static void
{
G_TYPE_BOOLEAN, 1,
g_signal_new ("destroy",
G_TYPE_NONE, 0);
}
static void
{
item->ctrlResize = 0;
// 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->in_destruction = false;
}
SPCanvasItem *sp_canvas_item_new(SPCanvasGroup *parent, GType type, gchar const *first_arg_name, ...)
{
return item;
}
namespace {
void sp_canvas_item_construct(SPCanvasItem *item, SPCanvasGroup *parent, gchar const *first_arg_name, va_list args)
{
}
} // namespace
/**
* Helper function that requests redraw only if item's visible flag is set.
*/
{
item->canvas->requestRedraw((int)(item->x1 - 1), (int)(item->y1 -1), (int)(item->x2 + 1), (int)(item->y2 + 1));
}
}
}
{
if (!item->in_destruction)
}
namespace {
{
/* guard against reinvocations during
* destruction with the in_destruction flag.
*/
if (!item->in_destruction)
{
item->in_destruction=true;
// 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)) {
} else {
}
}
}
#if GTK_CHECK_VERSION(3,0,0)
#else
#endif
}
}
}
item->in_destruction = false;
}
}
{
}
{
if (g_object_is_floating (object))
{
g_warning ("A floating object was finalized. This means that someone\n"
"called g_object_unref() on an object that had only a floating\n"
"reference; the initial floating reference is not owned by anyone\n"
"and must be removed with g_object_ref_sink().");
}
}
} // namespace
/**
* Helper function to update item and its children.
*
* NB! affine is parent2canvas.
*/
static void sp_canvas_item_invoke_update(SPCanvasItem *item, Geom::Affine const &affine, unsigned int flags)
{
// Apply the child item's transform
// apply object flags to child flags
if (item->need_update) {
}
if (item->need_affine) {
}
}
}
}
/**
* 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,
* maintaining the affine invariant.
*/
static double sp_canvas_item_invoke_point(SPCanvasItem *item, Geom::Point p, SPCanvasItem **actual_item)
{
}
}
/**
* Makes the item's affine transformation matrix be equal to the specified
* matrix.
*
* @item: A canvas item.
* @affine: An affine transformation matrix.
*/
{
if (!item->need_affine) {
} else {
}
}
}
/**
* 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.
*
* If the number of positions is greater than the distance to the top of the
* stack, then the item is put at the top.
*/
{
g_return_if_fail (positions >= 0);
return;
}
++l;
}
{
return;
}
/**
* Lowers the item in its parent's stack by the specified number of positions.
*
* @param item A canvas item.
* @param positions Number of steps to lower the item.
*
* If the number of positions is greater than the distance to the bottom of the
* stack, then the item is put at the bottom.
*/
{
return;
}
std::list<SPCanvasItem *>::iterator l = std::find(parent->items.begin(), parent->items.end(), item);
--l;
}
{
return;
}
{
}
/**
* Sets visible flag on item and requests a redraw.
*/
{
return;
}
item->canvas->requestRedraw((int)(item->x1), (int)(item->y1), (int)(item->x2 + 1), (int)(item->y2 + 1));
}
}
/**
* Clears visible flag on item and requests a redraw.
*/
{
return;
}
item->canvas->requestRedraw((int)item->x1, (int)item->y1, (int)(item->x2 + 1), (int)(item->y2 + 1));
}
}
/**
* Grab item under cursor.
*
* \pre !canvas->grabbed_item && 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) {
}
// fixme: Top hack (Lauris)
// fixme: If we add key masks to event mask, Gdk will abort (Lauris)
// fixme: But Canvas actualle does get key events, so all we need is routing these here
#if GTK_CHECK_VERSION(3,0,0)
etime);
#else
#endif
return 0;
}
/**
* 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.
*/
{
return;
}
#if GTK_CHECK_VERSION(3,0,0)
#else
#endif
}
/**
* Returns the product of all transformation matrices from the root item down
* to the item.
*/
{
while (item) {
}
return affine;
}
namespace {
{
while (item) {
return true;
}
}
return false;
}
} // namespace
/**
* Requests that the canvas queue an update for the specified item.
*
* To be used only by item implementations.
*/
{
if (item->need_update) {
return;
}
// Recurse up the tree
} else {
// Have reached the top of the tree, make sure the update call gets scheduled.
}
}
/**
* Returns position of item in group.
*/
{
for (std::list<SPCanvasItem*>::const_iterator it = p->items.begin(); it != p->items.end(); ++it, ++index) {
return index;
}
}
return -1;
}
// SPCanvasGroup
{
}
{
}
{
for (std::list<SPCanvasItem *>::iterator it = group->items.begin(); it != group->items.end(); ++it) {
}
}
}
{
for (std::list<SPCanvasItem *>::const_iterator it = group->items.begin(); it != group->items.end(); ++it) {
SPCanvasItem *i = *it;
}
}
if (bounds) {
} else {
// FIXME ?
}
}
{
double const x = p[Geom::X];
double const y = p[Geom::Y];
*actual_item = NULL;
for (std::list<SPCanvasItem *>::const_iterator it = group->items.begin(); it != group->items.end(); ++it) {
int pickable;
} else {
}
// TODO: This metric should be improved, because in case of (partly) overlapping items we will now
// always select the last one that has been added to the group. We could instead select the one
// of which the center is the closest, for example. One can then move to the center
// of the item to be focused, and have that one selected. Of course this will only work if the
// centers are not coincident, but at least it's better than what we have now.
// See the extensive comment in Inkscape::SelTrans::_updateHandles()
}
}
}
return best;
}
{
for (std::list<SPCanvasItem *>::const_iterator it = group->items.begin(); it != group->items.end(); ++it) {
}
}
}
}
}
{
for (std::list<SPCanvasItem *>::const_iterator it = group->items.begin(); it != group->items.end(); ++it) {
}
}
}
}
{
}
{
// Unparent the child
}
{
#if GTK_CHECK_VERSION(3,0,0)
#else
#endif
}
{
// Create the root item as a special case
// See comment at in sp-canvas.h.
canvas->_gen_all_enter_events = false;
canvas->_drawing_disabled = false;
canvas->_background_is_checkerboard = false;
canvas->_forced_redraw_count = 0;
#if defined(HAVE_LIBLCMS1) || defined(HAVE_LIBLCMS2)
canvas->_enable_cms_display_adj = false;
#endif // defined(HAVE_LIBLCMS1) || defined(HAVE_LIBLCMS2)
}
{
// Reset the clean region
dirtyAll();
if (_grabbed_item) {
#if GTK_CHECK_VERSION(3,0,0)
#else
#endif
}
removeIdle();
}
{
}
if (canvas->_backing_store) {
}
if (canvas->_clean_region) {
}
if (canvas->_background) {
}
#if defined(HAVE_LIBLCMS1) || defined(HAVE_LIBLCMS2)
#endif
}
}
namespace {
{
//g_warning("Event latency reached %f sec (%1.4f)", *latency, tracker.getSkew());
}
}
} // namespace
{
return GTK_WIDGET(canvas);
}
{
attributes.x = allocation.x;
attributes.y = allocation.y;
#if !GTK_CHECK_VERSION(3,0,0)
#endif
0 : GDK_POINTER_MOTION_HINT_MASK ) |
#if GTK_CHECK_VERSION(3,0,0)
#else
#endif
GdkWindow *window = gdk_window_new (gtk_widget_get_parent_window (widget), &attributes, attributes_mask);
#if !GTK_CHECK_VERSION(3,0,0)
// TODO: Extension event stuff has been deprecated in GTK+ 3
#endif
}
#if !GTK_CHECK_VERSION(3,0,0)
// This does nothing in GTK+ 3
#endif
}
{
}
#if GTK_CHECK_VERSION(3,0,0)
void SPCanvas::handle_get_preferred_width(GtkWidget *widget, gint *minimum_width, gint *natural_width)
{
*minimum_width = 256;
*natural_width = 256;
}
void SPCanvas::handle_get_preferred_height(GtkWidget *widget, gint *minimum_height, gint *natural_height)
{
*minimum_height = 256;
*natural_height = 256;
}
#else
{
}
#endif
{
// Geom::IntRect old_area = Geom::IntRect::from_xywh(canvas->_x0, canvas->_y0,
// old_allocation.width, old_allocation.height);
// resize backing store
if (canvas->_backing_store) {
}
// Clip the clean region to the new allocation
if (gtk_widget_get_realized (widget)) {
allocation->x, allocation->y,
}
// Schedule redraw of any newly exposed regions
}
{
if (_grabbed_item) {
case GDK_ENTER_NOTIFY:
break;
case GDK_LEAVE_NOTIFY:
break;
case GDK_MOTION_NOTIFY:
break;
case GDK_BUTTON_PRESS:
case GDK_2BUTTON_PRESS:
case GDK_3BUTTON_PRESS:
break;
case GDK_BUTTON_RELEASE:
break;
case GDK_KEY_PRESS:
break;
case GDK_KEY_RELEASE:
break;
case GDK_SCROLL:
#if GTK_CHECK_VERSION(3,0,0)
#endif
break;
default:
mask = 0;
break;
}
}
// Convert to world coordinates -- we have two cases because of different
// offsets of the fields in the event structures.
case GDK_ENTER_NOTIFY:
case GDK_LEAVE_NOTIFY:
break;
case GDK_MOTION_NOTIFY:
case GDK_BUTTON_PRESS:
case GDK_2BUTTON_PRESS:
case GDK_3BUTTON_PRESS:
case GDK_BUTTON_RELEASE:
break;
default:
break;
}
// Block Undo and Redo while we drag /anything/
_is_dragging = true;
_is_dragging = false;
// Choose where we send the event
// canvas->current_item becomes NULL in some cases under Win32
// (e.g. if the pointer leaves the window). So this is a hack that
// Lauris applied to SP to get around the problem.
//
} else {
}
if (_focused_item &&
}
// The event is propagated up the hierarchy (for if someone connected to
// a group instead of a leaf event), and emission is stopped if a
// handler returns TRUE, just like for GtkWidget events.
g_object_ref (item);
}
return finished;
}
{
int button_down = 0;
if (!_root) // canvas may have already be destroyed by closing desktop during interrupted display!
return FALSE;
if (_gen_all_enter_events == false) {
// If a button is down, we'll perform enter and leave events on the
// current item, but not enter on any other item. This is more or
// less like X pointer grabbing for canvas items.
//
}
// Save the event in the canvas. This is used to synthesize enter and
// leave events in case the current item changes. It is also used to
// re-pick the current item if the current one gets deleted. Also,
// synthesize an enter event.
if (event != &_pick_event) {
// these fields have the same offsets in both types of events
// these fields don't have the same offsets in both types of events
} else {
}
} else {
_pick_event = *event;
}
}
// Don't do anything else if this is a recursive call
if (_in_repick) {
return retval;
}
// LeaveNotify means that there is no current item, so we don't look for one
// these fields don't have the same offsets in both types of events
double x, y;
x = _pick_event.crossing.x;
y = _pick_event.crossing.y;
} else {
x = _pick_event.motion.x;
y = _pick_event.motion.y;
}
// world coords
x += _x0;
y += _y0;
// find the closest item
} else {
}
} else {
}
return retval; // current item did not change
}
// Synthesize events for old and new current items
if ((_new_current_item != _current_item) &&
{
_in_repick = TRUE;
_in_repick = FALSE;
}
if (_gen_all_enter_events == false) {
// new_current_item may have been set to NULL during the call to
// emitEvent() above
return retval;
}
}
// Handle the rest of cases
if (_current_item != NULL) {
}
return retval;
}
{
// dispatch normally regardless of the event's window if an item
// has a pointer grab in effect
if (!canvas->_grabbed_item &&
return retval;
int mask;
case 1:
break;
case 2:
break;
case 3:
break;
case 4:
break;
case 5:
break;
default:
mask = 0;
}
case GDK_BUTTON_PRESS:
case GDK_2BUTTON_PRESS:
case GDK_3BUTTON_PRESS:
// Pick the current item as if the button were not pressed, and
// then process the event.
//
break;
case GDK_BUTTON_RELEASE:
// Process the event as if the button were pressed, then repick
// after the button has been released
//
break;
default:
}
return retval;
}
{
}
#if GTK_CHECK_VERSION(3,0,0)
#else
#endif
}
{
int status;
return FALSE;
}
return FALSE;
}
return status;
}
void SPCanvas::paintSingleBuffer(Geom::IntRect const &paint_rect, Geom::IntRect const &canvas_rect, int /*sw*/)
{
buf.buf_rowstride = 0;
// create temporary surface
cairo_surface_t *imgs = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, paint_rect.width(), paint_rect.height());
}
// output to X
#if defined(HAVE_LIBLCMS1) || defined(HAVE_LIBLCMS2)
if (_enable_cms_display_adj) {
if ( fromDisplay ) {
} else {
}
if (transf) {
for (int i=0; i<paint_rect.height(); ++i) {
}
}
}
#endif // defined(HAVE_LIBLCMS1) || defined(HAVE_LIBLCMS2)
//cairo_t *xct = gdk_cairo_create(gtk_widget_get_window (widget));
// Mark the painted rectangle clean
markRect(paint_rect, 0);
}
struct PaintRectSetup {
int max_pixels;
};
{
// 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 (_forced_redraw_limit < 0 ||
if (_forced_redraw_limit != -1) {
}
return false;
}
}
// Find the optimal buffer dimensions
return 0;
// We are small enough
/*
GdkRectangle r;
r.x = this_rect.x0 - setup->canvas->x0;
r.y = this_rect.y0 - setup->canvas->y0;
r.width = this_rect.x1 - this_rect.x0;
r.height = this_rect.y1 - this_rect.y0;
GdkWindow *window = gtk_widget_get_window(GTK_WIDGET(setup->canvas));
gdk_window_begin_paint_rect(window, &r);
*/
//gdk_window_end_paint(window);
return 1;
}
/*
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.
*/
// Make sure that mid lies on a tile boundary
// Always paint towards the mouse first
} else {
}
} else {
// Make sure that mid lies on a tile boundary
// Always paint towards the mouse first
} else {
}
}
}
{
g_return_val_if_fail (!_need_update, false);
paint_rect = *area;
// Save the mouse location
gint x, y;
#if GTK_CHECK_VERSION(3,0,0)
&x, &y, NULL);
#else
#endif
// use 256K as a compromise to not slow down gradients
// 256K is the cached buffer and we need 4 channels
} else {
// paths only, so 1M works faster
// 1M is the cached buffer and we need 4 channels
}
// Start the clock
// Go
}
{
_forced_redraw_count = 0;
}
{
_forced_redraw_limit = -1;
}
// Blit from the backing store, without regard for the clean region.
// This is necessary because GTK clears the widget for us, which causes
// severe flicker while drawing if we don't blit the old contents.
for (int i = 0; i < rects->num_rectangles; i++) {
}
// Render the dirty portion in the background
if (!cairo_region_is_empty(dirty_region)) {
}
return TRUE;
}
#if !GTK_CHECK_VERSION(3,0,0)
{
cairo_clip (cr);
cairo_destroy (cr);
return result;
}
#endif
{
}
{
return FALSE;
}
}
{
if (canvas->_focused_item) {
} else {
return FALSE;
}
}
{
if (canvas->_focused_item) {
} else {
return FALSE;
}
}
{
if (_need_update) {
}
for (int i = 0; i < n_rects; ++i) {
// Aborted
return FALSE;
};
}
// we've had a full unaborted redraw, reset the full redraw counter
if (_forced_redraw_limit != -1) {
_forced_redraw_count = 0;
}
return TRUE;
}
{
if (!_root) { // canvas may have already be destroyed by closing desktop during interrupted display!
return TRUE;
}
if (_drawing_disabled) {
return TRUE;
}
// Cause the update if necessary
if (_need_update) {
}
// Paint if able to
if (gtk_widget_is_drawable(GTK_WIDGET(this))) {
return paint();
}
// Pick new current item
while (_need_repick) {
}
return TRUE;
}
{
if (ret) {
// Reset idle id
}
return !ret;
}
{
if (_idle_id == 0) {
}
}
{
if (_idle_id) {
_idle_id = 0;
}
}
{
return SP_CANVAS_GROUP(_root);
}
{
// adjust backing store contents
// Paint the background
// Copy the old backing store contents
cairo_clip(cr);
// Adjust the clean region
if (clear) {
dirtyAll();
} else {
}
}
if (!clear) {
// scrolling without zoom; redraw only the newly exposed areas
if (gtk_widget_get_realized(GTK_WIDGET(this))) {
}
}
}
addIdle();
}
{
if (_need_update) {
doUpdate();
}
}
{
_need_update = TRUE;
addIdle();
}
{
if (!gtk_widget_is_drawable( GTK_WIDGET(this) )) {
return;
}
return;
}
addIdle();
}
if (!_background_is_checkerboard) {
}
if (_background) {
}
_background_is_checkerboard = false;
dirtyAll();
addIdle();
}
if (_background_is_checkerboard) return;
if (_background) {
}
_background_is_checkerboard = true;
dirtyAll();
addIdle();
}
/**
* Sets world coordinates from win and canvas.
*/
void sp_canvas_window_to_world(SPCanvas const *canvas, double winx, double winy, double *worldx, double *worldy)
{
}
/**
* Sets win coordinates from world and canvas.
*/
void sp_canvas_world_to_window(SPCanvas const *canvas, double worldx, double worldy, double *winx, double *winy)
{
}
/**
* Converts point from win to world coordinates.
*/
{
}
/**
* Converts point from world to win coordinates.
*/
{
}
/**
* Returns true if point given in world coordinates is inside window.
*/
{
}
/**
* Return canvas window coordinates as Geom::Rect.
*/
{
}
/**
* Return canvas window coordinates as integer rectangle.
*/
{
return ret;
}
inline int sp_canvas_tile_floor(int x)
{
}
inline int sp_canvas_tile_ceil(int x)
{
}
}
}
}
{
if (val) {
} else {
}
}
/*
Local Variables:
mode:c++
c-file-style:"stroustrup"
c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
indent-tabs-mode:nil
fill-column:99
End:
*/
// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :