1 #define __SP_CANVAS_ARENA_C__
3 /*
4 * RGBA display list system for inkscape
5 *
6 * Author:
7 * Lauris Kaplinski <lauris@kaplinski.com>
8 *
9 * Copyright (C) 2001-2002 Lauris Kaplinski
10 * Copyright (C) 2001 Ximian, Inc.
11 *
12 * Released under GNU GPL, read the file 'COPYING' for more information
13 */
15 #include <libnr/nr-blit.h>
16 #include <gtk/gtksignal.h>
18 #include <display/display-forward.h>
19 #include <display/sp-canvas-util.h>
20 #include <helper/sp-marshal.h>
21 #include <display/nr-arena.h>
22 #include <display/nr-arena-group.h>
23 #include <display/canvas-arena.h>
24 #include <display/inkscape-cairo.h>
26 enum {
27 ARENA_EVENT,
28 LAST_SIGNAL
29 };
31 static void sp_canvas_arena_class_init(SPCanvasArenaClass *klass);
32 static void sp_canvas_arena_init(SPCanvasArena *group);
33 static void sp_canvas_arena_destroy(GtkObject *object);
35 static void sp_canvas_arena_update (SPCanvasItem *item, NR::Matrix const &affine, unsigned int flags);
36 static void sp_canvas_arena_render (SPCanvasItem *item, SPCanvasBuf *buf);
37 static double sp_canvas_arena_point (SPCanvasItem *item, NR::Point p, SPCanvasItem **actual_item);
38 static gint sp_canvas_arena_event (SPCanvasItem *item, GdkEvent *event);
40 static gint sp_canvas_arena_send_event (SPCanvasArena *arena, GdkEvent *event);
42 static void sp_canvas_arena_request_update (NRArena *arena, NRArenaItem *item, void *data);
43 static void sp_canvas_arena_request_render (NRArena *arena, NRRectL *area, void *data);
45 NRArenaEventVector carenaev = {
46 {NULL},
47 sp_canvas_arena_request_update,
48 sp_canvas_arena_request_render
49 };
51 static SPCanvasItemClass *parent_class;
52 static guint signals[LAST_SIGNAL] = {0};
54 GtkType
55 sp_canvas_arena_get_type (void)
56 {
57 static GtkType type = 0;
58 if (!type) {
59 GtkTypeInfo info = {
60 "SPCanvasArena",
61 sizeof (SPCanvasArena),
62 sizeof (SPCanvasArenaClass),
63 (GtkClassInitFunc) sp_canvas_arena_class_init,
64 (GtkObjectInitFunc) sp_canvas_arena_init,
65 NULL, NULL, NULL
66 };
67 type = gtk_type_unique (SP_TYPE_CANVAS_ITEM, &info);
68 }
69 return type;
70 }
72 static void
73 sp_canvas_arena_class_init (SPCanvasArenaClass *klass)
74 {
75 GtkObjectClass *object_class;
76 SPCanvasItemClass *item_class;
78 object_class = (GtkObjectClass *) klass;
79 item_class = (SPCanvasItemClass *) klass;
81 parent_class = (SPCanvasItemClass*)gtk_type_class (SP_TYPE_CANVAS_ITEM);
83 signals[ARENA_EVENT] = gtk_signal_new ("arena_event",
84 GTK_RUN_LAST,
85 GTK_CLASS_TYPE(object_class),
86 GTK_SIGNAL_OFFSET (SPCanvasArenaClass, arena_event),
87 sp_marshal_INT__POINTER_POINTER,
88 GTK_TYPE_INT, 2, GTK_TYPE_POINTER, GTK_TYPE_POINTER);
90 object_class->destroy = sp_canvas_arena_destroy;
92 item_class->update = sp_canvas_arena_update;
93 item_class->render = sp_canvas_arena_render;
94 item_class->point = sp_canvas_arena_point;
95 item_class->event = sp_canvas_arena_event;
96 }
98 static void
99 sp_canvas_arena_init (SPCanvasArena *arena)
100 {
101 arena->sticky = FALSE;
103 arena->arena = NRArena::create();
104 arena->root = NRArenaGroup::create(arena->arena);
105 nr_arena_group_set_transparent (NR_ARENA_GROUP (arena->root), TRUE);
107 arena->active = NULL;
109 nr_active_object_add_listener ((NRActiveObject *) arena->arena, (NRObjectEventVector *) &carenaev, sizeof (carenaev), arena);
110 }
112 static void
113 sp_canvas_arena_destroy (GtkObject *object)
114 {
115 SPCanvasArena *arena = SP_CANVAS_ARENA (object);
117 if (arena->active) {
118 nr_object_unref ((NRObject *) arena->active);
119 arena->active = NULL;
120 }
122 if (arena->root) {
123 nr_arena_item_unref (arena->root);
124 arena->root = NULL;
125 }
127 if (arena->arena) {
128 nr_active_object_remove_listener_by_data ((NRActiveObject *) arena->arena, arena);
130 nr_object_unref ((NRObject *) arena->arena);
131 arena->arena = NULL;
132 }
134 if (GTK_OBJECT_CLASS (parent_class)->destroy)
135 (* GTK_OBJECT_CLASS (parent_class)->destroy) (object);
136 }
138 static void
139 sp_canvas_arena_update (SPCanvasItem *item, NR::Matrix const &affine, unsigned int flags)
140 {
141 SPCanvasArena *arena = SP_CANVAS_ARENA (item);
143 if (((SPCanvasItemClass *) parent_class)->update)
144 (* ((SPCanvasItemClass *) parent_class)->update) (item, affine, flags);
146 arena->gc.transform = affine;
148 guint reset;
149 reset = (flags & SP_CANVAS_UPDATE_AFFINE)? NR_ARENA_ITEM_STATE_ALL : NR_ARENA_ITEM_STATE_NONE;
151 nr_arena_item_invoke_update (arena->root, NULL, &arena->gc, NR_ARENA_ITEM_STATE_ALL, reset);
153 item->x1 = arena->root->bbox.x0 - 1;
154 item->y1 = arena->root->bbox.y0 - 1;
155 item->x2 = arena->root->bbox.x1 + 1;
156 item->y2 = arena->root->bbox.y1 + 1;
158 if (arena->cursor) {
159 /* Mess with enter/leave notifiers */
160 NRArenaItem *new_arena = nr_arena_item_invoke_pick (arena->root, arena->c, arena->arena->delta, arena->sticky);
161 if (new_arena != arena->active) {
162 GdkEventCrossing ec;
163 ec.window = GTK_WIDGET (item->canvas)->window;
164 ec.send_event = TRUE;
165 ec.subwindow = ec.window;
166 ec.time = GDK_CURRENT_TIME;
167 ec.x = arena->c[NR::X];
168 ec.y = arena->c[NR::Y];
169 /* fixme: */
170 if (arena->active) {
171 ec.type = GDK_LEAVE_NOTIFY;
172 sp_canvas_arena_send_event (arena, (GdkEvent *) &ec);
173 }
174 /* fixme: This is not optimal - better track ::destroy (Lauris) */
175 if (arena->active) nr_object_unref ((NRObject *) arena->active);
176 arena->active = new_arena;
177 if (arena->active) nr_object_ref ((NRObject *) arena->active);
178 if (arena->active) {
179 ec.type = GDK_ENTER_NOTIFY;
180 sp_canvas_arena_send_event (arena, (GdkEvent *) &ec);
181 }
182 }
183 }
184 }
186 static void
187 sp_canvas_arena_render (SPCanvasItem *item, SPCanvasBuf *buf)
188 {
189 gint bw, bh, sw, sh;
190 gint x, y;
192 SPCanvasArena *arena = SP_CANVAS_ARENA (item);
194 nr_arena_item_invoke_update (arena->root, NULL, &arena->gc,
195 NR_ARENA_ITEM_STATE_BBOX | NR_ARENA_ITEM_STATE_RENDER,
196 NR_ARENA_ITEM_STATE_NONE);
198 sp_canvas_prepare_buffer(buf);
200 bw = buf->rect.x1 - buf->rect.x0;
201 bh = buf->rect.y1 - buf->rect.y0;
202 if ((bw < 1) || (bh < 1)) return;
204 // FIXME: currently this function is a huge waste. It receives a buffer but creates a new one and loops
205 // within the large one, doing arena painting in several blocks. This just makes no sense because the
206 // buf that we are given is already only a strip of the screen, created by one iteration of a loop in
207 // sp_canvas_paint_rect_internal. With the current numbers, this function's buffer is always 1/4
208 // smaller than the one we get, because they both are the same number of bytes but
209 // buf uses 3 bytes per pixel (24bpp, packed) while the pixblock created here uses 4 bytes (32bpp).
210 // Eventually I want to switch buf to using 4 bytes (see comment in canvas.cpp) and then remove
211 // from here the sw/sh calculation, the loop, and creating the intermediate buffer, allowing arena
212 // just render into buf in one go.
214 if (arena->arena->rendermode != RENDERMODE_OUTLINE) { // use 256K as a compromise to not slow down gradients
215 /* 256K is the cached buffer and we need 4 channels */
216 if (bw * bh < 65536) { // 256K/4
217 /* We can go with single buffer */
218 sw = bw;
219 sh = bh;
220 } else if (bw <= 4096) {
221 /* Go with row buffer */
222 sw = bw;
223 sh = 65536 / bw;
224 } else if (bh <= 4096) {
225 /* Go with column buffer */
226 sw = 65536 / bh;
227 sh = bh;
228 } else {
229 sw = 256;
230 sh = 256;
231 }
232 } else { // paths only, so 1M works faster
233 /* 1M is the cached buffer and we need 4 channels */
234 if (bw * bh < 262144) { // 1M/4
235 /* We can go with single buffer */
236 sw = bw;
237 sh = bh;
238 } else if (bw <= 8192) {
239 /* Go with row buffer */
240 sw = bw;
241 sh = 262144 / bw;
242 } else if (bh <= 8192) {
243 /* Go with column buffer */
244 sw = 262144 / bh;
245 sh = bh;
246 } else {
247 sw = 512;
248 sh = 512;
249 }
250 }
252 /*
253 This define chooses between two modes: When on, arena renders into a temporary
254 32bpp buffer, and the result is then squished into the SPCanvasBuf. When off, arena
255 renders directly to SPCanvasBuf. However currently this gives no speed advantage,
256 perhaps because the lack of squishing is offset by the need for arena items to render
257 to the inconvenient (and probably slower) 24bpp buffer. When SPCanvasBuf is
258 switched to 32bpp and cairo drawing, however, this define should be removed to
259 streamline rendering.
260 */
261 #define STRICT_RGBA
263 for (y = buf->rect.y0; y < buf->rect.y1; y += sh) {
264 for (x = buf->rect.x0; x < buf->rect.x1; x += sw) {
265 NRRectL area;
266 #ifdef STRICT_RGBA
267 NRPixBlock pb;
268 #endif
269 NRPixBlock cb;
271 area.x0 = x;
272 area.y0 = y;
273 area.x1 = MIN (x + sw, buf->rect.x1);
274 area.y1 = MIN (y + sh, buf->rect.y1);
276 #ifdef STRICT_RGBA
277 nr_pixblock_setup_fast (&pb, NR_PIXBLOCK_MODE_R8G8B8A8P, area.x0, area.y0, area.x1, area.y1, TRUE);
278 #endif
280 // CAIRO FIXME: switch this to R8G8B8A8P and 4 * ...
281 nr_pixblock_setup_extern (&cb, NR_PIXBLOCK_MODE_R8G8B8, area.x0, area.y0, area.x1, area.y1,
282 buf->buf + (y - buf->rect.y0) * buf->buf_rowstride + 3 * (x - buf->rect.x0),
283 buf->buf_rowstride,
284 FALSE, FALSE);
286 #ifdef STRICT_RGBA
287 pb.visible_area = buf->visible_rect;
289 if (pb.data.px != NULL) {
290 cairo_t *ct = nr_create_cairo_context (&area, &pb);
292 nr_arena_item_invoke_render (ct, arena->root, &area, &pb, 0);
294 if (pb.empty == FALSE) {
297 // this does the 32->24 squishing, using an assembler routine:
298 nr_blit_pixblock_pixblock (&cb, &pb);
299 }
301 cairo_surface_t *cst = cairo_get_target(ct);
302 cairo_destroy (ct);
303 cairo_surface_finish (cst);
304 cairo_surface_destroy (cst);
305 }
307 nr_pixblock_release (&pb);
308 #else
309 cb.visible_area = buf->visible_rect;
310 nr_arena_item_invoke_render (NULL, arena->root, &area, &cb, 0);
311 #endif
313 nr_pixblock_release (&cb);
314 }
315 }
316 }
318 static double
319 sp_canvas_arena_point (SPCanvasItem *item, NR::Point p, SPCanvasItem **actual_item)
320 {
321 SPCanvasArena *arena = SP_CANVAS_ARENA (item);
323 nr_arena_item_invoke_update (arena->root, NULL, &arena->gc,
324 NR_ARENA_ITEM_STATE_BBOX | NR_ARENA_ITEM_STATE_PICK,
325 NR_ARENA_ITEM_STATE_NONE);
327 NRArenaItem *picked = nr_arena_item_invoke_pick (arena->root, p, arena->arena->delta, arena->sticky);
329 arena->picked = picked;
331 if (picked) {
332 *actual_item = item;
333 return 0.0;
334 }
336 return 1e18;
337 }
339 static gint
340 sp_canvas_arena_event (SPCanvasItem *item, GdkEvent *event)
341 {
342 NRArenaItem *new_arena;
343 /* fixme: This sucks, we have to handle enter/leave notifiers */
345 SPCanvasArena *arena = SP_CANVAS_ARENA (item);
347 gint ret = FALSE;
349 switch (event->type) {
350 case GDK_ENTER_NOTIFY:
351 if (!arena->cursor) {
352 if (arena->active) {
353 //g_warning ("Cursor entered to arena with already active item");
354 nr_object_unref ((NRObject *) arena->active);
355 }
356 arena->cursor = TRUE;
358 /* TODO ... event -> arena transform? */
359 arena->c = NR::Point(event->crossing.x, event->crossing.y);
361 /* fixme: Not sure abut this, but seems the right thing (Lauris) */
362 nr_arena_item_invoke_update (arena->root, NULL, &arena->gc, NR_ARENA_ITEM_STATE_PICK, NR_ARENA_ITEM_STATE_NONE);
363 arena->active = nr_arena_item_invoke_pick (arena->root, arena->c, arena->arena->delta, arena->sticky);
364 if (arena->active) nr_object_ref ((NRObject *) arena->active);
365 ret = sp_canvas_arena_send_event (arena, event);
366 }
367 break;
368 case GDK_LEAVE_NOTIFY:
369 if (arena->cursor) {
370 ret = sp_canvas_arena_send_event (arena, event);
371 if (arena->active) nr_object_unref ((NRObject *) arena->active);
372 arena->active = NULL;
373 arena->cursor = FALSE;
374 }
375 break;
376 case GDK_MOTION_NOTIFY:
377 /* TODO ... event -> arena transform? */
378 arena->c = NR::Point(event->motion.x, event->motion.y);
380 /* fixme: Not sure abut this, but seems the right thing (Lauris) */
381 nr_arena_item_invoke_update (arena->root, NULL, &arena->gc, NR_ARENA_ITEM_STATE_PICK, NR_ARENA_ITEM_STATE_NONE);
382 new_arena = nr_arena_item_invoke_pick (arena->root, arena->c, arena->arena->delta, arena->sticky);
383 if (new_arena != arena->active) {
384 GdkEventCrossing ec;
385 ec.window = event->motion.window;
386 ec.send_event = event->motion.send_event;
387 ec.subwindow = event->motion.window;
388 ec.time = event->motion.time;
389 ec.x = event->motion.x;
390 ec.y = event->motion.y;
391 /* fixme: */
392 if (arena->active) {
393 ec.type = GDK_LEAVE_NOTIFY;
394 ret = sp_canvas_arena_send_event (arena, (GdkEvent *) &ec);
395 }
396 if (arena->active) nr_object_unref ((NRObject *) arena->active);
397 arena->active = new_arena;
398 if (arena->active) nr_object_ref ((NRObject *) arena->active);
399 if (arena->active) {
400 ec.type = GDK_ENTER_NOTIFY;
401 ret = sp_canvas_arena_send_event (arena, (GdkEvent *) &ec);
402 }
403 }
404 ret = sp_canvas_arena_send_event (arena, event);
405 break;
406 default:
407 /* Just send event */
408 ret = sp_canvas_arena_send_event (arena, event);
409 break;
410 }
412 return ret;
413 }
415 static gint
416 sp_canvas_arena_send_event (SPCanvasArena *arena, GdkEvent *event)
417 {
418 gint ret = FALSE;
420 /* Send event to arena */
421 gtk_signal_emit (GTK_OBJECT (arena), signals[ARENA_EVENT], arena->active, event, &ret);
423 return ret;
424 }
426 static void
427 sp_canvas_arena_request_update (NRArena *arena, NRArenaItem *item, void *data)
428 {
429 sp_canvas_item_request_update (SP_CANVAS_ITEM (data));
430 }
432 static void
433 sp_canvas_arena_request_render (NRArena *arena, NRRectL *area, void *data)
434 {
435 sp_canvas_request_redraw (SP_CANVAS_ITEM (data)->canvas, area->x0, area->y0, area->x1, area->y1);
436 }
438 void
439 sp_canvas_arena_set_pick_delta (SPCanvasArena *ca, gdouble delta)
440 {
441 g_return_if_fail (ca != NULL);
442 g_return_if_fail (SP_IS_CANVAS_ARENA (ca));
444 /* fixme: repick? */
445 ca->delta = delta;
446 }
448 void
449 sp_canvas_arena_set_sticky (SPCanvasArena *ca, gboolean sticky)
450 {
451 g_return_if_fail (ca != NULL);
452 g_return_if_fail (SP_IS_CANVAS_ARENA (ca));
454 /* fixme: repick? */
455 ca->sticky = sticky;
456 }
458 void
459 sp_canvas_arena_render_pixblock (SPCanvasArena *ca, NRPixBlock *pb)
460 {
461 NRRectL area;
463 g_return_if_fail (ca != NULL);
464 g_return_if_fail (SP_IS_CANVAS_ARENA (ca));
466 /* fixme: */
467 pb->empty = FALSE;
469 area.x0 = pb->area.x0;
470 area.y0 = pb->area.y0;
471 area.x1 = pb->area.x1;
472 area.y1 = pb->area.y1;
474 nr_arena_item_invoke_render (NULL, ca->root, &area, pb, 0);
475 }