summaryrefslogtreecommitdiffstats
path: root/src/knot.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/knot.cpp')
-rw-r--r--src/knot.cpp602
1 files changed, 602 insertions, 0 deletions
diff --git a/src/knot.cpp b/src/knot.cpp
new file mode 100644
index 0000000..5dc22cb
--- /dev/null
+++ b/src/knot.cpp
@@ -0,0 +1,602 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * SPKnot implementation
+ *
+ * Authors:
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ * bulia byak <buliabyak@users.sf.net>
+ * Abhishek Sharma
+ *
+ * Copyright (C) 1999-2005 authors
+ * Copyright (C) 2001-2002 Ximian, Inc.
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#ifdef HAVE_CONFIG_H
+#endif
+#include <gdk/gdkkeysyms.h>
+#include <glibmm/i18n.h>
+#include "display/sodipodi-ctrl.h"
+#include "desktop.h"
+
+#include "knot.h"
+#include "knot-ptr.h"
+#include "document.h"
+#include "document-undo.h"
+#include "message-stack.h"
+#include "message-context.h"
+#include "ui/tools/node-tool.h"
+#include <gtk/gtk.h>
+
+using Inkscape::DocumentUndo;
+
+#define KNOT_EVENT_MASK (GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | \
+ GDK_POINTER_MOTION_MASK | \
+ GDK_POINTER_MOTION_HINT_MASK | \
+ GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK)
+
+const gchar *nograbenv = getenv("INKSCAPE_NO_GRAB");
+static bool nograb = (nograbenv && *nograbenv && (*nograbenv != '0'));
+
+static bool grabbed = false;
+static bool moved = false;
+
+static gint xp = 0, yp = 0; // where drag started
+static gint tolerance = 0;
+static bool within_tolerance = false;
+
+static bool transform_escaped = false; // true iff resize or rotate was cancelled by esc.
+
+void knot_ref(SPKnot* knot) {
+ knot->ref_count++;
+}
+
+void knot_unref(SPKnot* knot) {
+ if (--knot->ref_count < 1) {
+ delete knot;
+ }
+}
+
+
+static int sp_knot_handler(SPCanvasItem *item, GdkEvent *event, SPKnot *knot);
+
+SPKnot::SPKnot(SPDesktop *desktop, gchar const *tip)
+ : ref_count(1)
+{
+ this->desktop = nullptr;
+ this->item = nullptr;
+ this->owner = nullptr;
+ this->flags = 0;
+
+ this->size = 8;
+ this->angle = 0;
+ this->pos = Geom::Point(0, 0);
+ this->grabbed_rel_pos = Geom::Point(0, 0);
+ this->anchor = SP_ANCHOR_CENTER;
+ this->shape = SP_KNOT_SHAPE_SQUARE;
+ this->mode = SP_KNOT_MODE_XOR;
+ this->tip = nullptr;
+ this->_event_handler_id = 0;
+ this->pressure = 0;
+
+ this->fill[SP_KNOT_STATE_NORMAL] = 0xffffff00;
+ this->fill[SP_KNOT_STATE_MOUSEOVER] = 0xff0000ff;
+ this->fill[SP_KNOT_STATE_DRAGGING] = 0xff0000ff;
+ this->fill[SP_KNOT_STATE_SELECTED] = 0x0000ffff;
+
+ this->stroke[SP_KNOT_STATE_NORMAL] = 0x01000000;
+ this->stroke[SP_KNOT_STATE_MOUSEOVER] = 0x01000000;
+ this->stroke[SP_KNOT_STATE_DRAGGING] = 0x01000000;
+ this->stroke[SP_KNOT_STATE_SELECTED] = 0x01000000;
+
+ this->image[SP_KNOT_STATE_NORMAL] = nullptr;
+ this->image[SP_KNOT_STATE_MOUSEOVER] = nullptr;
+ this->image[SP_KNOT_STATE_DRAGGING] = nullptr;
+ this->image[SP_KNOT_STATE_SELECTED] = nullptr;
+
+ this->cursor[SP_KNOT_STATE_NORMAL] = nullptr;
+ this->cursor[SP_KNOT_STATE_MOUSEOVER] = nullptr;
+ this->cursor[SP_KNOT_STATE_DRAGGING] = nullptr;
+ this->cursor[SP_KNOT_STATE_SELECTED] = nullptr;
+
+ this->saved_cursor = nullptr;
+ this->pixbuf = nullptr;
+
+
+ this->desktop = desktop;
+ this->flags = SP_KNOT_VISIBLE;
+
+ if (tip) {
+ this->tip = g_strdup (tip);
+ }
+
+ this->item = sp_canvas_item_new(desktop->getControls(),
+ SP_TYPE_CTRL,
+ "anchor", SP_ANCHOR_CENTER,
+ "size", 9,
+ "angle", 0.0,
+ "filled", TRUE,
+ "fill_color", 0xffffff00,
+ "stroked", TRUE,
+ "stroke_color", 0x01000000,
+ "mode", SP_KNOT_MODE_XOR,
+ NULL);
+
+ this->_event_handler_id = g_signal_connect(G_OBJECT(this->item), "event",
+ G_CALLBACK(sp_knot_handler), this);
+ knot_created_callback(this);
+}
+
+SPKnot::~SPKnot() {
+ auto display = gdk_display_get_default();
+ auto seat = gdk_display_get_default_seat(display);
+ auto device = gdk_seat_get_pointer(seat);
+
+ if ((this->flags & SP_KNOT_GRABBED) && gdk_display_device_is_grabbed(display, device)) {
+ // This happens e.g. when deleting a node in node tool while dragging it
+ gdk_seat_ungrab(seat);
+ }
+
+ if (this->_event_handler_id > 0) {
+ g_signal_handler_disconnect(G_OBJECT (this->item), this->_event_handler_id);
+ this->_event_handler_id = 0;
+ }
+
+ if (this->item) {
+ sp_canvas_item_destroy(this->item);
+ this->item = nullptr;
+ }
+
+ for (auto & i : this->cursor) {
+ if (i) {
+ g_object_unref(i);
+ i = nullptr;
+ }
+ }
+
+ if (this->tip) {
+ g_free(this->tip);
+ this->tip = nullptr;
+ }
+
+ // FIXME: cannot snap to destroyed knot (lp:1309050)
+ //sp_event_context_discard_delayed_snap_event(this->desktop->event_context);
+ knot_deleted_callback(this);
+}
+
+void SPKnot::startDragging(Geom::Point const &p, gint x, gint y, guint32 etime) {
+ // save drag origin
+ xp = x;
+ yp = y;
+ within_tolerance = true;
+
+ this->grabbed_rel_pos = p - this->pos;
+ this->drag_origin = this->pos;
+
+ if (!nograb) {
+ sp_canvas_item_grab(this->item, KNOT_EVENT_MASK, this->cursor[SP_KNOT_STATE_DRAGGING], etime);
+ }
+ this->setFlag(SP_KNOT_GRABBED, TRUE);
+
+ grabbed = TRUE;
+}
+
+void SPKnot::selectKnot(bool select){
+ setFlag(SP_KNOT_SELECTED, select);
+}
+
+/**
+ * Called to handle events on knots.
+ */
+static int sp_knot_handler(SPCanvasItem */*item*/, GdkEvent *event, SPKnot *knot)
+{
+ g_assert(knot != nullptr);
+ g_assert(SP_IS_KNOT(knot));
+
+ /* Run client universal event handler, if present */
+ bool consumed = knot->event_signal.emit(knot, event);
+
+ if (consumed) {
+ return true;
+ }
+
+ bool key_press_event_unconsumed = FALSE;
+
+ knot_ref(knot);
+
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ tolerance = prefs->getIntLimited("/options/dragtolerance/value", 0, 0, 100);
+
+ switch (event->type) {
+ case GDK_2BUTTON_PRESS:
+ if (event->button.button == 1) {
+ knot->doubleclicked_signal.emit(knot, event->button.state);
+
+ grabbed = FALSE;
+ moved = FALSE;
+ consumed = TRUE;
+ }
+ break;
+ case GDK_BUTTON_PRESS:
+ if ((event->button.button == 1) && knot->desktop && knot->desktop->event_context && !knot->desktop->event_context->space_panning) {
+ Geom::Point const p = knot->desktop->w2d(Geom::Point(event->button.x, event->button.y));
+ knot->startDragging(p, (gint) event->button.x, (gint) event->button.y, event->button.time);
+ knot->mousedown_signal.emit(knot, event->button.state);
+ consumed = TRUE;
+ }
+ break;
+ case GDK_BUTTON_RELEASE:
+ if (event->button.button == 1 && knot->desktop && knot->desktop->event_context && !knot->desktop->event_context->space_panning) {
+ // If we have any pending snap event, then invoke it now
+ if (knot->desktop->event_context->_delayed_snap_event) {
+ sp_event_context_snap_watchdog_callback(knot->desktop->event_context->_delayed_snap_event);
+ }
+ sp_event_context_discard_delayed_snap_event(knot->desktop->event_context);
+ knot->pressure = 0;
+
+ if (transform_escaped) {
+ transform_escaped = false;
+ consumed = TRUE;
+ } else {
+ knot->setFlag(SP_KNOT_GRABBED, FALSE);
+
+ if (!nograb) {
+ sp_canvas_item_ungrab(knot->item);
+ }
+
+ if (moved) {
+ knot->setFlag(SP_KNOT_DRAGGING, FALSE);
+ knot->ungrabbed_signal.emit(knot, event->button.state);
+ } else {
+ knot->click_signal.emit(knot, event->button.state);
+ }
+
+ grabbed = FALSE;
+ moved = FALSE;
+ consumed = TRUE;
+ }
+ }
+ Inkscape::UI::Tools::sp_update_helperpath();
+ break;
+ case GDK_MOTION_NOTIFY:
+ if (!(event->motion.state & GDK_BUTTON1_MASK) && knot->flags & SP_KNOT_DRAGGING) {
+ // If we have any pending snap event, then invoke it now
+ if (knot->desktop->event_context->_delayed_snap_event) {
+ sp_event_context_snap_watchdog_callback(knot->desktop->event_context->_delayed_snap_event);
+ }
+ sp_event_context_discard_delayed_snap_event(knot->desktop->event_context);
+ knot->pressure = 0;
+
+ if (transform_escaped) {
+ transform_escaped = false;
+ consumed = TRUE;
+ } else {
+ knot->setFlag(SP_KNOT_GRABBED, FALSE);
+
+ if (!nograb) {
+ sp_canvas_item_ungrab(knot->item);
+ }
+
+ if (moved) {
+ knot->setFlag(SP_KNOT_DRAGGING, FALSE);
+ knot->ungrabbed_signal.emit(knot, event->motion.state);
+ } else {
+ knot->click_signal.emit(knot, event->motion.state);
+ }
+
+ grabbed = FALSE;
+ moved = FALSE;
+ consumed = TRUE;
+ Inkscape::UI::Tools::sp_update_helperpath();
+ }
+ } else if (grabbed && knot->desktop && knot->desktop->event_context &&
+ !knot->desktop->event_context->space_panning) {
+ consumed = TRUE;
+
+ if ( within_tolerance
+ && ( abs( (gint) event->motion.x - xp ) < tolerance )
+ && ( abs( (gint) event->motion.y - yp ) < tolerance ) ) {
+ break; // do not drag if we're within tolerance from origin
+ }
+
+ // Once the user has moved farther than tolerance from the original location
+ // (indicating they intend to move the object, not click), then always process the
+ // motion notify coordinates as given (no snapping back to origin)
+ within_tolerance = false;
+
+ if (gdk_event_get_axis (event, GDK_AXIS_PRESSURE, &knot->pressure)) {
+ knot->pressure = CLAMP (knot->pressure, 0, 1);
+ } else {
+ knot->pressure = 0.5;
+ }
+
+ if (!moved) {
+ knot->setFlag(SP_KNOT_DRAGGING, TRUE);
+ knot->grabbed_signal.emit(knot, event->button.state);
+ }
+
+ sp_event_context_snap_delay_handler(knot->desktop->event_context, nullptr, knot, (GdkEventMotion *)event, Inkscape::UI::Tools::DelayedSnapEvent::KNOT_HANDLER);
+ sp_knot_handler_request_position(event, knot);
+ moved = TRUE;
+ }
+ break;
+ case GDK_ENTER_NOTIFY:
+ knot->setFlag(SP_KNOT_MOUSEOVER, TRUE);
+ knot->setFlag(SP_KNOT_GRABBED, FALSE);
+
+ if (knot->tip && knot->desktop && knot->desktop->event_context) {
+ knot->desktop->event_context->defaultMessageContext()->set(Inkscape::NORMAL_MESSAGE, knot->tip);
+ }
+
+ grabbed = FALSE;
+ moved = FALSE;
+ consumed = TRUE;
+ break;
+ case GDK_LEAVE_NOTIFY:
+ knot->setFlag(SP_KNOT_MOUSEOVER, FALSE);
+ knot->setFlag(SP_KNOT_GRABBED, FALSE);
+
+ if (knot->tip && knot->desktop && knot->desktop->event_context) {
+ knot->desktop->event_context->defaultMessageContext()->clear();
+ }
+
+ grabbed = FALSE;
+ moved = FALSE;
+ consumed = TRUE;
+ break;
+ case GDK_KEY_PRESS: // keybindings for knot
+ switch (Inkscape::UI::Tools::get_latin_keyval(&event->key)) {
+ case GDK_KEY_Escape:
+ knot->setFlag(SP_KNOT_GRABBED, FALSE);
+
+ if (!nograb) {
+ sp_canvas_item_ungrab(knot->item);
+ }
+
+ if (moved) {
+ knot->setFlag(SP_KNOT_DRAGGING, FALSE);
+
+ knot->ungrabbed_signal.emit(knot, event->button.state);
+
+ DocumentUndo::undo(knot->desktop->getDocument());
+ knot->desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("Node or handle drag canceled."));
+ transform_escaped = true;
+ consumed = TRUE;
+ }
+
+ grabbed = FALSE;
+ moved = FALSE;
+
+ sp_event_context_discard_delayed_snap_event(knot->desktop->event_context);
+ break;
+ default:
+ consumed = FALSE;
+ key_press_event_unconsumed = TRUE;
+ break;
+ }
+ break;
+ default:
+ break;
+ }
+
+ knot_unref(knot);
+
+ if (key_press_event_unconsumed) {
+ return false; // e.g. in case "%" was pressed to toggle snapping, or Q for quick zoom (while dragging a handle)
+ } else {
+ return consumed || grabbed;
+ }
+}
+
+void sp_knot_handler_request_position(GdkEvent *event, SPKnot *knot) {
+ Geom::Point const motion_w(event->motion.x, event->motion.y);
+ Geom::Point const motion_dt = knot->desktop->w2d(motion_w);
+ Geom::Point p = motion_dt - knot->grabbed_rel_pos;
+
+ knot->requestPosition(p, event->motion.state);
+ knot->desktop->scroll_to_point (motion_dt);
+ knot->desktop->set_coordinate_status(knot->pos); // display the coordinate of knot, not cursor - they may be different!
+
+ if (event->motion.state & GDK_BUTTON1_MASK) {
+ Inkscape::UI::Tools::gobble_motion_events(GDK_BUTTON1_MASK);
+ }
+}
+
+void SPKnot::show() {
+ this->setFlag(SP_KNOT_VISIBLE, TRUE);
+}
+
+void SPKnot::hide() {
+ this->setFlag(SP_KNOT_VISIBLE, FALSE);
+}
+
+void SPKnot::requestPosition(Geom::Point const &p, guint state) {
+ bool done = this->request_signal.emit(this, &const_cast<Geom::Point&>(p), state);
+
+ /* If user did not complete, we simply move knot to new position */
+ if (!done) {
+ this->setPosition(p, state);
+ }
+}
+
+void SPKnot::setPosition(Geom::Point const &p, guint state) {
+ this->pos = p;
+
+ if (this->item) {
+ SP_CTRL(this->item)->moveto(p);
+ }
+
+ this->moved_signal.emit(this, p, state);
+}
+
+void SPKnot::moveto(Geom::Point const &p) {
+ this->pos = p;
+
+ if (this->item) {
+ SP_CTRL(this->item)->moveto(p);
+ }
+}
+
+Geom::Point SPKnot::position() const {
+ return this->pos;
+}
+
+void SPKnot::setFlag(guint flag, bool set) {
+ if (set) {
+ this->flags |= flag;
+ } else {
+ this->flags &= ~flag;
+ }
+
+ switch (flag) {
+ case SP_KNOT_VISIBLE:
+ if (set) {
+ sp_canvas_item_show(this->item);
+ } else {
+ sp_canvas_item_hide(this->item);
+ }
+ break;
+ case SP_KNOT_MOUSEOVER:
+ case SP_KNOT_DRAGGING:
+ case SP_KNOT_SELECTED:
+ this->_setCtrlState();
+ break;
+ case SP_KNOT_GRABBED:
+ break;
+ default:
+ g_assert_not_reached();
+ break;
+ }
+}
+
+void SPKnot::updateCtrl() {
+ if (!this->item) {
+ return;
+ }
+
+ g_object_set(this->item, "shape", this->shape, NULL);
+ g_object_set(this->item, "mode", this->mode, NULL);
+ g_object_set(this->item, "size", this->size, NULL);
+ g_object_set(this->item, "angle", this->angle, NULL);
+ g_object_set(this->item, "anchor", this->anchor, NULL);
+
+ if (this->pixbuf) {
+ g_object_set(this->item, "pixbuf", this->pixbuf, NULL);
+ }
+
+ this->_setCtrlState();
+}
+
+void SPKnot::_setCtrlState() {
+ int state = SP_KNOT_STATE_NORMAL;
+
+ if (this->flags & SP_KNOT_DRAGGING) {
+ state = SP_KNOT_STATE_DRAGGING;
+ } else if (this->flags & SP_KNOT_MOUSEOVER) {
+ state = SP_KNOT_STATE_MOUSEOVER;
+ } else if (this->flags & SP_KNOT_SELECTED) {
+ state = SP_KNOT_STATE_SELECTED;
+ }
+ g_object_set(this->item, "fill_color", this->fill[state], NULL);
+ g_object_set(this->item, "stroke_color", this->stroke[state], NULL);
+}
+
+
+void SPKnot::setSize(guint i) {
+ size = i;
+}
+
+void SPKnot::setShape(guint i) {
+ shape = (SPKnotShapeType) i;
+}
+
+void SPKnot::setAnchor(guint i) {
+ anchor = (SPAnchorType) i;
+}
+
+void SPKnot::setMode(guint i) {
+ mode = (SPKnotModeType) i;
+}
+
+void SPKnot::setPixbuf(gpointer p) {
+ pixbuf = p;
+}
+
+void SPKnot::setAngle(double i) {
+ angle = i;
+}
+
+void SPKnot::setFill(guint32 normal, guint32 mouseover, guint32 dragging, guint32 selected) {
+ fill[SP_KNOT_STATE_NORMAL] = normal;
+ fill[SP_KNOT_STATE_MOUSEOVER] = mouseover;
+ fill[SP_KNOT_STATE_DRAGGING] = dragging;
+ fill[SP_KNOT_STATE_SELECTED] = selected;
+}
+
+void SPKnot::setStroke(guint32 normal, guint32 mouseover, guint32 dragging, guint32 selected) {
+ stroke[SP_KNOT_STATE_NORMAL] = normal;
+ stroke[SP_KNOT_STATE_MOUSEOVER] = mouseover;
+ stroke[SP_KNOT_STATE_DRAGGING] = dragging;
+ stroke[SP_KNOT_STATE_SELECTED] = selected;
+}
+
+void SPKnot::setImage(guchar* normal, guchar* mouseover, guchar* dragging, guchar* selected) {
+ image[SP_KNOT_STATE_NORMAL] = normal;
+ image[SP_KNOT_STATE_MOUSEOVER] = mouseover;
+ image[SP_KNOT_STATE_DRAGGING] = dragging;
+ image[SP_KNOT_STATE_SELECTED] = selected;
+}
+
+void SPKnot::setCursor(GdkCursor* normal, GdkCursor* mouseover, GdkCursor* dragging, GdkCursor* selected) {
+ if (cursor[SP_KNOT_STATE_NORMAL]) {
+ g_object_unref(cursor[SP_KNOT_STATE_NORMAL]);
+ }
+
+ cursor[SP_KNOT_STATE_NORMAL] = normal;
+
+ if (normal) {
+ g_object_ref(normal);
+ }
+
+ if (cursor[SP_KNOT_STATE_MOUSEOVER]) {
+ g_object_unref(cursor[SP_KNOT_STATE_MOUSEOVER]);
+ }
+
+ cursor[SP_KNOT_STATE_MOUSEOVER] = mouseover;
+
+ if (mouseover) {
+ g_object_ref(mouseover);
+ }
+
+ if (cursor[SP_KNOT_STATE_DRAGGING]) {
+ g_object_unref(cursor[SP_KNOT_STATE_DRAGGING]);
+ }
+
+ cursor[SP_KNOT_STATE_DRAGGING] = dragging;
+
+ if (dragging) {
+ g_object_ref(dragging);
+ }
+
+ if (cursor[SP_KNOT_STATE_SELECTED]) {
+ g_object_unref(cursor[SP_KNOT_STATE_SELECTED]);
+ }
+
+ cursor[SP_KNOT_STATE_SELECTED] = selected;
+
+ if (selected) {
+ g_object_ref(selected);
+ }
+}
+
+/*
+ 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 :