diff options
Diffstat (limited to '')
-rw-r--r-- | src/display/control/canvas-item-drawing.cpp | 296 |
1 files changed, 296 insertions, 0 deletions
diff --git a/src/display/control/canvas-item-drawing.cpp b/src/display/control/canvas-item-drawing.cpp new file mode 100644 index 0000000..17750b7 --- /dev/null +++ b/src/display/control/canvas-item-drawing.cpp @@ -0,0 +1,296 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** + * A class to render the SVG drawing. + */ + +/* + * Author: + * Tavmjong Bah + * + * Copyright (C) 2020 Tavmjong Bah + * + * Rewrite of _SPCavasArena. + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "canvas-item-drawing.h" + +#include "desktop.h" + +#include "preferences.h" + +#include "display/cairo-utils.h" +#include "display/drawing.h" +#include "display/drawing-context.h" +#include "display/drawing-item.h" +#include "display/drawing-group.h" +#include "display/drawing-surface.h" + +#include "ui/widget/canvas.h" +#include "ui/modifiers.h" + +namespace Inkscape { + +struct CachePref2Observer : public Inkscape::Preferences::Observer { + CachePref2Observer(Inkscape::CanvasItemDrawing *item) + : Inkscape::Preferences::Observer("/options/renderingcache") + , _canvas_item_drawing(item) + { + 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(Inkscape::Preferences::Entry const &v) override { + Glib::ustring name = v.getEntryName(); + if (name == "size") { + _canvas_item_drawing->get_drawing()->setCacheBudget((1 << 20) * v.getIntLimited(64, 0, 4096)); + } + } + Inkscape::CanvasItemDrawing *_canvas_item_drawing; +}; + +/** + * Create the drawing. One per window! + */ +CanvasItemDrawing::CanvasItemDrawing(CanvasItemGroup *group) + : CanvasItem(group) +{ + _name = "CanvasItemDrawing"; + _pickable = true; + + _drawing = new Inkscape::Drawing(this); + _drawing->delta = 1.0; // Default + auto root = new Inkscape::DrawingGroup(*_drawing); + root->setPickChildren(true); + _drawing->setRoot(root); + + // _drawing->signal_request_update.connect(...); Not needed now. + // _drawing->signal_request_render.connect(...); Not needed now. + // _drawing->signal_item_deleted.connect(...); Not needed now. + + _observer = new CachePref2Observer(this); +} + +CanvasItemDrawing::~CanvasItemDrawing() +{ + delete _observer; + delete _drawing; +} + +/** + * Returns distance between point in canvas units and nearest point on drawing. + */ +double CanvasItemDrawing::closest_distance_to(Geom::Point const &p) +{ + double d = Geom::infinity(); + std::cerr << "CanvasItemDrawing::closest_distance_to: Not implemented!" << std::endl; + return d; +} + +/** + * Returns true if point p (in canvas units) is inside some object in drawing. + */ +bool CanvasItemDrawing::contains(Geom::Point const &p, double tolerance) +{ + if (tolerance != 0) { + std::cerr << "CanvasItemDrawing::contains: Non-zero tolerance not implemented!" << std::endl; + } + + _drawing->update(Geom::IntRect::infinite(), DrawingItem::STATE_PICK | DrawingItem::STATE_BBOX); + _picked_item = _drawing->pick(p, _drawing->delta, _sticky); + + if (_picked_item) { + // This will trigger a signal that is handled by our event handler. Seems a bit of a + // round-a-bout way of doing things but it matches what other pickable canvas-item classes + // do. + return true; + } + + return false; +} + +/** + * Update and redraw drawing. + */ +void CanvasItemDrawing::update(Geom::Affine const &affine) +{ + auto new_affine = affine; + + // Correct for y-axis. This should not be here!!!! + if (auto *desktop = _canvas->get_desktop()) { + new_affine = desktop->doc2dt() * new_affine; + } + + // if (_affine == new_affine && !_need_update) { + // // Nothing to do. + // return; + // } + + _ctx.ctm = new_affine; // TODO Remove _ctx.ctm... it's exactly the same as _affine! + + unsigned reset = (_affine != new_affine ? DrawingItem::STATE_ALL : 0); + + _affine = new_affine; + + _drawing->update(Geom::IntRect::infinite(), DrawingItem::STATE_ALL, reset); + + Geom::OptIntRect bbox = _drawing->root()->visualBounds(); + if (bbox) { + _bounds = *bbox; + _bounds.expandBy(1); // Avoid aliasing artifacts. + } + + if (_cursor) { + /* Mess with enter/leave notifiers */ + DrawingItem *new_drawing_item = _drawing->pick(_c, _delta, _sticky); + if (_active_item != new_drawing_item) { + + GdkEventCrossing ec; + ec.window = _canvas->get_window()->gobj(); + ec.send_event = true; + ec.subwindow = ec.window; + ec.time = GDK_CURRENT_TIME; + ec.x = _c.x(); + ec.y = _c.y(); + + /* fixme: Why? */ + if (_active_item) { + ec.type = GDK_LEAVE_NOTIFY; + _drawing_event_signal.emit((GdkEvent *) &ec, _active_item); + } + + _active_item = new_drawing_item; + + if (_active_item) { + ec.type = GDK_ENTER_NOTIFY; + _drawing_event_signal.emit((GdkEvent *) &ec, _active_item); + } + } + } + + _need_update = false; +} + +/** + * Render drawing to screen via Cairo. + */ +void CanvasItemDrawing::render(Inkscape::CanvasItemBuffer *buf) +{ + if (!buf) { + std::cerr << "CanvasItemDrawing::Render: No buffer!" << std::endl; + return; + } + + if (buf->rect.hasZeroArea()) { + return; + } + + Inkscape::DrawingContext dc(buf->cr->cobj(), buf->rect.min()); + _drawing->update(); + _drawing->render(dc, buf->rect); +} + +/** + * Handle events directed at the drawing. We first attempt to handle them here. + */ +bool CanvasItemDrawing::handle_event(GdkEvent *event) +{ + bool retval = false; + + switch (event->type) { + case GDK_ENTER_NOTIFY: + if (!_cursor) { + if (_active_item) { + std::cerr << "CanvasItemDrawing::event_handler: cursor entered drawing with an active item!" << std::endl; + } + _cursor = true; + + /* TODO ... event -> arena transform? */ + _c = Geom::Point(event->crossing.x, event->crossing.y); + + /* fixme: Not sure abut this, but seems the right thing (Lauris) */ + //_drawing->update(Geom::IntRect::infinite(), _ctx, DrawingItem::STATE_PICK | DrawingItem::STATE_BBOX, 0); + _active_item = _drawing->pick(_c, _drawing->delta, _sticky); + retval = _drawing_event_signal.emit(event, _active_item); + } + break; + + case GDK_LEAVE_NOTIFY: + if (_cursor) { + retval = _drawing_event_signal.emit(event, _active_item); + _active_item = nullptr; + _cursor = false; + } + break; + + case GDK_MOTION_NOTIFY: + { + /* TODO ... event -> arena transform? */ + _c = Geom::Point(event->motion.x, event->motion.y); + + /* fixme: Not sure abut this, but seems the right thing (Lauris) */ + //_drawing->update(Geom::IntRect::infinite(), _ctx, DrawingItem::STATE_PICK | DrawingItem::STATE_BBOX); + + auto new_drawing_item = _drawing->pick(_c, _drawing->delta, _sticky); + if (_active_item != new_drawing_item) { + + 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: What is wrong? */ + if (_active_item) { + ec.type = GDK_LEAVE_NOTIFY; + retval = _drawing_event_signal.emit((GdkEvent *) &ec, _active_item); + } + + _active_item = new_drawing_item; + + if (_active_item) { + ec.type = GDK_ENTER_NOTIFY; + retval = _drawing_event_signal.emit((GdkEvent *) &ec, _active_item); + } + } + retval = retval || _drawing_event_signal.emit(event, _active_item); + break; + } + + case GDK_SCROLL: + { + if (Modifiers::Modifier::get(Modifiers::Type::CANVAS_ZOOM)->active(event->scroll.state)) { + /* Zoom is emitted by the canvas as well, ignore here */ + return false; + } + retval = _drawing_event_signal.emit(event, _active_item); + break; + } + + default: + /* Just send event */ + retval = _drawing_event_signal.emit(event, _active_item); + break; + } + + return retval; +} + +} // namespace Inkscape + +/* + 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 : |