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>
25 enum {
26 ARENA_EVENT,
27 LAST_SIGNAL
28 };
30 static void sp_canvas_arena_class_init(SPCanvasArenaClass *klass);
31 static void sp_canvas_arena_init(SPCanvasArena *group);
32 static void sp_canvas_arena_destroy(GtkObject *object);
34 static void sp_canvas_arena_update (SPCanvasItem *item, NR::Matrix const &affine, unsigned int flags);
35 static void sp_canvas_arena_render (SPCanvasItem *item, SPCanvasBuf *buf);
36 static double sp_canvas_arena_point (SPCanvasItem *item, NR::Point p, SPCanvasItem **actual_item);
37 static gint sp_canvas_arena_event (SPCanvasItem *item, GdkEvent *event);
39 static gint sp_canvas_arena_send_event (SPCanvasArena *arena, GdkEvent *event);
41 static void sp_canvas_arena_request_update (NRArena *arena, NRArenaItem *item, void *data);
42 static void sp_canvas_arena_request_render (NRArena *arena, NRRectL *area, void *data);
44 NRArenaEventVector carenaev = {
45 {NULL},
46 sp_canvas_arena_request_update,
47 sp_canvas_arena_request_render
48 };
50 static SPCanvasItemClass *parent_class;
51 static guint signals[LAST_SIGNAL] = {0};
53 GtkType
54 sp_canvas_arena_get_type (void)
55 {
56 static GtkType type = 0;
57 if (!type) {
58 GtkTypeInfo info = {
59 "SPCanvasArena",
60 sizeof (SPCanvasArena),
61 sizeof (SPCanvasArenaClass),
62 (GtkClassInitFunc) sp_canvas_arena_class_init,
63 (GtkObjectInitFunc) sp_canvas_arena_init,
64 NULL, NULL, NULL
65 };
66 type = gtk_type_unique (SP_TYPE_CANVAS_ITEM, &info);
67 }
68 return type;
69 }
71 static void
72 sp_canvas_arena_class_init (SPCanvasArenaClass *klass)
73 {
74 GtkObjectClass *object_class;
75 SPCanvasItemClass *item_class;
77 object_class = (GtkObjectClass *) klass;
78 item_class = (SPCanvasItemClass *) klass;
80 parent_class = (SPCanvasItemClass*)gtk_type_class (SP_TYPE_CANVAS_ITEM);
82 signals[ARENA_EVENT] = gtk_signal_new ("arena_event",
83 GTK_RUN_LAST,
84 GTK_CLASS_TYPE(object_class),
85 GTK_SIGNAL_OFFSET (SPCanvasArenaClass, arena_event),
86 sp_marshal_INT__POINTER_POINTER,
87 GTK_TYPE_INT, 2, GTK_TYPE_POINTER, GTK_TYPE_POINTER);
89 object_class->destroy = sp_canvas_arena_destroy;
91 item_class->update = sp_canvas_arena_update;
92 item_class->render = sp_canvas_arena_render;
93 item_class->point = sp_canvas_arena_point;
94 item_class->event = sp_canvas_arena_event;
95 }
97 static void
98 sp_canvas_arena_init (SPCanvasArena *arena)
99 {
100 arena->sticky = FALSE;
102 arena->arena = NRArena::create();
103 arena->root = NRArenaGroup::create(arena->arena);
104 nr_arena_group_set_transparent (NR_ARENA_GROUP (arena->root), TRUE);
106 arena->active = NULL;
108 nr_active_object_add_listener ((NRActiveObject *) arena->arena, (NRObjectEventVector *) &carenaev, sizeof (carenaev), arena);
109 }
111 static void
112 sp_canvas_arena_destroy (GtkObject *object)
113 {
114 SPCanvasArena *arena = SP_CANVAS_ARENA (object);
116 if (arena->active) {
117 nr_object_unref ((NRObject *) arena->active);
118 arena->active = NULL;
119 }
121 if (arena->root) {
122 nr_arena_item_unref (arena->root);
123 arena->root = NULL;
124 }
126 if (arena->arena) {
127 nr_active_object_remove_listener_by_data ((NRActiveObject *) arena->arena, arena);
129 nr_object_unref ((NRObject *) arena->arena);
130 arena->arena = NULL;
131 }
133 if (GTK_OBJECT_CLASS (parent_class)->destroy)
134 (* GTK_OBJECT_CLASS (parent_class)->destroy) (object);
135 }
137 static void
138 sp_canvas_arena_update (SPCanvasItem *item, NR::Matrix const &affine, unsigned int flags)
139 {
140 SPCanvasArena *arena = SP_CANVAS_ARENA (item);
142 if (((SPCanvasItemClass *) parent_class)->update)
143 (* ((SPCanvasItemClass *) parent_class)->update) (item, affine, flags);
145 arena->gc.transform = affine;
147 guint reset;
148 reset = (flags & SP_CANVAS_UPDATE_AFFINE)? NR_ARENA_ITEM_STATE_ALL : NR_ARENA_ITEM_STATE_NONE;
150 nr_arena_item_invoke_update (arena->root, NULL, &arena->gc, NR_ARENA_ITEM_STATE_ALL, reset);
152 item->x1 = arena->root->bbox.x0 - 1;
153 item->y1 = arena->root->bbox.y0 - 1;
154 item->x2 = arena->root->bbox.x1 + 1;
155 item->y2 = arena->root->bbox.y1 + 1;
157 if (arena->cursor) {
158 /* Mess with enter/leave notifiers */
159 NRArenaItem *new_arena = nr_arena_item_invoke_pick (arena->root, arena->c, arena->arena->delta, arena->sticky);
160 if (new_arena != arena->active) {
161 GdkEventCrossing ec;
162 ec.window = GTK_WIDGET (item->canvas)->window;
163 ec.send_event = TRUE;
164 ec.subwindow = ec.window;
165 ec.time = GDK_CURRENT_TIME;
166 ec.x = arena->c[NR::X];
167 ec.y = arena->c[NR::Y];
168 /* fixme: */
169 if (arena->active) {
170 ec.type = GDK_LEAVE_NOTIFY;
171 sp_canvas_arena_send_event (arena, (GdkEvent *) &ec);
172 }
173 /* fixme: This is not optimal - better track ::destroy (Lauris) */
174 if (arena->active) nr_object_unref ((NRObject *) arena->active);
175 arena->active = new_arena;
176 if (arena->active) nr_object_ref ((NRObject *) arena->active);
177 if (arena->active) {
178 ec.type = GDK_ENTER_NOTIFY;
179 sp_canvas_arena_send_event (arena, (GdkEvent *) &ec);
180 }
181 }
182 }
183 }
185 static void
186 sp_canvas_arena_render (SPCanvasItem *item, SPCanvasBuf *buf)
187 {
188 gint bw, bh, sw, sh;
189 gint x, y;
191 SPCanvasArena *arena = SP_CANVAS_ARENA (item);
193 nr_arena_item_invoke_update (arena->root, NULL, &arena->gc,
194 NR_ARENA_ITEM_STATE_BBOX | NR_ARENA_ITEM_STATE_RENDER,
195 NR_ARENA_ITEM_STATE_NONE);
197 sp_canvas_prepare_buffer(buf);
199 bw = buf->rect.x1 - buf->rect.x0;
200 bh = buf->rect.y1 - buf->rect.y0;
201 if ((bw < 1) || (bh < 1)) return;
203 // FIXME: currently this function is a huge waste. It receives a buffer but creates a new one and loops
204 // within the large one, doing arena painting in several blocks. This just makes no sense because the
205 // buf that we are given is already only a strip of the screen, created by one iteration of a loop in
206 // sp_canvas_paint_rect_internal. With the current numbers, this function's buffer is always 1/4
207 // smaller than the one we get, because they both are the same number of bytes but
208 // buf uses 3 bytes per pixel (24bpp, packed) while the pixblock created here uses 4 bytes (32bpp).
209 // Eventually I want to switch buf to using 4 bytes (see comment in canvas.cpp) and then remove
210 // from here the sw/sh calculation, the loop, and creating the intermediate buffer, allowing arena
211 // just render into buf in one go.
213 if (arena->arena->rendermode != RENDERMODE_OUTLINE) { // use 256K as a compromise to not slow down gradients
214 /* 256K is the cached buffer and we need 4 channels */
215 if (bw * bh < 65536) { // 256K/4
216 /* We can go with single buffer */
217 sw = bw;
218 sh = bh;
219 } else if (bw <= 4096) {
220 /* Go with row buffer */
221 sw = bw;
222 sh = 65536 / bw;
223 } else if (bh <= 4096) {
224 /* Go with column buffer */
225 sw = 65536 / bh;
226 sh = bh;
227 } else {
228 sw = 256;
229 sh = 256;
230 }
231 } else { // paths only, so 1M works faster
232 /* 1M is the cached buffer and we need 4 channels */
233 if (bw * bh < 262144) { // 1M/4
234 /* We can go with single buffer */
235 sw = bw;
236 sh = bh;
237 } else if (bw <= 8192) {
238 /* Go with row buffer */
239 sw = bw;
240 sh = 262144 / bw;
241 } else if (bh <= 8192) {
242 /* Go with column buffer */
243 sw = 262144 / bh;
244 sh = bh;
245 } else {
246 sw = 512;
247 sh = 512;
248 }
249 }
251 /*
252 This define chooses between two modes: When on, arena renders into a temporary
253 32bpp buffer, and the result is then squished into the SPCanvasBuf. When off, arena
254 renders directly to SPCanvasBuf. However currently this gives no speed advantage,
255 perhaps because the lack of squishing is offset by the need for arena items to render
256 to the inconvenient (and probably slower) 24bpp buffer. When SPCanvasBuf is
257 switched to 32bpp and cairo drawing, however, this define should be removed to
258 streamline rendering.
259 */
260 #define STRICT_RGBA
262 for (y = buf->rect.y0; y < buf->rect.y1; y += sh) {
263 for (x = buf->rect.x0; x < buf->rect.x1; x += sw) {
264 NRRectL area;
265 #ifdef STRICT_RGBA
266 NRPixBlock pb;
267 #endif
268 NRPixBlock cb;
270 area.x0 = x;
271 area.y0 = y;
272 area.x1 = MIN (x + sw, buf->rect.x1);
273 area.y1 = MIN (y + sh, buf->rect.y1);
275 #ifdef STRICT_RGBA
276 nr_pixblock_setup_fast (&pb, NR_PIXBLOCK_MODE_R8G8B8A8P, area.x0, area.y0, area.x1, area.y1, TRUE);
277 /* fixme: */
278 pb.empty = FALSE;
279 #endif
281 // CAIRO FIXME: switch this to R8G8B8A8P and 4 * ...
282 nr_pixblock_setup_extern (&cb, NR_PIXBLOCK_MODE_R8G8B8, area.x0, area.y0, area.x1, area.y1,
283 buf->buf + (y - buf->rect.y0) * buf->buf_rowstride + 3 * (x - buf->rect.x0),
284 buf->buf_rowstride,
285 FALSE, FALSE);
287 #ifdef STRICT_RGBA
288 pb.visible_area = buf->visible_rect;
289 if (pb.data.px != NULL) {
290 nr_arena_item_invoke_render (NULL, arena->root, &area, &pb, 0);
291 // this does the 32->24 squishing, using an assembler routine:
292 nr_blit_pixblock_pixblock (&cb, &pb);
293 }
294 nr_pixblock_release (&pb);
295 #else
296 cb.visible_area = buf->visible_rect;
297 nr_arena_item_invoke_render (NULL, arena->root, &area, &cb, 0);
298 #endif
300 nr_pixblock_release (&cb);
301 }
302 }
303 }
305 static double
306 sp_canvas_arena_point (SPCanvasItem *item, NR::Point p, SPCanvasItem **actual_item)
307 {
308 SPCanvasArena *arena = SP_CANVAS_ARENA (item);
310 nr_arena_item_invoke_update (arena->root, NULL, &arena->gc,
311 NR_ARENA_ITEM_STATE_BBOX | NR_ARENA_ITEM_STATE_PICK,
312 NR_ARENA_ITEM_STATE_NONE);
314 NRArenaItem *picked = nr_arena_item_invoke_pick (arena->root, p, arena->arena->delta, arena->sticky);
316 arena->picked = picked;
318 if (picked) {
319 *actual_item = item;
320 return 0.0;
321 }
323 return 1e18;
324 }
326 static gint
327 sp_canvas_arena_event (SPCanvasItem *item, GdkEvent *event)
328 {
329 NRArenaItem *new_arena;
330 /* fixme: This sucks, we have to handle enter/leave notifiers */
332 SPCanvasArena *arena = SP_CANVAS_ARENA (item);
334 gint ret = FALSE;
336 switch (event->type) {
337 case GDK_ENTER_NOTIFY:
338 if (!arena->cursor) {
339 if (arena->active) {
340 //g_warning ("Cursor entered to arena with already active item");
341 nr_object_unref ((NRObject *) arena->active);
342 }
343 arena->cursor = TRUE;
345 /* TODO ... event -> arena transform? */
346 arena->c = NR::Point(event->crossing.x, event->crossing.y);
348 /* fixme: Not sure abut this, but seems the right thing (Lauris) */
349 nr_arena_item_invoke_update (arena->root, NULL, &arena->gc, NR_ARENA_ITEM_STATE_PICK, NR_ARENA_ITEM_STATE_NONE);
350 arena->active = nr_arena_item_invoke_pick (arena->root, arena->c, arena->arena->delta, arena->sticky);
351 if (arena->active) nr_object_ref ((NRObject *) arena->active);
352 ret = sp_canvas_arena_send_event (arena, event);
353 }
354 break;
355 case GDK_LEAVE_NOTIFY:
356 if (arena->cursor) {
357 ret = sp_canvas_arena_send_event (arena, event);
358 if (arena->active) nr_object_unref ((NRObject *) arena->active);
359 arena->active = NULL;
360 arena->cursor = FALSE;
361 }
362 break;
363 case GDK_MOTION_NOTIFY:
364 /* TODO ... event -> arena transform? */
365 arena->c = NR::Point(event->motion.x, event->motion.y);
367 /* fixme: Not sure abut this, but seems the right thing (Lauris) */
368 nr_arena_item_invoke_update (arena->root, NULL, &arena->gc, NR_ARENA_ITEM_STATE_PICK, NR_ARENA_ITEM_STATE_NONE);
369 new_arena = nr_arena_item_invoke_pick (arena->root, arena->c, arena->arena->delta, arena->sticky);
370 if (new_arena != arena->active) {
371 GdkEventCrossing ec;
372 ec.window = event->motion.window;
373 ec.send_event = event->motion.send_event;
374 ec.subwindow = event->motion.window;
375 ec.time = event->motion.time;
376 ec.x = event->motion.x;
377 ec.y = event->motion.y;
378 /* fixme: */
379 if (arena->active) {
380 ec.type = GDK_LEAVE_NOTIFY;
381 ret = sp_canvas_arena_send_event (arena, (GdkEvent *) &ec);
382 }
383 if (arena->active) nr_object_unref ((NRObject *) arena->active);
384 arena->active = new_arena;
385 if (arena->active) nr_object_ref ((NRObject *) arena->active);
386 if (arena->active) {
387 ec.type = GDK_ENTER_NOTIFY;
388 ret = sp_canvas_arena_send_event (arena, (GdkEvent *) &ec);
389 }
390 }
391 ret = sp_canvas_arena_send_event (arena, event);
392 break;
393 default:
394 /* Just send event */
395 ret = sp_canvas_arena_send_event (arena, event);
396 break;
397 }
399 return ret;
400 }
402 static gint
403 sp_canvas_arena_send_event (SPCanvasArena *arena, GdkEvent *event)
404 {
405 gint ret = FALSE;
407 /* Send event to arena */
408 gtk_signal_emit (GTK_OBJECT (arena), signals[ARENA_EVENT], arena->active, event, &ret);
410 return ret;
411 }
413 static void
414 sp_canvas_arena_request_update (NRArena *arena, NRArenaItem *item, void *data)
415 {
416 sp_canvas_item_request_update (SP_CANVAS_ITEM (data));
417 }
419 static void
420 sp_canvas_arena_request_render (NRArena *arena, NRRectL *area, void *data)
421 {
422 sp_canvas_request_redraw (SP_CANVAS_ITEM (data)->canvas, area->x0, area->y0, area->x1, area->y1);
423 }
425 void
426 sp_canvas_arena_set_pick_delta (SPCanvasArena *ca, gdouble delta)
427 {
428 g_return_if_fail (ca != NULL);
429 g_return_if_fail (SP_IS_CANVAS_ARENA (ca));
431 /* fixme: repick? */
432 ca->delta = delta;
433 }
435 void
436 sp_canvas_arena_set_sticky (SPCanvasArena *ca, gboolean sticky)
437 {
438 g_return_if_fail (ca != NULL);
439 g_return_if_fail (SP_IS_CANVAS_ARENA (ca));
441 /* fixme: repick? */
442 ca->sticky = sticky;
443 }
445 void
446 sp_canvas_arena_render_pixblock (SPCanvasArena *ca, NRPixBlock *pb)
447 {
448 NRRectL area;
450 g_return_if_fail (ca != NULL);
451 g_return_if_fail (SP_IS_CANVAS_ARENA (ca));
453 /* fixme: */
454 pb->empty = FALSE;
456 area.x0 = pb->area.x0;
457 area.y0 = pb->area.y0;
458 area.x1 = pb->area.x1;
459 area.y1 = pb->area.y1;
461 nr_arena_item_invoke_render (NULL, ca->root, &area, pb, 0);
462 }