diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 18:24:48 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 18:24:48 +0000 |
commit | cca66b9ec4e494c1d919bff0f71a820d8afab1fa (patch) | |
tree | 146f39ded1c938019e1ed42d30923c2ac9e86789 /src/ui/knot/knot.cpp | |
parent | Initial commit. (diff) | |
download | inkscape-upstream.tar.xz inkscape-upstream.zip |
Adding upstream version 1.2.2.upstream/1.2.2upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r-- | src/ui/knot/knot.cpp | 520 |
1 files changed, 520 insertions, 0 deletions
diff --git a/src/ui/knot/knot.cpp b/src/ui/knot/knot.cpp new file mode 100644 index 0000000..d0b2e0c --- /dev/null +++ b/src/ui/knot/knot.cpp @@ -0,0 +1,520 @@ +// 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 "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 "display/control/canvas-item-ctrl.h" +#include "ui/tools/tool-base.h" +#include "ui/tools/node-tool.h" +#include "ui/widget/canvas.h" + +using Inkscape::DocumentUndo; + +Gdk::EventMask KNOT_EVENT_MASK ( + Gdk::BUTTON_PRESS_MASK | + Gdk::BUTTON_RELEASE_MASK | + Gdk::POINTER_MOTION_MASK | + Gdk::KEY_PRESS_MASK | + Gdk::KEY_RELEASE_MASK); + +const gchar *nograbenv = getenv("INKSCAPE_NO_GRAB"); +static bool nograb = (nograbenv && *nograbenv && (*nograbenv != '0')); + + +void knot_ref(SPKnot* knot) { + knot->ref_count++; +} + +void knot_unref(SPKnot* knot) { + if (--knot->ref_count < 1) { + delete knot; + } +} + +SPKnot::SPKnot(SPDesktop *desktop, gchar const *tip, Inkscape::CanvasItemCtrlType type, Glib::ustring const & name) + : desktop(desktop) + , ref_count(1) +{ + if (tip) { + this->tip = g_strdup (tip); + } + + fill[SP_KNOT_STATE_NORMAL] = 0xffffff00; + fill[SP_KNOT_STATE_MOUSEOVER] = 0xff0000ff; + fill[SP_KNOT_STATE_DRAGGING] = 0xff0000ff; + fill[SP_KNOT_STATE_SELECTED] = 0x0000ffff; + + stroke[SP_KNOT_STATE_NORMAL] = 0x01000000; + stroke[SP_KNOT_STATE_MOUSEOVER] = 0x01000000; + stroke[SP_KNOT_STATE_DRAGGING] = 0x01000000; + stroke[SP_KNOT_STATE_SELECTED] = 0x01000000; + + image[SP_KNOT_STATE_NORMAL] = nullptr; + image[SP_KNOT_STATE_MOUSEOVER] = nullptr; + image[SP_KNOT_STATE_DRAGGING] = nullptr; + image[SP_KNOT_STATE_SELECTED] = nullptr; + + ctrl = new Inkscape::CanvasItemCtrl(desktop->getCanvasControls(), type); // Shape, mode set + Glib::ustring ctrl_name = "CanvasItemCtrl:Knot: " + name; + ctrl->set_name(ctrl_name); + + // Are these needed? + ctrl->set_fill(0xffffff00); + ctrl->set_stroke(0x01000000); + + _event_connection = ctrl->connect_event(sigc::mem_fun(this, &SPKnot::eventHandler)); + + 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 (ctrl) { + delete ctrl; + } + + if (this->tip) { + g_free(this->tip); + this->tip = nullptr; + } + + // FIXME: cannot snap to destroyed knot (lp:1309050) + // this->desktop->event_context->discard_delayed_snap_event(); + 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 && ctrl) { + ctrl->grab(KNOT_EVENT_MASK, _cursors[SP_KNOT_STATE_DRAGGING]); + } + this->setFlag(SP_KNOT_GRABBED, true); + + grabbed = true; +} + +void SPKnot::selectKnot(bool select){ + setFlag(SP_KNOT_SELECTED, select); +} + +bool SPKnot::eventHandler(GdkEvent *event) +{ + /* Run client universal event handler, if present */ + bool consumed = event_signal.emit(this, event); + if (consumed) { + return true; + } + + bool key_press_event_unconsumed = false; + + ref_count++; + + 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) { + doubleclicked_signal.emit(this, event->button.state); + + grabbed = false; + moved = false; + consumed = true; + } + break; + case GDK_BUTTON_PRESS: + if ((event->button.button == 1) && desktop && desktop->event_context && !desktop->event_context->is_space_panning()) { + Geom::Point const p = desktop->w2d(Geom::Point(event->button.x, event->button.y)); + startDragging(p, (gint) event->button.x, (gint) event->button.y, event->button.time); + mousedown_signal.emit(this, event->button.state); + consumed = true; + } + break; + case GDK_BUTTON_RELEASE: + if (event->button.button == 1 && + desktop && + desktop->event_context && + !desktop->event_context->is_space_panning()) { + + // If we have any pending snap event, then invoke it now + if (desktop->event_context->_delayed_snap_event) { + sp_event_context_snap_watchdog_callback(desktop->event_context->_delayed_snap_event); + } + desktop->event_context->discard_delayed_snap_event(); + pressure = 0; + + if (transform_escaped) { + transform_escaped = false; + consumed = true; + } else { + setFlag(SP_KNOT_GRABBED, false); + + if (!nograb && ctrl) { + ctrl->ungrab(); + } + + if (moved) { + setFlag(SP_KNOT_DRAGGING, false); + ungrabbed_signal.emit(this, event->button.state); + } else { + click_signal.emit(this, event->button.state); + } + + grabbed = false; + moved = false; + consumed = true; + } + } + Inkscape::UI::Tools::sp_update_helperpath(desktop); + break; + + case GDK_MOTION_NOTIFY: + + if (!(event->motion.state & GDK_BUTTON1_MASK) && flags & SP_KNOT_DRAGGING) { + pressure = 0; + + if (transform_escaped) { + transform_escaped = false; + consumed = true; + } else { + setFlag(SP_KNOT_GRABBED, false); + + if (!nograb && ctrl) { + ctrl->ungrab(); + } + + if (moved) { + setFlag(SP_KNOT_DRAGGING, false); + ungrabbed_signal.emit(this, event->motion.state); + } else { + click_signal.emit(this, event->motion.state); + } + + grabbed = false; + moved = false; + consumed = true; + Inkscape::UI::Tools::sp_update_helperpath(desktop); + } + } else if (grabbed && desktop && desktop->event_context && + !desktop->event_context->is_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, &pressure)) { + pressure = CLAMP (pressure, 0, 1); + } else { + pressure = 0.5; + } + + if (!moved) { + setFlag(SP_KNOT_DRAGGING, true); + grabbed_signal.emit(this, event->button.state); + } + + sp_event_context_snap_delay_handler(desktop->event_context, nullptr, this, (GdkEventMotion *)event, Inkscape::UI::Tools::DelayedSnapEvent::KNOT_HANDLER); + sp_knot_handler_request_position(event, this); + moved = true; + } + break; + case GDK_ENTER_NOTIFY: + setFlag(SP_KNOT_MOUSEOVER, true); + setFlag(SP_KNOT_GRABBED, false); + + if (tip && desktop && desktop->event_context) { + desktop->event_context->defaultMessageContext()->set(Inkscape::NORMAL_MESSAGE, tip); + } + desktop->event_context->use_cursor(_cursors[SP_KNOT_STATE_MOUSEOVER]); + + grabbed = false; + moved = false; + consumed = true; + break; + case GDK_LEAVE_NOTIFY: + setFlag(SP_KNOT_MOUSEOVER, false); + setFlag(SP_KNOT_GRABBED, false); + + if (tip && desktop && desktop->event_context) { + desktop->event_context->defaultMessageContext()->clear(); + } + desktop->event_context->use_cursor(_cursors[SP_KNOT_STATE_NORMAL]); + + 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: + setFlag(SP_KNOT_GRABBED, false); + + if (!nograb && ctrl) { + ctrl->ungrab(); + } + + if (moved) { + setFlag(SP_KNOT_DRAGGING, false); + + ungrabbed_signal.emit(this, event->button.state); + + DocumentUndo::undo(desktop->getDocument()); + desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("Node or handle drag canceled.")); + transform_escaped = true; + consumed = true; + } + + grabbed = false; + moved = false; + + desktop->event_context->discard_delayed_snap_event(); + break; + default: + consumed = false; + key_press_event_unconsumed = true; + break; + } + break; + default: + break; + } + + knot_unref(this); + + 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) { + knot->desktop->canvas->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 (ctrl) { + ctrl->set_position(p); + } + + this->moved_signal.emit(this, p, state); +} + +void SPKnot::moveto(Geom::Point const &p) { + this->pos = p; + + if (ctrl) { + ctrl->set_position(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) { + if (ctrl) { + ctrl->show(); + } + } else { + if (ctrl) { + ctrl->hide(); + } + } + 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; + } +} + +// TODO: Look at removing this and setting ctrl parameters directly. +void SPKnot::updateCtrl() { + + if (ctrl) { + if (shape_set) { + ctrl->set_shape(shape); + } + ctrl->set_mode(mode); + if (size_set) { + ctrl->set_size(size); + } + ctrl->set_angle(angle); + ctrl->set_anchor(anchor); + ctrl->set_pixbuf(static_cast<GdkPixbuf *>(pixbuf)); + } + + _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; + } + if (ctrl) { + ctrl->set_fill(fill[state]); + ctrl->set_stroke(stroke[state]); + } +} + + +void SPKnot::setSize(guint i) { + size = i; + size_set = true; +} + +void SPKnot::setShape(Inkscape::CanvasItemCtrlShape s) { + shape = s; + shape_set = true; +} + +void SPKnot::setAnchor(guint i) { + anchor = (SPAnchorType) i; +} + +void SPKnot::setMode(Inkscape::CanvasItemCtrlMode m) { + mode = m; +} + +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(SPKnotStateType type, Glib::RefPtr<Gdk::Cursor> cursor) +{ + _cursors[type] = cursor; +} + +/* + 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 : |