summaryrefslogtreecommitdiffstats
path: root/src/display/canvas-arena.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/display/canvas-arena.cpp')
-rw-r--r--src/display/canvas-arena.cpp385
1 files changed, 385 insertions, 0 deletions
diff --git a/src/display/canvas-arena.cpp b/src/display/canvas-arena.cpp
new file mode 100644
index 0000000..1ab617f
--- /dev/null
+++ b/src/display/canvas-arena.cpp
@@ -0,0 +1,385 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * RGBA display list system for inkscape
+ *
+ * Author:
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ * Jon A. Cruz <jon@joncruz.org>
+ *
+ * Copyright (C) 2001-2002 Lauris Kaplinski
+ * Copyright (C) 2001 Ximian, Inc.
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include <gtkmm.h>
+
+#include "display/sp-canvas-util.h"
+#include "helper/sp-marshal.h"
+#include "display/canvas-arena.h"
+#include "display/cairo-utils.h"
+#include "display/drawing-context.h"
+#include "display/drawing-item.h"
+#include "display/drawing-group.h"
+#include "display/drawing-surface.h"
+#include "preferences.h"
+
+using namespace Inkscape;
+
+enum {
+ ARENA_EVENT,
+ LAST_SIGNAL
+};
+
+static void sp_canvas_arena_destroy(SPCanvasItem *object);
+
+static void sp_canvas_arena_item_deleted(SPCanvasArena *arena, Inkscape::DrawingItem *item);
+static void sp_canvas_arena_update (SPCanvasItem *item, Geom::Affine const &affine, unsigned int flags);
+static void sp_canvas_arena_render (SPCanvasItem *item, SPCanvasBuf *buf);
+static double sp_canvas_arena_point (SPCanvasItem *item, Geom::Point p, SPCanvasItem **actual_item);
+static void sp_canvas_arena_viewbox_changed (SPCanvasItem *item, Geom::IntRect const &new_area);
+static gint sp_canvas_arena_event (SPCanvasItem *item, GdkEvent *event);
+
+static gint sp_canvas_arena_send_event (SPCanvasArena *arena, GdkEvent *event);
+
+static void sp_canvas_arena_request_update (SPCanvasArena *ca, DrawingItem *item);
+static void sp_canvas_arena_request_render (SPCanvasArena *ca, Geom::IntRect const &area);
+
+static guint signals[LAST_SIGNAL] = {0};
+
+struct CachePrefObserver : public Inkscape::Preferences::Observer {
+ CachePrefObserver(SPCanvasArena *arena)
+ : Inkscape::Preferences::Observer("/options/renderingcache")
+ , _arena(arena)
+ {
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ std::vector<Inkscape::Preferences::Entry> v = prefs->getAllEntries(observed_path);
+ for (const auto & i : v) {
+ notify(i);
+ }
+ prefs->addObserver(*this);
+ }
+ void notify(Preferences::Entry const &v) override {
+ Glib::ustring name = v.getEntryName();
+ if (name == "size") {
+ _arena->drawing.setCacheBudget((1 << 20) * v.getIntLimited(64, 0, 4096));
+ }
+ }
+ SPCanvasArena *_arena;
+};
+
+G_DEFINE_TYPE(SPCanvasArena, sp_canvas_arena, SP_TYPE_CANVAS_ITEM);
+
+static void
+sp_canvas_arena_class_init (SPCanvasArenaClass *klass)
+{
+ SPCanvasItemClass *item_class = (SPCanvasItemClass *) klass;
+
+ signals[ARENA_EVENT] = g_signal_new ("arena_event",
+ G_TYPE_FROM_CLASS(item_class),
+ G_SIGNAL_RUN_LAST,
+ ((glong)((guint8*)&(klass->arena_event) - (guint8*)klass)),
+ nullptr, nullptr,
+ sp_marshal_INT__POINTER_POINTER,
+ G_TYPE_INT, 2, G_TYPE_POINTER, G_TYPE_POINTER);
+
+ item_class->destroy = sp_canvas_arena_destroy;
+ item_class->update = sp_canvas_arena_update;
+ item_class->render = sp_canvas_arena_render;
+ item_class->point = sp_canvas_arena_point;
+ item_class->event = sp_canvas_arena_event;
+ item_class->viewbox_changed = sp_canvas_arena_viewbox_changed;
+}
+
+static void
+sp_canvas_arena_init (SPCanvasArena *arena)
+{
+ arena->sticky = FALSE;
+
+ new (&arena->drawing) Inkscape::Drawing(arena);
+
+ Inkscape::DrawingGroup *root = new DrawingGroup(arena->drawing);
+ root->setPickChildren(true);
+ arena->drawing.setRoot(root);
+
+ arena->observer = new CachePrefObserver(arena);
+
+ arena->drawing.signal_request_update.connect(
+ sigc::bind<0>(
+ sigc::ptr_fun(&sp_canvas_arena_request_update),
+ arena));
+ arena->drawing.signal_request_render.connect(
+ sigc::bind<0>(
+ sigc::ptr_fun(&sp_canvas_arena_request_render),
+ arena));
+ arena->drawing.signal_item_deleted.connect(
+ sigc::bind<0>(
+ sigc::ptr_fun(&sp_canvas_arena_item_deleted),
+ arena));
+
+ arena->active = nullptr;
+}
+
+static void sp_canvas_arena_destroy(SPCanvasItem *object)
+{
+ SPCanvasArena *arena = SP_CANVAS_ARENA(object);
+
+ delete arena->observer;
+ arena->drawing.~Drawing();
+
+ if (SP_CANVAS_ITEM_CLASS(sp_canvas_arena_parent_class)->destroy)
+ SP_CANVAS_ITEM_CLASS(sp_canvas_arena_parent_class)->destroy(object);
+}
+
+static void
+sp_canvas_arena_update (SPCanvasItem *item, Geom::Affine const &affine, unsigned int flags)
+{
+ SPCanvasArena *arena = SP_CANVAS_ARENA (item);
+
+ if (SP_CANVAS_ITEM_CLASS(sp_canvas_arena_parent_class)->update)
+ SP_CANVAS_ITEM_CLASS(sp_canvas_arena_parent_class)->update(item, affine, flags);
+
+ arena->ctx.ctm = affine;
+
+ unsigned reset = flags & SP_CANVAS_UPDATE_AFFINE ? DrawingItem::STATE_ALL : 0;
+ arena->drawing.update(Geom::IntRect::infinite(), DrawingItem::STATE_ALL, reset);
+
+ Geom::OptIntRect b = arena->drawing.root()->visualBounds();
+ if (b) {
+ item->x1 = b->left() - 1;
+ item->y1 = b->top() - 1;
+ item->x2 = b->right() + 1;
+ item->y2 = b->bottom() + 1;
+ }
+
+ if (arena->cursor) {
+ /* Mess with enter/leave notifiers */
+ DrawingItem *new_arena = arena->drawing.pick(arena->c, arena->drawing.delta, arena->sticky);
+ if (new_arena != arena->active) {
+ GdkEventCrossing ec;
+ ec.window = gtk_widget_get_window (GTK_WIDGET (item->canvas));
+ ec.send_event = TRUE;
+ ec.subwindow = ec.window;
+ ec.time = GDK_CURRENT_TIME;
+ ec.x = arena->c[Geom::X];
+ ec.y = arena->c[Geom::Y];
+ /* fixme: */
+ if (arena->active) {
+ ec.type = GDK_LEAVE_NOTIFY;
+ sp_canvas_arena_send_event (arena, (GdkEvent *) &ec);
+ }
+ arena->active = new_arena;
+ if (arena->active) {
+ ec.type = GDK_ENTER_NOTIFY;
+ sp_canvas_arena_send_event (arena, (GdkEvent *) &ec);
+ }
+ }
+ }
+}
+
+static void
+sp_canvas_arena_item_deleted(SPCanvasArena *arena, Inkscape::DrawingItem *item)
+{
+ if (arena->active == item) {
+ arena->active = nullptr;
+ }
+}
+
+static void
+sp_canvas_arena_render (SPCanvasItem *item, SPCanvasBuf *buf)
+{
+ // todo: handle NR_ARENA_ITEM_RENDER_NO_CACHE
+ SPCanvasArena *arena = SP_CANVAS_ARENA (item);
+
+ Geom::OptIntRect r = buf->rect;
+ if (!r || r->hasZeroArea()) return;
+
+ Inkscape::DrawingContext dc(buf->ct, r->min());
+
+ arena->drawing.update(Geom::IntRect::infinite());
+ arena->drawing.render(dc, *r);
+}
+
+static double
+sp_canvas_arena_point (SPCanvasItem *item, Geom::Point p, SPCanvasItem **actual_item)
+{
+ SPCanvasArena *arena = SP_CANVAS_ARENA (item);
+
+ arena->drawing.update(Geom::IntRect::infinite(), DrawingItem::STATE_PICK | DrawingItem::STATE_BBOX);
+ DrawingItem *picked = arena->drawing.pick(p, arena->drawing.delta, arena->sticky);
+
+ arena->picked = picked;
+
+ if (picked) {
+ *actual_item = item;
+ return 0.0;
+ }
+
+ return 1e18;
+}
+
+static void
+sp_canvas_arena_viewbox_changed (SPCanvasItem *item, Geom::IntRect const &new_area)
+{
+ SPCanvasArena *arena = SP_CANVAS_ARENA(item);
+ // make the cache limit larger than screen to facilitate smooth scrolling
+ Geom::IntRect expanded = new_area;
+ Geom::IntPoint expansion(new_area.width()/2, new_area.height()/2);
+ expanded.expandBy(expansion);
+ arena->drawing.setCacheLimit(expanded);
+}
+
+// What is this used for? It appears to allow one to get signals on entering/leaving elements which
+// is intend to be used by Inkview to change the cursor when over an anchor. However arena->active
+// appears to only be set to root.
+static gint
+sp_canvas_arena_event (SPCanvasItem *item, GdkEvent *event)
+{
+ Inkscape::DrawingItem *new_arena;
+ /* fixme: This sucks, we have to handle enter/leave notifiers */
+
+ SPCanvasArena *arena = SP_CANVAS_ARENA (item);
+ gint ret = FALSE;
+
+ switch (event->type) {
+ case GDK_ENTER_NOTIFY:
+ if (!arena->cursor) {
+ if (arena->active) {
+ //g_warning ("Cursor entered to arena with already active item");
+ }
+ arena->cursor = TRUE;
+
+ /* TODO ... event -> arena transform? */
+ arena->c = Geom::Point(event->crossing.x, event->crossing.y);
+
+ /* fixme: Not sure abut this, but seems the right thing (Lauris) */
+ arena->drawing.update(Geom::IntRect::infinite(),
+ DrawingItem::STATE_PICK | DrawingItem::STATE_BBOX, 0);
+ arena->active = arena->drawing.pick(arena->c, arena->drawing.delta, arena->sticky);
+ ret = sp_canvas_arena_send_event (arena, event);
+ }
+ break;
+
+ case GDK_LEAVE_NOTIFY:
+ if (arena->cursor) {
+ ret = sp_canvas_arena_send_event (arena, event);
+ arena->active = nullptr;
+ arena->cursor = FALSE;
+ }
+ break;
+
+ case GDK_MOTION_NOTIFY:
+ /* TODO ... event -> arena transform? */
+ arena->c = Geom::Point(event->motion.x, event->motion.y);
+
+ /* fixme: Not sure abut this, but seems the right thing (Lauris) */
+ arena->drawing.update(Geom::IntRect::infinite(),
+ DrawingItem::STATE_PICK | DrawingItem::STATE_BBOX);
+ new_arena = arena->drawing.pick(arena->c, arena->drawing.delta, arena->sticky);
+ if (new_arena != arena->active) {
+ GdkEventCrossing ec;
+ ec.window = event->motion.window;
+ ec.send_event = event->motion.send_event;
+ ec.subwindow = event->motion.window;
+ ec.time = event->motion.time;
+ ec.x = event->motion.x;
+ ec.y = event->motion.y;
+ /* fixme: */
+ if (arena->active) {
+ ec.type = GDK_LEAVE_NOTIFY;
+ ret = sp_canvas_arena_send_event (arena, (GdkEvent *) &ec);
+ }
+ arena->active = new_arena;
+ if (arena->active) {
+ ec.type = GDK_ENTER_NOTIFY;
+ ret = sp_canvas_arena_send_event (arena, (GdkEvent *) &ec);
+ }
+ }
+ ret = ret || sp_canvas_arena_send_event (arena, event);
+ break;
+
+ case GDK_SCROLL: {
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ bool wheelzooms = prefs->getBool("/options/wheelzooms/value");
+ bool ctrl = (event->scroll.state & GDK_CONTROL_MASK);
+ if ((ctrl && !wheelzooms) || (!ctrl && wheelzooms)) {
+ /* Zoom is emitted by the canvas as well, ignore here */
+ return FALSE;
+ }
+ ret = sp_canvas_arena_send_event (arena, event);
+ break;
+ }
+
+ default:
+ /* Just send event */
+ ret = sp_canvas_arena_send_event (arena, event);
+ break;
+ }
+
+ return ret;
+}
+
+static gint
+sp_canvas_arena_send_event (SPCanvasArena *arena, GdkEvent *event)
+{
+ gint ret = FALSE;
+
+ /* Send event to arena */
+ g_signal_emit (G_OBJECT (arena), signals[ARENA_EVENT], 0, arena->active, event, &ret);
+
+ return ret;
+}
+
+static void
+sp_canvas_arena_request_update (SPCanvasArena *ca, DrawingItem */*item*/)
+{
+ sp_canvas_item_request_update (SP_CANVAS_ITEM (ca));
+}
+
+static void sp_canvas_arena_request_render(SPCanvasArena *ca, Geom::IntRect const &area)
+{
+ SPCanvas *canvas = SP_CANVAS_ITEM(ca)->canvas;
+ canvas->requestRedraw(area.left(), area.top(), area.right(), area.bottom());
+}
+
+void
+sp_canvas_arena_set_pick_delta (SPCanvasArena *ca, gdouble delta)
+{
+ g_return_if_fail (ca != nullptr);
+ g_return_if_fail (SP_IS_CANVAS_ARENA (ca));
+
+ /* fixme: repick? */
+ ca->delta = delta;
+}
+
+void
+sp_canvas_arena_set_sticky (SPCanvasArena *ca, gboolean sticky)
+{
+ g_return_if_fail (ca != nullptr);
+ g_return_if_fail (SP_IS_CANVAS_ARENA (ca));
+
+ /* fixme: repick? */
+ ca->sticky = sticky;
+}
+
+void
+sp_canvas_arena_render_surface (SPCanvasArena *ca, cairo_surface_t *surface, Geom::IntRect const &r)
+{
+ g_return_if_fail (ca != nullptr);
+ g_return_if_fail (SP_IS_CANVAS_ARENA (ca));
+
+ Inkscape::DrawingContext dc(surface, r.min());
+ ca->drawing.update(Geom::IntRect::infinite());
+ ca->drawing.render(dc, r);
+}
+
+/*
+ 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 :