sp-canvas.cpp revision 95a7a222debe11414fb889a8929dfa89372a7174
#define __SP_CANVAS_C__
/** \file
* 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
*
* Copyright (C) 1998 The Free Software Foundation
* Copyright (C) 2002-2006 authors
*
* Released under GNU GPL, read the file 'COPYING' for more information
*/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include <libnr/nr-pixblock.h>
#include <gtk/gtksignal.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>
#include "prefs-utils.h"
enum {
};
enum {
};
/**
* A group of Items.
*/
struct SPCanvasGroup {
};
/**
* The SPCanvasGroup vtable.
*/
struct SPCanvasGroupClass {
};
/**
* The SPCanvas vtable.
*/
struct SPCanvasClass {
};
/* SPCanvasItem */
enum {ITEM_EVENT, ITEM_LAST_SIGNAL};
static void sp_canvas_item_construct (SPCanvasItem *item, SPCanvasGroup *parent, const gchar *first_arg_name, va_list args);
static GtkObjectClass *item_parent_class;
/**
* Registers the SPCanvasItem class with Glib and returns its type number.
*/
sp_canvas_item_get_type (void)
{
if (!type) {
sizeof (SPCanvasItemClass),
sizeof (SPCanvasItem),
0,
};
}
return type;
}
/**
* Initializes the SPCanvasItem vtable and the "event" signal.
*/
static void
{
/* fixme: Derive from GObject */
G_TYPE_BOOLEAN, 1,
}
/**
* Callback for initialization of SPCanvasItem.
*/
static void
{
}
/**
* Constructs new SPCanvasItem on SPCanvasGroup.
*/
{
return item;
}
/**
* Sets up the newly created SPCanvasItem.
*
* 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_request_redraw (item->canvas, (int)(item->x1), (int)(item->y1), (int)(item->x2 + 1), (int)(item->y2 + 1));
}
/**
* Helper function that requests redraw only if item's visible flag is set.
*/
static void
{
sp_canvas_request_redraw (item->canvas, (int)(item->x1), (int)(item->y1), (int)(item->x2 + 1), (int)(item->y2 + 1));
}
}
/**
* Callback that removes item from all referers and destroys it.
*/
static void
{
}
}
}
}
}
/**
* Helper function to update item and its children.
*
* NB! affine is parent2canvas.
*/
static void
{
/* Apply the child item's transform */
/* apply object flags to child flags */
}
}
/**
* 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
{
return NR_HUGE;
}
/**
* Makes the item's affine transformation matrix be equal to the specified
* matrix.
*
* @item: A canvas item.
* @affine: An affine transformation matrix.
*/
void
{
} else {
}
}
}
/**
* Convenience function to reorder items in a group's child list.
*
* This puts the specified link after the "before" link.
*/
static void
{
return;
} else {
}
} else {
return;
else {
}
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.
*/
void
{
g_return_if_fail (positions >= 0);
return;
if (!before)
}
/**
* 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.
**/
void
{
return;
else
}
/**
* Sets visible flag on item and requests a redraw.
*/
void
{
return;
sp_canvas_request_redraw (item->canvas, (int)(item->x1), (int)(item->y1), (int)(item->x2 + 1), (int)(item->y2 + 1));
}
/**
* Clears visible flag on item and requests a redraw.
*/
void
{
return;
sp_canvas_request_redraw (item->canvas, (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
*/
int
{
return -1;
return -1;
/* 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 */
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.
*/
void
{
return;
}
/**
* Returns the product of all transformation matrices from the root item down
* to the item.
*/
{
while (item) {
}
return affine;
}
/**
* Helper that returns true iff item is descendant of parent.
*/
{
while (item) {
return true;
}
return false;
}
/**
* Focus canvas, and item under cursor if it is not already focussed.
*/
void
{
if (focused_item) {
}
if (focused_item) {
}
}
/**
* Requests that the canvas queue an update for the specified item.
*
* To be used only by item implementations.
*/
void
{
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.
*/
{
}
/* SPCanvasGroup */
static void sp_canvas_group_update (SPCanvasItem *item, NR::Matrix const &affine, unsigned int flags);
static SPCanvasItemClass *group_parent_class;
/**
* Registers SPCanvasGroup class with Gtk and returns its type number.
*/
sp_canvas_group_get_type (void)
{
static GtkType group_type = 0;
if (!group_type) {
static const GtkTypeInfo group_info = {
"SPCanvasGroup",
sizeof (SPCanvasGroup),
sizeof (SPCanvasGroupClass),
};
}
return group_type;
}
/**
* Class initialization function for SPCanvasGroupClass
*/
static void
{
}
/**
* Callback. Empty.
*/
static void
{
/* Nothing here */
}
/**
* Callback that destroys all items in group and calls group's virtual
* destroy() function.
*/
static void
{
while (list) {
}
}
/**
* Update handler for canvas groups
*/
static void
{
bool empty=true;
if (empty) {
empty = false;
} else {
}
}
}
}
/**
* Point handler for canvas groups.
*/
static double
{
const double x = p[NR::X];
const double y = p[NR::Y];
double best = 0.0;
*actual_item = NULL;
double dist = 0.0;
int has_point;
} else
}
}
}
return best;
}
/**
* Renders all visible canvas group items in buf rectangle.
*/
static void
{
}
}
}
}
/**
* Adds an item to a canvas group.
*/
static void
{
} else {
}
}
/**
* Removes an item from a canvas group
*/
static void
{
/* Unparent the child */
/* Remove it from the list */
break;
}
}
}
/* SPCanvas */
static GtkWidgetClass *canvas_parent_class;
/**
* Registers the SPCanvas class if necessary, and returns the type ID
* associated to it.
*
* \return The type ID of the SPCanvas class.
**/
sp_canvas_get_type (void)
{
static GtkType canvas_type = 0;
if (!canvas_type) {
static const GtkTypeInfo canvas_info = {
"SPCanvas",
sizeof (SPCanvas),
sizeof (SPCanvasClass),
};
}
return canvas_type;
}
/**
* Class initialization function for SPCanvasClass.
*/
static void
{
}
/**
* Callback: object initialization for SPCanvas.
*/
static void
{
/* Create the root item as a special case */
// See comment at in sp-canvas.h.
canvas->gen_all_enter_events = false;
canvas->redraw_count = 0;
canvas->forced_redraw_count = 0;
canvas->slowest_buffer = 0;
}
/**
* Convenience function to remove the idle handler of a canvas.
*/
static void
{
}
}
/*
* Removes the transient state of the canvas (idle handler, grabs).
*/
static void
{
/* We turn off the need_redraw flag, since if the canvas is mapped again
* it will request a redraw anyways. We do not turn off the need_update
* flag, though, because updates are not queued when the canvas remaps
* itself.
*/
if (canvas->need_redraw) {
}
if (canvas->grabbed_item) {
}
}
/**
* Destroy handler for SPCanvas.
*/
static void
{
}
}
/**
* Returns new canvas as widget.
*/
sp_canvas_new_aa (void)
{
}
/**
* The canvas widget's realize callback.
*/
static void
{
widget->window = gdk_window_new (gtk_widget_get_parent_window (widget), &attributes, attributes_mask);
}
/**
* The canvas widget's unrealize callback.
*/
static void
{
}
/**
* The canvas widget's size_request callback.
*/
static void
{
}
/**
* The canvas widget's size_allocate callback.
*/
static void
{
/* Schedule redraw of new region */
sp_canvas_resize_tiles(canvas,canvas->x0,canvas->y0,canvas->x0+allocation->width,canvas->y0+allocation->height);
0,
}
0,
}
if (GTK_WIDGET_REALIZED (widget)) {
}
}
/**
* 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
{
if (canvas->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:
mask = GDK_SCROLL;
break;
default:
mask = 0;
break;
}
}
/* Convert to world coordinates -- we have two cases because of diferent
* 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;
}
/* 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 (canvas->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.
*/
}
return finished;
}
/**
* Helper that re-picks the current item in the canvas, based on the event's
*/
static int
{
int button_down = 0;
double x, y;
if (canvas->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.
*/
/* 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 {
}
}
/* Don't do anything else if this is a recursive call */
/* 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 */
} else {
}
/* world coords */
/* find the closest item */
} else {
}
} else {
}
return retval; /* current item did not change */
}
/* Synthesize events for old and new current items */
&& !canvas->left_grabbed_item) {
}
if (canvas->gen_all_enter_events == false) {
// new_current_item may have been set to NULL during the call to
// emit_event() above
return retval;
}
}
/* Handle the rest of cases */
}
return retval;
}
/**
* Button event handler for the canvas.
*/
static gint
{
/* dispatch normally regardless of the event's window if an item has
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;
}
/**
* Scroll event handler for the canvas.
*
* \todo FIXME: generate motion events to re-select items.
*/
static gint
{
}
/**
* Motion event handler for the canvas.
*/
static int
{
return FALSE;
gint x, y;
event->x = x;
event->y = y;
}
}
static void
sp_canvas_paint_single_buffer (SPCanvas *canvas, int x0, int y0, int x1, int y1, int draw_x1, int draw_y1, int draw_x2, int draw_y2, int sw)
{
} else {
}
}
TRUE,
} else {
sw * 3,
}
} else {
}
}
/* 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)
*/
static int
sp_canvas_paint_rect_internal (SPCanvas *canvas, NRRectL *rect, NR::ICoord *x_aborted_limit, NR::ICoord *y_aborted_limit)
{
// Here we'll store the time it took to draw the slowest buffer of this paint.
glong slowest_buffer = 0;
// Find the optimal buffer dimensions
return 0;
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 */
// We can go with single buffer
// Go with row buffer
// Go with column buffer
} else {
sw = 341;
sh = 256;
}
} else { // paths only, so 1M works faster
/* 1M is the cached buffer and we need 3 channels */
// We can go with single buffer
// Go with row buffer
// Go with column buffer
} else {
sw = 682;
sh = 512;
}
}
// Will this paint require more than one buffer?
// remember the counter during this paint
// Time values to measure each buffer's paint time
// This is the main loop which corresponds to the visible left-to-right, top-to-bottom drawing
// of screen blocks (buffers).
// 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.
// 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)
// After each successful buffer, reduce the rect remaining to redraw by what is already redrawn
// 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
}
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;
// If one of the iterations has redrawn by itself, abort
}
return 1; // interrupted
}
}
// If not aborted so far, check if the events set redraw or update flags;
// if so, force update and abort
}
return 1; // interrupted
}
}
}
}
// Remember the slowest buffer of this paint in canvas
return 0; // finished
}
/**
* Helper that draws a specific rectangular part of the canvas.
*/
static void
{
// 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++;
// Clip rect-to-draw by the current visible area
// Clip rect-aborted-last-time by the current visible area
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)
// calculate the rectangle union of the both rects (the smallest rectangle which covers both)
// subtract one of the rects-to-draw from the other (the smallest rectangle which covers
// all of the first not covered by the second)
// Initially, the rect to redraw later (in case we're aborted) is the same as the union of both rects
// 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
} else {
}
} else {
}
// paint the first rect;
if (sp_canvas_paint_rect_internal (canvas, first, &(second_minus_first.x0), &(second_minus_first.y0))) {
// aborted!
return;
}
// if not aborted, assign (second rect minus first) as the new redraw_aborted and paint the same
return; // aborted
}
} else {
// no need for separate drawing, just draw the union as one rect
return; // aborted
}
}
} 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.
return; // aborted
}
}
// we've had a full unaborted redraw, reset the full redraw counter
canvas->forced_redraw_count = 0;
}
}
/**
* Force a full redraw after a specified number of interrupted redraws
*/
void
canvas->forced_redraw_count = 0;
}
/**
* End forced full redraw requests
*/
void
}
/**
* The canvas widget's expose callback.
*/
static gint
{
if (!GTK_WIDGET_DRAWABLE (widget) ||
return FALSE;
int n_rects;
for (int i = 0; i < n_rects; i++) {
}
if (n_rects > 0)
return FALSE;
}
/**
* The canvas widget's keypress callback.
*/
static gint
{
}
/**
* Crossing event handler for the canvas.
*/
static gint
{
return FALSE;
}
/**
* Focus in handler for the canvas.
*/
static gint
{
if (canvas->focused_item) {
} else {
return FALSE;
}
}
/**
* Focus out handler for the canvas.
*/
static gint
{
if (canvas->focused_item)
else
return FALSE;
}
/**
* Helper that repaints the areas in the canvas that need it.
*/
static int
{
if (canvas->need_update) {
}
if (!canvas->need_redraw)
return TRUE;
int mode=0;
}
}
}
if ( mode ) {
}
}
}
}
return TRUE;
}
/**
* Helper that invokes update, paint, and repick on canvas.
*/
static int
{
if (!canvas->root) // canvas may have already be destroyed by closing desktop durring interrupted display!
return TRUE;
/* Cause the update if necessary */
if (canvas->need_update) {
}
/* Paint if able to */
if (GTK_WIDGET_DRAWABLE (canvas)) {
}
/* Pick new current item */
while (canvas->need_repick) {
}
return TRUE;
}
/**
* Idle handler for the canvas that deals with pending updates and redraws.
*/
static gint
{
if (ret) {
/* Reset idle id */
}
return !ret;
}
/**
* Convenience function to add an idle handler to a canvas.
*/
static void
{
return;
}
/**
* Returns the root group of the specified canvas.
*/
{
}
/**
* Scrolls canvas to specific position.
*/
void
{
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 (GTK_WIDGET_REALIZED (canvas)) {
}
if (dx < 0) {
} else if (dx > 0) {
}
if (dy < 0) {
} else if (dy > 0) {
}
}
} else {
// scrolling as part of zoom; do nothing here - the next do_update will perform full redraw
}
}
/**
* Updates canvas if necessary.
*/
void
{
if (!(canvas->need_update ||
return;
}
/**
* Update callback for canvas widget.
*/
static void
{
}
/**
* Forces redraw of rectangular canvas area.
*/
void
{
if (!GTK_WIDGET_DRAWABLE (canvas)) return;
}
/**
* 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.
*/
{
using NR::X;
using NR::Y;
}
/**
* Return canvas window coordinates as NRRect.
*/
{
GtkWidget const *w = GTK_WIDGET(this);
}
inline int sp_canvas_tile_floor(int x)
{
return (x&(~31))/32;
}
inline int sp_canvas_tile_ceil(int x)
{
return ((x+31)&(~31))/32;
}
/**
* Helper that changes tile size for canvas redraw.
*/
{
return;
}
} else {
}
}
}
}
/**
* Helper that marks specific canvas rectangle for redraw.
*/
{
return;
}
if ( tl >= canvas->tRight || tr <= canvas->tLeft || tt >= canvas->tBottom || tb <= canvas->tTop ) return;
}
}
}
/*
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 :