Code

create/destroy the cairo_t for all items to render into (currently used only in outli...
[inkscape.git] / src / display / canvas-arena.cpp
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)
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);
112 static void
113 sp_canvas_arena_destroy (GtkObject *object)
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);
138 static void
139 sp_canvas_arena_update (SPCanvasItem *item, NR::Matrix const &affine, unsigned int flags)
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         }
186 static void
187 sp_canvas_arena_render (SPCanvasItem *item, SPCanvasBuf *buf)
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);
199   
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         }
318 static double
319 sp_canvas_arena_point (SPCanvasItem *item, NR::Point p, SPCanvasItem **actual_item)
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;
339 static gint
340 sp_canvas_arena_event (SPCanvasItem *item, GdkEvent *event)
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;
415 static gint
416 sp_canvas_arena_send_event (SPCanvasArena *arena, GdkEvent *event)
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;
426 static void
427 sp_canvas_arena_request_update (NRArena *arena, NRArenaItem *item, void *data)
429         sp_canvas_item_request_update (SP_CANVAS_ITEM (data));
432 static void
433 sp_canvas_arena_request_render (NRArena *arena, NRRectL *area, void *data)
435         sp_canvas_request_redraw (SP_CANVAS_ITEM (data)->canvas, area->x0, area->y0, area->x1, area->y1);
438 void
439 sp_canvas_arena_set_pick_delta (SPCanvasArena *ca, gdouble delta)
441         g_return_if_fail (ca != NULL);
442         g_return_if_fail (SP_IS_CANVAS_ARENA (ca));
444         /* fixme: repick? */
445         ca->delta = delta;
448 void
449 sp_canvas_arena_set_sticky (SPCanvasArena *ca, gboolean sticky)
451         g_return_if_fail (ca != NULL);
452         g_return_if_fail (SP_IS_CANVAS_ARENA (ca));
454         /* fixme: repick? */
455         ca->sticky = sticky;
458 void
459 sp_canvas_arena_render_pixblock (SPCanvasArena *ca, NRPixBlock *pb)
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);