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 | |
parent | Initial commit. (diff) | |
download | inkscape-cca66b9ec4e494c1d919bff0f71a820d8afab1fa.tar.xz inkscape-cca66b9ec4e494c1d919bff0f71a820d8afab1fa.zip |
Adding upstream version 1.2.2.upstream/1.2.2upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/ui/knot')
-rw-r--r-- | src/ui/knot/README | 62 | ||||
-rw-r--r-- | src/ui/knot/knot-enums.h | 49 | ||||
-rw-r--r-- | src/ui/knot/knot-holder-entity.cpp | 454 | ||||
-rw-r--r-- | src/ui/knot/knot-holder-entity.h | 205 | ||||
-rw-r--r-- | src/ui/knot/knot-holder.cpp | 455 | ||||
-rw-r--r-- | src/ui/knot/knot-holder.h | 116 | ||||
-rw-r--r-- | src/ui/knot/knot-ptr.cpp | 34 | ||||
-rw-r--r-- | src/ui/knot/knot-ptr.h | 17 | ||||
-rw-r--r-- | src/ui/knot/knot.cpp | 520 | ||||
-rw-r--r-- | src/ui/knot/knot.h | 208 |
10 files changed, 2120 insertions, 0 deletions
diff --git a/src/ui/knot/README b/src/ui/knot/README new file mode 100644 index 0000000..7a76593 --- /dev/null +++ b/src/ui/knot/README @@ -0,0 +1,62 @@ + + +This directory contains code related to on-screen editing knots. + +Note that there are classes with similar functionality based on the ControlPoint class in src/ui/tool. + +Classes here: + + * SPKnot A class that describes a knot (size, type, position, state, etc.) with some signals. + + * KnotHolderEntity A class that has an SPKnot with some signals connections. + + Derived classes: + + LPEKnotHolderEntity + PatternKnotHolderEntity + HatchKnotHolderEntity + FilterKnotHolderEntity + RectKnotHolderEntityRX + RectKnotHolderEntityRY + RectKnotHolderEntityWH + RectKnotHolderEntityXY + RectKnotHolderEntityCenter + Box3DKnotHolderEntity + Box3DKnotHolderEntityCenter + ArcKnotHolderEntityStart + ArcKnotHolderEntityEnd + ArcKnotHolderEntityRX + ArcKnotHolderEntityRY + ArcKnotHolderEntityCenter + StarKnotHolderEntity1 + StarKnotHolderEntity2 + StarKnotHolderEntityCenter + SpiralKnotHolderEntityInner + SpiralKnotHolderEntityOuter + SpiralKnotHolderEntityCenter + OffsetKnotHolderEntity + TextKnotHolderEntityInlineSize + TextKnotHolderEntityShapeInside + FilletChamferKnotHolderEntity + TransformedPointParamKnotHolderEntity_Vector + PowerStrokePointArrayParamKnotHolderEntity + PowerStrokePointArrayParamKnotHolderEntity + PointParamKnotHolderEntity + VectorParamKnotHolderEntity_Origin + VectorParamKnotHolderEntity_Vector + + And many classes derived from above! + + * KnotHolder A class that has one or more overlapping knots via KnotHolderEntity's. + + Derived classes: + + ArcKnotHolder + Box3DKnotHolder + FlowtextKnotHolder + MiscKnotHolder + OffsetKnotHolder + RectKnotHolder + SpiralKnotHolder + StarKnotHolder + TextKnotHolder diff --git a/src/ui/knot/knot-enums.h b/src/ui/knot/knot-enums.h new file mode 100644 index 0000000..4f12cd7 --- /dev/null +++ b/src/ui/knot/knot-enums.h @@ -0,0 +1,49 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#ifndef SEEN_KNOT_ENUMS_H +#define SEEN_KNOT_ENUMS_H + +/** + * @file + * Some enums used by SPKnot and by related types \& functions. + */ +/* + * Authors: + * Lauris Kaplinski <lauris@kaplinski.com> + * + * Copyright (C) 1999-2002 authors + * Copyright (C) 2001-2002 Ximian, Inc. + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +enum SPKnotStateType { + SP_KNOT_STATE_NORMAL, + SP_KNOT_STATE_MOUSEOVER, + SP_KNOT_STATE_DRAGGING, + SP_KNOT_STATE_SELECTED, + SP_KNOT_STATE_HIDDEN +}; + +#define SP_KNOT_VISIBLE_STATES 4 + +enum { + SP_KNOT_VISIBLE = 1 << 0, + SP_KNOT_MOUSEOVER = 1 << 1, + SP_KNOT_DRAGGING = 1 << 2, + SP_KNOT_GRABBED = 1 << 3, + SP_KNOT_SELECTED = 1 << 4 +}; + + +#endif /* !SEEN_KNOT_ENUMS_H */ + +/* + 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 : diff --git a/src/ui/knot/knot-holder-entity.cpp b/src/ui/knot/knot-holder-entity.cpp new file mode 100644 index 0000000..a339539 --- /dev/null +++ b/src/ui/knot/knot-holder-entity.cpp @@ -0,0 +1,454 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * KnotHolderEntity definition. + * + * Authors: + * Mitsuru Oka <oka326@parkcity.ne.jp> + * Maximilian Albert <maximilian.albert@gmail.com> + * Abhishek Sharma + * + * Copyright (C) 1999-2001 Lauris Kaplinski + * Copyright (C) 2000-2001 Ximian, Inc. + * Copyright (C) 2001 Mitsuru Oka + * Copyright (C) 2004 Monash University + * Copyright (C) 2008 Maximilian Albert + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "knot-holder-entity.h" + +#include "knot-holder.h" + +#include "desktop.h" +#include "inkscape.h" +#include "preferences.h" +#include "snap.h" +#include "style.h" + +#include "live_effects/effect.h" +#include "object/sp-hatch.h" +#include "object/sp-item.h" +#include "object/sp-namedview.h" +#include "object/sp-pattern.h" +#include "object/sp-marker.h" + +#include "display/control/canvas-item-ctrl.h" + +void KnotHolderEntity::create(SPDesktop *desktop, SPItem *item, KnotHolder *parent, + Inkscape::CanvasItemCtrlType type, + Glib::ustring const & name, + const gchar *tip, guint32 color) +{ + if (!desktop) { + desktop = parent->getDesktop(); + } + + g_assert(item == parent->getItem()); + g_assert(desktop && desktop == parent->getDesktop()); + g_assert(knot == nullptr); + + parent_holder = parent; + this->item = item; // TODO: remove the item either from here or from knotholder.cpp + this->desktop = desktop; + + my_counter = KnotHolderEntity::counter++; + + knot = new SPKnot(desktop, tip, type, name); + knot->fill [SP_KNOT_STATE_NORMAL] = color; + knot->ctrl->set_fill(color); + update_knot(); + knot->show(); + + _mousedown_connection = knot->mousedown_signal.connect(sigc::mem_fun(*parent_holder, &KnotHolder::knot_mousedown_handler)); + _moved_connection = knot->moved_signal.connect(sigc::mem_fun(*parent_holder, &KnotHolder::knot_moved_handler)); + _click_connection = knot->click_signal.connect(sigc::mem_fun(*parent_holder, &KnotHolder::knot_clicked_handler)); + _ungrabbed_connection = knot->ungrabbed_signal.connect(sigc::mem_fun(*parent_holder, &KnotHolder::knot_ungrabbed_handler)); +} + +KnotHolderEntity::~KnotHolderEntity() +{ + _mousedown_connection.disconnect(); + _moved_connection.disconnect(); + _click_connection.disconnect(); + _ungrabbed_connection.disconnect(); + + /* unref should call destroy */ + if (knot) { + //g_object_unref(knot); + knot_unref(knot); + } else { + // FIXME: This shouldn't occur. Perhaps it is caused by LPE PointParams being knotholder entities, too + // If so, it will likely be fixed with upcoming refactoring efforts. + g_return_if_fail(knot); + } +} + +void +KnotHolderEntity::update_knot() +{ + Geom::Point knot_pos(knot_get()); + if (knot_pos.isFinite()) { + Geom::Point dp(knot_pos * parent_holder->getEditTransform() * item->i2dt_affine()); + + _moved_connection.block(); + knot->setPosition(dp, SP_KNOT_STATE_NORMAL); + _moved_connection.unblock(); + } else { + // knot coords are non-finite, hide knot + knot->hide(); + } +} + +Geom::Point +KnotHolderEntity::snap_knot_position(Geom::Point const &p, guint state) +{ + if (state & GDK_SHIFT_MASK) { // Don't snap when shift-key is held + return p; + } + + Geom::Affine const i2dt (parent_holder->getEditTransform() * item->i2dt_affine()); + Geom::Point s = p * i2dt; + + if (!desktop) std::cout << "No desktop" << std::endl; + if (!desktop->namedview) std::cout << "No named view" << std::endl; + SnapManager &m = desktop->namedview->snap_manager; + m.setup(desktop, true, item); + m.freeSnapReturnByRef(s, Inkscape::SNAPSOURCE_NODE_HANDLE); + m.unSetup(); + + return s * i2dt.inverse(); +} + +Geom::Point +KnotHolderEntity::snap_knot_position_constrained(Geom::Point const &p, Inkscape::Snapper::SnapConstraint const &constraint, guint state) +{ + if (state & GDK_SHIFT_MASK) { // Don't snap when shift-key is held + return p; + } + + Geom::Affine const i2d (parent_holder->getEditTransform() * item->i2dt_affine()); + Geom::Point s = p * i2d; + + SnapManager &m = desktop->namedview->snap_manager; + m.setup(desktop, true, item); + + // constrainedSnap() will first project the point p onto the constraint line and then try to snap along that line. + // This way the constraint is already enforced, no need to worry about that later on + Inkscape::Snapper::SnapConstraint transformed_constraint = Inkscape::Snapper::SnapConstraint(constraint.getPoint() * i2d, (constraint.getPoint() + constraint.getDirection()) * i2d - constraint.getPoint() * i2d); + m.constrainedSnapReturnByRef(s, Inkscape::SNAPSOURCE_NODE_HANDLE, transformed_constraint); + m.unSetup(); + + return s * i2d.inverse(); +} + +void +LPEKnotHolderEntity::knot_ungrabbed(Geom::Point const &p, Geom::Point const &origin, guint state) +{ + Inkscape::LivePathEffect::Effect *effect = _effect; + if (effect) { + effect->refresh_widgets = true; + effect->writeParamsToSVG(); + } +} + +/* Pattern manipulation */ + +/* TODO: this pattern manipulation is not able to handle general transformation matrices. Only matrices that are the result of a pure scale times a pure rotation. */ + +void +PatternKnotHolderEntityXY::knot_set(Geom::Point const &p, Geom::Point const &origin, guint state) +{ + // FIXME: this snapping should be done together with knowing whether control was pressed. If GDK_CONTROL_MASK, then constrained snapping should be used. + Geom::Point p_snapped = snap_knot_position(p, state); + + if ( state & GDK_CONTROL_MASK ) { + if (fabs((p - origin)[Geom::X]) > fabs((p - origin)[Geom::Y])) { + p_snapped[Geom::Y] = origin[Geom::Y]; + } else { + p_snapped[Geom::X] = origin[Geom::X]; + } + } + + if (state) { + Geom::Point const q = p_snapped - knot_get(); + item->adjust_pattern(Geom::Translate(q), false, _fill ? TRANSFORM_FILL : TRANSFORM_STROKE); + } + + item->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); +} + +static Geom::Point sp_pattern_knot_get(SPPattern const *pat, gdouble x, gdouble y) +{ + return Geom::Point(x, y) * pat->getTransform(); +} + +bool +PatternKnotHolderEntity::knot_missing() const +{ + SPPattern *pat = _pattern(); + return (pat == nullptr); +} + +SPPattern* +PatternKnotHolderEntity::_pattern() const +{ + return _fill ? SP_PATTERN(item->style->getFillPaintServer()) : SP_PATTERN(item->style->getStrokePaintServer()); +} + +Geom::Point +PatternKnotHolderEntityXY::knot_get() const +{ + SPPattern *pat = _pattern(); + return sp_pattern_knot_get(pat, 0, 0); +} + +Geom::Point +PatternKnotHolderEntityAngle::knot_get() const +{ + SPPattern *pat = _pattern(); + return sp_pattern_knot_get(pat, pat->width(), 0); +} + +Geom::Point +PatternKnotHolderEntityScale::knot_get() const +{ + SPPattern *pat = _pattern(); + return sp_pattern_knot_get(pat, pat->width(), pat->height()); +} + +void +PatternKnotHolderEntityAngle::knot_set(Geom::Point const &p, Geom::Point const &/*origin*/, guint state) +{ + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + int const snaps = prefs->getInt("/options/rotationsnapsperpi/value", 12); + + SPPattern *pat = _fill ? SP_PATTERN(item->style->getFillPaintServer()) : SP_PATTERN(item->style->getStrokePaintServer()); + + // get the angle from pattern 0,0 to the cursor pos + Geom::Point transform_origin = sp_pattern_knot_get(pat, 0, 0); + gdouble theta = atan2(p - transform_origin); + gdouble theta_old = atan2(knot_get() - transform_origin); + + if ( state & GDK_CONTROL_MASK ) { + /* Snap theta */ + double snaps_radian = M_PI/snaps; + theta = std::round(theta/snaps_radian) * snaps_radian; + } + + Geom::Affine rot = Geom::Translate(-transform_origin) + * Geom::Rotate(theta - theta_old) + * Geom::Translate(transform_origin); + item->adjust_pattern(rot, false, _fill ? TRANSFORM_FILL : TRANSFORM_STROKE); + item->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); +} + +void +PatternKnotHolderEntityScale::knot_set(Geom::Point const &p, Geom::Point const &origin, guint state) +{ + SPPattern *pat = _pattern(); + + // FIXME: this snapping should be done together with knowing whether control was pressed. If GDK_CONTROL_MASK, then constrained snapping should be used. + Geom::Point p_snapped = snap_knot_position(p, state); + + // Get the new scale from the position of the knotholder + Geom::Affine transform = pat->getTransform(); + Geom::Affine transform_inverse = transform.inverse(); + Geom::Point d = p_snapped * transform_inverse; + Geom::Point d_origin = origin * transform_inverse; + Geom::Point origin_dt; + gdouble pat_x = pat->width(); + gdouble pat_y = pat->height(); + if ( state & GDK_CONTROL_MASK ) { + // if ctrl is pressed: use 1:1 scaling + d = d_origin * (d.length() / d_origin.length()); + } + + Geom::Affine rot = Geom::Translate(-origin_dt) + * Geom::Scale(d.x() / pat_x, d.y() / pat_y) + * Geom::Translate(origin_dt) + * transform; + + item->adjust_pattern(rot, true, _fill ? TRANSFORM_FILL : TRANSFORM_STROKE); + item->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); +} + +/* Hatch manipulation */ +bool HatchKnotHolderEntity::knot_missing() const +{ + SPHatch *hatch = _hatch(); + return (hatch == nullptr); +} + +SPHatch *HatchKnotHolderEntity::_hatch() const +{ + return _fill ? SP_HATCH(item->style->getFillPaintServer()) : SP_HATCH(item->style->getStrokePaintServer()); +} + +static Geom::Point sp_hatch_knot_get(SPHatch const *hatch, gdouble x, gdouble y) +{ + return Geom::Point(x, y) * hatch->hatchTransform(); +} + +Geom::Point HatchKnotHolderEntityXY::knot_get() const +{ + SPHatch *hatch = _hatch(); + return sp_hatch_knot_get(hatch, 0, 0); +} + +Geom::Point HatchKnotHolderEntityAngle::knot_get() const +{ + SPHatch *hatch = _hatch(); + return sp_hatch_knot_get(hatch, hatch->pitch(), 0); +} + +Geom::Point HatchKnotHolderEntityScale::knot_get() const +{ + SPHatch *hatch = _hatch(); + return sp_hatch_knot_get(hatch, hatch->pitch(), hatch->pitch()); +} + +void HatchKnotHolderEntityXY::knot_set(Geom::Point const &p, Geom::Point const &origin, unsigned int state) +{ + Geom::Point p_snapped = snap_knot_position(p, state); + + if (state & GDK_CONTROL_MASK) { + if (fabs((p - origin)[Geom::X]) > fabs((p - origin)[Geom::Y])) { + p_snapped[Geom::Y] = origin[Geom::Y]; + } else { + p_snapped[Geom::X] = origin[Geom::X]; + } + } + + if (state) { + Geom::Point const q = p_snapped - knot_get(); + item->adjust_hatch(Geom::Translate(q), false, _fill ? TRANSFORM_FILL : TRANSFORM_STROKE); + } + + item->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); +} + +void HatchKnotHolderEntityAngle::knot_set(Geom::Point const &p, Geom::Point const &origin, unsigned int state) +{ + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + int const snaps = prefs->getInt("/options/rotationsnapsperpi/value", 12); + + SPHatch *hatch = _hatch(); + + // get the angle from hatch 0,0 to the cursor pos + Geom::Point transform_origin = sp_hatch_knot_get(hatch, 0, 0); + gdouble theta = atan2(p - transform_origin); + gdouble theta_old = atan2(knot_get() - transform_origin); + + if (state & GDK_CONTROL_MASK) { + /* Snap theta */ + double snaps_radian = M_PI/snaps; + theta = std::round(theta/snaps_radian) * snaps_radian; + } + + Geom::Affine rot = + Geom::Translate(-transform_origin) * Geom::Rotate(theta - theta_old) * Geom::Translate(transform_origin); + item->adjust_hatch(rot, false, _fill ? TRANSFORM_FILL : TRANSFORM_STROKE); + item->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); +} + +void HatchKnotHolderEntityScale::knot_set(Geom::Point const &p, Geom::Point const &origin, unsigned int state) +{ + SPHatch *hatch = _hatch(); + + // FIXME: this snapping should be done together with knowing whether control was pressed. + // If GDK_CONTROL_MASK, then constrained snapping should be used. + Geom::Point p_snapped = snap_knot_position(p, state); + + // Get the new scale from the position of the knotholder + Geom::Affine transform = hatch->hatchTransform(); + Geom::Affine transform_inverse = transform.inverse(); + Geom::Point d = p_snapped * transform_inverse; + Geom::Point d_origin = origin * transform_inverse; + Geom::Point origin_dt; + gdouble hatch_pitch = hatch->pitch(); + if (state & GDK_CONTROL_MASK) { + // if ctrl is pressed: use 1:1 scaling + d = d_origin * (d.length() / d_origin.length()); + } + + Geom::Affine scale = Geom::Translate(-origin_dt) * Geom::Scale(d.x() / hatch_pitch, d.y() / hatch_pitch) * + Geom::Translate(origin_dt) * transform; + + item->adjust_hatch(scale, true, _fill ? TRANSFORM_FILL : TRANSFORM_STROKE); + item->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); +} + +/* Filter manipulation */ +void FilterKnotHolderEntity::knot_set(Geom::Point const &p, Geom::Point const &origin, unsigned int state) +{ + // FIXME: this snapping should be done together with knowing whether control was pressed. If GDK_CONTROL_MASK, then constrained snapping should be used. + Geom::Point p_snapped = snap_knot_position(p, state); + + if ( state & GDK_CONTROL_MASK ) { + if (fabs((p - origin)[Geom::X]) > fabs((p - origin)[Geom::Y])) { + p_snapped[Geom::Y] = origin[Geom::Y]; + } else { + p_snapped[Geom::X] = origin[Geom::X]; + } + } + + if (state) { + SPFilter *filter = (item->style) ? item->style->getFilter() : nullptr; + if(!filter) return; + Geom::OptRect orig_bbox = item->visualBounds(); + std::unique_ptr<Geom::Rect> new_bbox(_topleft ? new Geom::Rect(p,orig_bbox->max()) : new Geom::Rect(orig_bbox->min(), p)); + + if (!filter->width._set) { + filter->width.set(SVGLength::PERCENT, 1.2); + } + if (!filter->height._set) { + filter->height.set(SVGLength::PERCENT, 1.2); + } + if (!filter->x._set) { + filter->x.set(SVGLength::PERCENT, -0.1); + } + if (!filter->y._set) { + filter->y.set(SVGLength::PERCENT, -0.1); + } + + if(_topleft) { + float x_a = filter->width.computed; + float y_a = filter->height.computed; + filter->height.scale(new_bbox->height()/orig_bbox->height()); + filter->width.scale(new_bbox->width()/orig_bbox->width()); + float x_b = filter->width.computed; + float y_b = filter->height.computed; + filter->x.set(filter->x.unit, filter->x.computed + x_a - x_b); + filter->y.set(filter->y.unit, filter->y.computed + y_a - y_b); + } else { + filter->height.scale(new_bbox->height()/orig_bbox->height()); + filter->width.scale(new_bbox->width()/orig_bbox->width()); + } + filter->auto_region = false; + filter->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + + } + + item->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); +} + +Geom::Point FilterKnotHolderEntity::knot_get() const +{ + SPFilter *filter = (item->style) ? item->style->getFilter() : nullptr; + if(!filter) return Geom::Point(Geom::infinity(), Geom::infinity()); + Geom::OptRect r = item->visualBounds(); + if (_topleft) return Geom::Point(r->min()); + else return Geom::Point(r->max()); +} + +/* + 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 : diff --git a/src/ui/knot/knot-holder-entity.h b/src/ui/knot/knot-holder-entity.h new file mode 100644 index 0000000..3195c2a --- /dev/null +++ b/src/ui/knot/knot-holder-entity.h @@ -0,0 +1,205 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#ifndef SEEN_KNOT_HOLDER_ENTITY_H +#define SEEN_KNOT_HOLDER_ENTITY_H +/* + * Authors: + * Mitsuru Oka <oka326@parkcity.ne.jp> + * Maximilian Albert <maximilian.albert@gmail.com> + * + * Copyright (C) 1999-2001 Lauris Kaplinski + * Copyright (C) 2000-2001 Ximian, Inc. + * Copyright (C) 2001 Mitsuru Oka + * Copyright (C) 2004 Monash University + * Copyright (C) 2008 Maximilian Albert + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include <2geom/forward.h> + +#include "knot.h" +#include "snapper.h" + +#include "display/control/canvas-item-enums.h" + +class SPHatch; +class SPItem; +class SPKnot; +class SPDesktop; +class SPPattern; +class KnotHolder; + +namespace Inkscape { +namespace LivePathEffect { + class Effect; +} // namespace LivePathEffect +} // namespace Inkscape + +typedef void (* SPKnotHolderSetFunc) (SPItem *item, Geom::Point const &p, Geom::Point const &origin, unsigned int state); +typedef Geom::Point (* SPKnotHolderGetFunc) (SPItem *item); + +/** + * KnotHolderEntity definition. + */ +class KnotHolderEntity { +public: + KnotHolderEntity() {} + virtual ~KnotHolderEntity(); + + void create(SPDesktop *desktop, SPItem *item, KnotHolder *parent, + Inkscape::CanvasItemCtrlType type = Inkscape::CANVAS_ITEM_CTRL_TYPE_DEFAULT, + Glib::ustring const & name = Glib::ustring("unknown"), + char const *tip = "", + guint32 color = 0xffffff00); + + /* the get/set/click handlers are virtual functions; each handler class for a knot + should be derived from KnotHolderEntity and override these functions */ + virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, unsigned int state) = 0; + virtual void knot_ungrabbed(Geom::Point const &p, Geom::Point const &origin, unsigned int state) = 0; + virtual bool knot_missing() const { return false; } + virtual Geom::Point knot_get() const = 0; + virtual void knot_click(unsigned int /*state*/) {} + + void update_knot(); + +//private: + Geom::Point snap_knot_position(Geom::Point const &p, unsigned int state); + Geom::Point snap_knot_position_constrained(Geom::Point const &p, Inkscape::Snapper::SnapConstraint const &constraint, unsigned int state); + + SPKnot *knot = nullptr; + SPItem *item = nullptr; + SPDesktop *desktop = nullptr; + KnotHolder *parent_holder = nullptr; + + int my_counter = 0; + inline static int counter = 0; + + /** Connection to \a knot's "moved" signal. */ + unsigned int handler_id = 0; + /** Connection to \a knot's "clicked" signal. */ + unsigned int _click_handler_id = 0; + /** Connection to \a knot's "ungrabbed" signal. */ + unsigned int _ungrab_handler_id = 0; + +private: + sigc::connection _mousedown_connection; + sigc::connection _moved_connection; + sigc::connection _click_connection; + sigc::connection _ungrabbed_connection; +}; + +// derived KnotHolderEntity class for LPEs +class LPEKnotHolderEntity : public KnotHolderEntity { +public: + LPEKnotHolderEntity(Inkscape::LivePathEffect::Effect *effect) : _effect(effect) {}; + void knot_ungrabbed(Geom::Point const &p, Geom::Point const &origin, guint state) override; +protected: + Inkscape::LivePathEffect::Effect *_effect; +}; + +/* pattern manipulation */ + +class PatternKnotHolderEntity : public KnotHolderEntity { + public: + PatternKnotHolderEntity(bool fill) : KnotHolderEntity(), _fill(fill) {} + bool knot_missing() const override; + void knot_ungrabbed(Geom::Point const &p, Geom::Point const &origin, guint state) override{}; + + protected: + // true if the entity tracks fill, false for stroke + bool _fill; + SPPattern *_pattern() const; +}; + +class PatternKnotHolderEntityXY : public PatternKnotHolderEntity { +public: + PatternKnotHolderEntityXY(bool fill) : PatternKnotHolderEntity(fill) {} + Geom::Point knot_get() const override; + void knot_set(Geom::Point const &p, Geom::Point const &origin, unsigned int state) override; +}; + +class PatternKnotHolderEntityAngle : public PatternKnotHolderEntity { +public: + PatternKnotHolderEntityAngle(bool fill) : PatternKnotHolderEntity(fill) {} + Geom::Point knot_get() const override; + void knot_set(Geom::Point const &p, Geom::Point const &origin, unsigned int state) override; +}; + +class PatternKnotHolderEntityScale : public PatternKnotHolderEntity { +public: + PatternKnotHolderEntityScale(bool fill) : PatternKnotHolderEntity(fill) {} + Geom::Point knot_get() const override; + void knot_set(Geom::Point const &p, Geom::Point const &origin, unsigned int state) override; +}; + +/* Hatch manipulation */ +class HatchKnotHolderEntity : public KnotHolderEntity { + public: + HatchKnotHolderEntity(bool fill) + : KnotHolderEntity() + , _fill(fill) + { + } + bool knot_missing() const override; + void knot_ungrabbed(Geom::Point const &p, Geom::Point const &origin, guint state) override{}; + + protected: + // true if the entity tracks fill, false for stroke + bool _fill; + SPHatch *_hatch() const; +}; + +class HatchKnotHolderEntityXY : public HatchKnotHolderEntity { + public: + HatchKnotHolderEntityXY(bool fill) + : HatchKnotHolderEntity(fill) + { + } + Geom::Point knot_get() const override; + void knot_set(Geom::Point const &p, Geom::Point const &origin, unsigned int state) override; +}; + +class HatchKnotHolderEntityAngle : public HatchKnotHolderEntity { + public: + HatchKnotHolderEntityAngle(bool fill) + : HatchKnotHolderEntity(fill) + { + } + Geom::Point knot_get() const override; + void knot_set(Geom::Point const &p, Geom::Point const &origin, unsigned int state) override; +}; + +class HatchKnotHolderEntityScale : public HatchKnotHolderEntity { + public: + HatchKnotHolderEntityScale(bool fill) + : HatchKnotHolderEntity(fill) + { + } + Geom::Point knot_get() const override; + void knot_set(Geom::Point const &p, Geom::Point const &origin, unsigned int state) override; +}; + + +/* Filter manipulation */ +class FilterKnotHolderEntity : public KnotHolderEntity { + public: + FilterKnotHolderEntity(bool topleft) : KnotHolderEntity(), _topleft(topleft) {} + Geom::Point knot_get() const override; + void knot_ungrabbed(Geom::Point const &p, Geom::Point const &origin, guint state) override {}; + void knot_set(Geom::Point const &p, Geom::Point const &origin, unsigned int state) override; +private: + bool _topleft; // true for topleft point, false for bottomright +}; + +#endif /* !SEEN_KNOT_HOLDER_ENTITY_H */ + +/* + 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 : diff --git a/src/ui/knot/knot-holder.cpp b/src/ui/knot/knot-holder.cpp new file mode 100644 index 0000000..a743168 --- /dev/null +++ b/src/ui/knot/knot-holder.cpp @@ -0,0 +1,455 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Container for SPKnot visual handles. + * + * Authors: + * Mitsuru Oka <oka326@parkcity.ne.jp> + * bulia byak <buliabyak@users.sf.net> + * Maximilian Albert <maximilian.albert@gmail.com> + * Abhishek Sharma + * Jon A. Cruz <jon@joncruz.org> + * + * Copyright (C) 2001-2008 authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "knot-holder.h" + +#include "desktop.h" +#include "document-undo.h" +#include "document.h" +#include "knot-holder-entity.h" +#include "knot.h" + +#include "live_effects/effect.h" +#include "live_effects/lpeobject.h" + +#include "object/box3d.h" +#include "object/sp-ellipse.h" +#include "object/sp-hatch.h" +#include "object/sp-offset.h" +#include "object/sp-pattern.h" +#include "object/sp-rect.h" +#include "object/sp-shape.h" +#include "object/sp-spiral.h" +#include "object/sp-star.h" +#include "object/sp-marker.h" +#include "style.h" + +#include "ui/icon-names.h" +#include "ui/shape-editor.h" +#include "ui/tools/arc-tool.h" +#include "ui/tools/node-tool.h" +#include "ui/tools/rect-tool.h" +#include "ui/tools/spiral-tool.h" +#include "ui/tools/tweak-tool.h" + +#include "display/control/snap-indicator.h" + +// TODO due to internal breakage in glibmm headers, this must be last: +#include <glibmm/i18n.h> + +using Inkscape::DocumentUndo; + +class SPDesktop; + +KnotHolder::KnotHolder(SPDesktop *desktop, SPItem *item, SPKnotHolderReleasedFunc relhandler) : + desktop(desktop), + item(item), + //XML Tree being used directly for item->getRepr() while it shouldn't be... + repr(item ? item->getRepr() : nullptr), + entity(), + released(relhandler), + local_change(FALSE), + dragging(false), + _edit_transform(Geom::identity()) +{ + if (!desktop || !item) { + g_print ("Error! Throw an exception, please!\n"); + } + + sp_object_ref(item); +} + +KnotHolder::~KnotHolder() { + sp_object_unref(item); + + for (auto & i : entity) { + delete i; + } + entity.clear(); // is this necessary? +} + +void +KnotHolder::setEditTransform(Geom::Affine edit_transform) +{ + _edit_transform = edit_transform; +} + +void KnotHolder::update_knots() +{ + for (auto e = entity.begin(); e != entity.end(); ) { + // check if pattern was removed without deleting the knot + if ((*e)->knot_missing()) { + delete (*e); + e = entity.erase(e); + } else { + (*e)->update_knot(); + ++e; + } + } +} + +/** + * Returns true if at least one of the KnotHolderEntities has the mouse hovering above it. + */ +bool KnotHolder::knot_mouseover() const { + for (auto i : entity) { + const SPKnot *knot = i->knot; + + if (knot && knot->is_mouseover()) { + return true; + } + } + + return false; +} + +/** + * Returns true if at least one of the KnotHolderEntities is selected + */ +bool KnotHolder::knot_selected() const { + for (auto i : entity) { + const SPKnot *knot = i->knot; + + if (knot && knot->is_selected()) { + return true; + } + } + return false; +} + +void +KnotHolder::knot_mousedown_handler(SPKnot *knot, guint state) +{ + if (!(state & GDK_SHIFT_MASK)) { + unselect_knots(); + } + for(auto e : this->entity) { + if (!(state & GDK_SHIFT_MASK)) { + e->knot->selectKnot(false); + } + if (e->knot == knot) { + if (!(e->knot->is_selected()) || !(state & GDK_SHIFT_MASK)){ + e->knot->selectKnot(true); + } else { + e->knot->selectKnot(false); + } + } + } +} + +void +KnotHolder::knot_clicked_handler(SPKnot *knot, guint state) +{ + SPItem *saved_item = this->item; + + for(auto e : this->entity) { + if (e->knot == knot) + // no need to test whether knot_click exists since it's virtual now + e->knot_click(state); + } + + { + SPShape *savedShape = dynamic_cast<SPShape *>(saved_item); + if (savedShape) { + savedShape->set_shape(); + } + } + + this->update_knots(); + + Glib::ustring icon_name; + + // TODO extract duplicated blocks; + if (dynamic_cast<SPRect *>(saved_item)) { + icon_name = INKSCAPE_ICON("draw-rectangle"); + } else if (dynamic_cast<SPBox3D *>(saved_item)) { + icon_name = INKSCAPE_ICON("draw-cuboid"); + } else if (dynamic_cast<SPGenericEllipse *>(saved_item)) { + icon_name = INKSCAPE_ICON("draw-ellipse"); + } else if (dynamic_cast<SPStar *>(saved_item)) { + icon_name = INKSCAPE_ICON("draw-polygon-star"); + } else if (dynamic_cast<SPSpiral *>(saved_item)) { + icon_name = INKSCAPE_ICON("draw-spiral"); + } else if (dynamic_cast<SPMarker *>(saved_item)) { + icon_name = INKSCAPE_ICON("tool-pointer"); + } else { + SPOffset *offset = dynamic_cast<SPOffset *>(saved_item); + if (offset) { + if (offset->sourceHref) { + icon_name = INKSCAPE_ICON("path-offset-linked"); + } else { + icon_name = INKSCAPE_ICON("path-offset-dynamic"); + } + } + } + + // for drag, this is done by ungrabbed_handler, but for click we must do it here + + if (saved_item) { //increasingly aggressive sanity checks + if (saved_item->document) { + DocumentUndo::done(saved_item->document, _("Change handle"), icon_name); + } + } // else { abort(); } +} + +void +KnotHolder::transform_selected(Geom::Affine transform){ + for (auto & i : entity) { + SPKnot *knot = i->knot; + if (knot->is_selected()) { + knot_moved_handler(knot, knot->pos * transform , 0); + knot->selectKnot(true); + } + } +} + +void +KnotHolder::unselect_knots(){ + Inkscape::UI::Tools::NodeTool *nt = dynamic_cast<Inkscape::UI::Tools::NodeTool*>(desktop->event_context); + if (nt) { + for (auto &_shape_editor : nt->_shape_editors) { + Inkscape::UI::ShapeEditor *shape_editor = _shape_editor.second.get(); + if (shape_editor && shape_editor->has_knotholder()) { + KnotHolder * knotholder = shape_editor->knotholder; + if (knotholder) { + for (auto e : knotholder->entity) { + if (e->knot->is_selected()) { + e->knot->selectKnot(false); + } + } + } + } + } + } +} + +void +KnotHolder::knot_moved_handler(SPKnot *knot, Geom::Point const &p, guint state) +{ + if (this->dragging == false) { + this->dragging = true; + } + + // this was a local change and the knotholder does not need to be recreated: + this->local_change = TRUE; + + for(auto e : this->entity) { + if (e->knot == knot) { + Geom::Point const q = p * item->i2dt_affine().inverse() * _edit_transform.inverse(); + e->knot_set(q, e->knot->drag_origin * item->i2dt_affine().inverse() * _edit_transform.inverse(), state); + break; + } + } + + SPShape *shape = dynamic_cast<SPShape *>(item); + if (shape) { + shape->set_shape(); + } + + this->update_knots(); +} + +void +KnotHolder::knot_ungrabbed_handler(SPKnot *knot, guint state) +{ + this->dragging = false; + desktop->snapindicator->remove_snaptarget(); + + if (this->released) { + this->released(this->item); + } else { + // if a point is dragged while not selected, it should select itself, + // even if it was just unselected in the mousedown event handler. + if (!(knot->is_selected())) { + knot->selectKnot(true); + } else { + for(auto e : this->entity) { + if (e->knot == knot) { + e->knot_ungrabbed(e->knot->position(), e->knot->drag_origin * item->i2dt_affine().inverse() * _edit_transform.inverse(), state); + break; + } + } + } + + SPObject *object = (SPObject *) this->item; + + // Caution: this call involves a screen update, which may process events, and as a + // result the knotholder may be destructed. So, after the updateRepr, we cannot use any + // fields of this knotholder (such as this->item), but only values we have saved beforehand + // (such as object). + object->updateRepr(); + + /* do cleanup tasks (e.g., for LPE items write the parameter values + * that were changed by dragging the handle to SVG) + */ + SPLPEItem *lpeItem = dynamic_cast<SPLPEItem *>(object); + if (lpeItem) { + // This writes all parameters to SVG. Is this sufficiently efficient or should we only + // write the ones that were changed? + Inkscape::LivePathEffect::Effect *lpe = lpeItem->getCurrentLPE(); + if (lpe) { + LivePathEffectObject *lpeobj = lpe->getLPEObj(); + lpeobj->updateRepr(); + } + } + + SPFilter *filter = (object->style) ? object->style->getFilter() : nullptr; + if (filter) { + filter->updateRepr(); + } + + Glib::ustring icon_name; + + // TODO extract duplicated blocks; + if (dynamic_cast<SPRect *>(object)) { + icon_name = INKSCAPE_ICON("draw-rectangle"); + } else if (dynamic_cast<SPBox3D *>(object)) { + icon_name = INKSCAPE_ICON("draw-cuboid"); + } else if (dynamic_cast<SPGenericEllipse *>(object)) { + icon_name = INKSCAPE_ICON("draw-ellipse"); + } else if (dynamic_cast<SPStar *>(object)) { + icon_name = INKSCAPE_ICON("draw-polygon-star"); + } else if (dynamic_cast<SPSpiral *>(object)) { + icon_name = INKSCAPE_ICON("draw-spiral"); + } else if (dynamic_cast<SPMarker *>(object)) { + icon_name = INKSCAPE_ICON("tool-pointer"); + } else { + SPOffset *offset = dynamic_cast<SPOffset *>(object); + if (offset) { + if (offset->sourceHref) { + icon_name = INKSCAPE_ICON("path-offset-linked"); + } else { + icon_name = INKSCAPE_ICON("path-offset-dynamic"); + } + } + } + DocumentUndo::done(object->document, _("Move handle"), icon_name); + } +} + +void KnotHolder::add(KnotHolderEntity *e) +{ + // g_message("Adding a knot at %p", e); + entity.push_back(e); +} + +void KnotHolder::add_pattern_knotholder() +{ + if ((item->style->fill.isPaintserver()) && dynamic_cast<SPPattern *>(item->style->getFillPaintServer())) { + PatternKnotHolderEntityXY *entity_xy = new PatternKnotHolderEntityXY(true); + PatternKnotHolderEntityAngle *entity_angle = new PatternKnotHolderEntityAngle(true); + PatternKnotHolderEntityScale *entity_scale = new PatternKnotHolderEntityScale(true); + entity_xy->create(desktop, item, this, Inkscape::CANVAS_ITEM_CTRL_TYPE_POINT, "Pattern:Fill:xy", + // TRANSLATORS: This refers to the pattern that's inside the object + _("<b>Move</b> the pattern fill inside the object")); + + entity_scale->create(desktop, item, this, Inkscape::CANVAS_ITEM_CTRL_TYPE_SIZER, "Pattern:Fill:scale", + _("<b>Scale</b> the pattern fill; uniformly if with <b>Ctrl</b>")); + + entity_angle->create(desktop, item, this, Inkscape::CANVAS_ITEM_CTRL_TYPE_ROTATE, "Pattern:Fill:angle", + _("<b>Rotate</b> the pattern fill; with <b>Ctrl</b> to snap angle")); + + entity.push_back(entity_xy); + entity.push_back(entity_angle); + entity.push_back(entity_scale); + } + + if ((item->style->stroke.isPaintserver()) && dynamic_cast<SPPattern *>(item->style->getStrokePaintServer())) { + PatternKnotHolderEntityXY *entity_xy = new PatternKnotHolderEntityXY(false); + PatternKnotHolderEntityAngle *entity_angle = new PatternKnotHolderEntityAngle(false); + PatternKnotHolderEntityScale *entity_scale = new PatternKnotHolderEntityScale(false); + entity_xy->create(desktop, item, this, Inkscape::CANVAS_ITEM_CTRL_TYPE_POINT, "Pattern:Stroke:xy", + // TRANSLATORS: This refers to the pattern that's inside the object + _("<b>Move</b> the stroke's pattern inside the object")); + + entity_scale->create(desktop, item, this, Inkscape::CANVAS_ITEM_CTRL_TYPE_SIZER, "Pattern:Stroke:scale", + _("<b>Scale</b> the stroke's pattern; uniformly if with <b>Ctrl</b>")); + + entity_angle->create(desktop, item, this, Inkscape::CANVAS_ITEM_CTRL_TYPE_ROTATE, "Pattern:Stroke:angle", + _("<b>Rotate</b> the stroke's pattern; with <b>Ctrl</b> to snap angle")); + + entity.push_back(entity_xy); + entity.push_back(entity_angle); + entity.push_back(entity_scale); + } +} + +void KnotHolder::add_hatch_knotholder() +{ + if ((item->style->fill.isPaintserver()) && dynamic_cast<SPHatch *>(item->style->getFillPaintServer())) { + HatchKnotHolderEntityXY *entity_xy = new HatchKnotHolderEntityXY(true); + HatchKnotHolderEntityAngle *entity_angle = new HatchKnotHolderEntityAngle(true); + HatchKnotHolderEntityScale *entity_scale = new HatchKnotHolderEntityScale(true); + entity_xy->create(desktop, item, this, Inkscape::CANVAS_ITEM_CTRL_TYPE_POINT, "Hatch:Fill:xy", + // TRANSLATORS: This refers to the hatch that's inside the object + _("<b>Move</b> the hatch fill inside the object")); + + entity_scale->create(desktop, item, this, Inkscape::CANVAS_ITEM_CTRL_TYPE_SIZER, "Hatch:Fill:scale", + _("<b>Scale</b> the hatch fill; uniformly if with <b>Ctrl</b>")); + + entity_angle->create(desktop, item, this, Inkscape::CANVAS_ITEM_CTRL_TYPE_ROTATE, "Hatch:Fill:angle", + _("<b>Rotate</b> the hatch fill; with <b>Ctrl</b> to snap angle")); + + entity.push_back(entity_xy); + entity.push_back(entity_angle); + entity.push_back(entity_scale); + } + + if ((item->style->stroke.isPaintserver()) && dynamic_cast<SPHatch *>(item->style->getStrokePaintServer())) { + HatchKnotHolderEntityXY *entity_xy = new HatchKnotHolderEntityXY(false); + HatchKnotHolderEntityAngle *entity_angle = new HatchKnotHolderEntityAngle(false); + HatchKnotHolderEntityScale *entity_scale = new HatchKnotHolderEntityScale(false); + entity_xy->create(desktop, item, this, Inkscape::CANVAS_ITEM_CTRL_TYPE_POINT, "Hatch:Stroke:xy", + // TRANSLATORS: This refers to the pattern that's inside the object + _("<b>Move</b> the hatch stroke inside the object")); + + entity_scale->create(desktop, item, this, Inkscape::CANVAS_ITEM_CTRL_TYPE_SIZER, "Hatch:Stroke:scale", + _("<b>Scale</b> the hatch stroke; uniformly if with <b>Ctrl</b>")); + + entity_angle->create(desktop, item, this, Inkscape::CANVAS_ITEM_CTRL_TYPE_ROTATE, "Hatch:Stroke:angle", + _("<b>Rotate</b> the hatch stroke; with <b>Ctrl</b> to snap angle")); + + entity.push_back(entity_xy); + entity.push_back(entity_angle); + entity.push_back(entity_scale); + } +} + +void KnotHolder::add_filter_knotholder() { + if (!item->style->filter.set || !item->style->getFilter() || item->style->getFilter()->auto_region) { + return; + } + + FilterKnotHolderEntity *entity_tl = new FilterKnotHolderEntity(true); + FilterKnotHolderEntity *entity_br = new FilterKnotHolderEntity(false); + entity_tl->create(desktop, item, this, Inkscape::CANVAS_ITEM_CTRL_TYPE_POINT, "Filter:TopLeft", + _("<b>Resize</b> the filter effect region")); + entity_br->create(desktop, item, this, Inkscape::CANVAS_ITEM_CTRL_TYPE_POINT, "Filter:BottomRight", + _("<b>Resize</b> the filter effect region")); + entity.push_back(entity_tl); + entity.push_back(entity_br); +} + +/* + 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 : diff --git a/src/ui/knot/knot-holder.h b/src/ui/knot/knot-holder.h new file mode 100644 index 0000000..95c9915 --- /dev/null +++ b/src/ui/knot/knot-holder.h @@ -0,0 +1,116 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#ifndef SEEN_SP_KNOTHOLDER_H +#define SEEN_SP_KNOTHOLDER_H + +/* + * KnotHolder - Hold SPKnot list and manage signals + * + * Author: + * Mitsuru Oka <oka326@parkcity.ne.jp> + * Maximilian Albert <maximilian.albert@gmail.com> + * + * Copyright (C) 1999-2001 Lauris Kaplinski + * Copyright (C) 2000-2001 Ximian, Inc. + * Copyright (C) 2001 Mitsuru Oka + * Copyright (C) 2008 Maximilian Albert + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + * + */ + +#include <2geom/forward.h> +#include <2geom/affine.h> +#include <list> +#include <sigc++/connection.h> + +namespace Inkscape { +namespace UI { +class ShapeEditor; +} +namespace XML { +class Node; +} +namespace LivePathEffect { +class PowerStrokePointArrayParamKnotHolderEntity; +class NodeSatelliteArrayParam; +class FilletChamferKnotHolderEntity; +} +} + +class KnotHolderEntity; +class SPItem; +class SPDesktop; +class SPKnot; + +/* fixme: Think how to make callbacks most sensitive (Lauris) */ +typedef void (* SPKnotHolderReleasedFunc) (SPItem *item); + +class KnotHolder { +public: + KnotHolder(SPDesktop *desktop, SPItem *item, SPKnotHolderReleasedFunc relhandler); + virtual ~KnotHolder(); + + KnotHolder() = delete; // declared but not defined + + void update_knots(); + void unselect_knots(); + void knot_mousedown_handler(SPKnot *knot, unsigned int state); + void knot_moved_handler(SPKnot *knot, Geom::Point const &p, unsigned int state); + void knot_clicked_handler(SPKnot *knot, unsigned int state); + void knot_ungrabbed_handler(SPKnot *knot, unsigned int state); + void transform_selected(Geom::Affine transform); + void add(KnotHolderEntity *e); + + void add_pattern_knotholder(); + void add_hatch_knotholder(); + void add_filter_knotholder(); + + void setEditTransform(Geom::Affine edit_transform); + Geom::Affine getEditTransform() const { return _edit_transform; } + + bool knot_selected() const; + bool knot_mouseover() const; + + SPDesktop *getDesktop() { return desktop; } + SPItem *getItem() { return item; } + bool is_dragging() const { return dragging; } + + friend class Inkscape::UI::ShapeEditor; // FIXME why? + friend class Inkscape::LivePathEffect::NodeSatelliteArrayParam; // why? + friend class Inkscape::LivePathEffect::PowerStrokePointArrayParamKnotHolderEntity; // why? + friend class Inkscape::LivePathEffect::FilletChamferKnotHolderEntity; // why? + +protected: + + SPDesktop *desktop; + SPItem *item; // TODO: Remove this and keep the actual item (e.g., SPRect etc.) in the item-specific knotholders + Inkscape::XML::Node *repr; ///< repr of the item, for setting and releasing listeners. + std::list<KnotHolderEntity *> entity; + + SPKnotHolderReleasedFunc released; + + bool local_change; ///< if true, no need to recreate knotholder if repr was changed. + + bool dragging; + + Geom::Affine _edit_transform; +}; + +/** +void knot_clicked_handler(SPKnot *knot, guint state, gpointer data); +void knot_moved_handler(SPKnot *knot, Geom::Point const *p, guint state, gpointer data); +void knot_ungrabbed_handler(SPKnot *knot, unsigned int state, KnotHolder *kh); +**/ + +#endif // SEEN_SP_KNOTHOLDER_H + +/* + 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 : diff --git a/src/ui/knot/knot-ptr.cpp b/src/ui/knot/knot-ptr.cpp new file mode 100644 index 0000000..8e275ac --- /dev/null +++ b/src/ui/knot/knot-ptr.cpp @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** @file + * TODO: insert short description here + *//* + * Authors: see git history + * + * Copyright (C) 2018 Authors + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ +#include <algorithm> +#include <glib.h> +#include <list> +#include "knot-ptr.h" + +static std::list<void*> deleted_knots; + +void knot_deleted_callback(void* knot) { + if (std::find(deleted_knots.begin(), deleted_knots.end(), knot) == deleted_knots.end()) { + deleted_knots.push_back(knot); + } +} + +void knot_created_callback(void* knot) { + std::list<void*>::iterator it = std::find(deleted_knots.begin(), deleted_knots.end(), knot); + if (it != deleted_knots.end()) { + deleted_knots.erase(it); + } +} + +void check_if_knot_deleted(void* knot) { + if (std::find(deleted_knots.begin(), deleted_knots.end(), knot) != deleted_knots.end()) { + g_warning("Accessed knot after it was freed at %p", knot); + } +} diff --git a/src/ui/knot/knot-ptr.h b/src/ui/knot/knot-ptr.h new file mode 100644 index 0000000..5141822 --- /dev/null +++ b/src/ui/knot/knot-ptr.h @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** @file + * TODO: insert short description here + *//* + * Authors: see git history + * + * Copyright (C) 2014 Authors + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ +#ifndef KNOT_PTR_DETECTOR +#define KNOT_PTR_DETECTOR + +void knot_deleted_callback(void* knot); +void knot_created_callback(void* knot); +void check_if_knot_deleted(void* knot); + +#endif 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 : diff --git a/src/ui/knot/knot.h b/src/ui/knot/knot.h new file mode 100644 index 0000000..59104e7 --- /dev/null +++ b/src/ui/knot/knot.h @@ -0,0 +1,208 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#ifndef SEEN_SP_KNOT_H +#define SEEN_SP_KNOT_H + +/** \file + * Declarations for SPKnot: Desktop-bound visual control object. + */ +/* + * Authors: + * Lauris Kaplinski <lauris@kaplinski.com> + * + * Copyright (C) 1999-2002 authors + * Copyright (C) 2001-2002 Ximian, Inc. + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include <2geom/point.h> +#include <sigc++/sigc++.h> +#include <glibmm/ustring.h> +#include <glibmm/refptr.h> +#include <gdkmm/cursor.h> + +#include "knot-enums.h" +#include "display/control/canvas-item-enums.h" +#include "enums.h" + +class SPDesktop; +class SPItem; + +typedef union _GdkEvent GdkEvent; +typedef unsigned int guint32; + +#define SP_KNOT(obj) (dynamic_cast<SPKnot*>(static_cast<SPKnot*>(obj))) +#define SP_IS_KNOT(obj) (dynamic_cast<const SPKnot*>(static_cast<const SPKnot*>(obj)) != NULL) + +namespace Inkscape { +class CanvasItemCtrl; +} + +/** + * Desktop-bound visual control object. + * + * A knot is a draggable object, with callbacks to change something by + * dragging it, visually represented by a canvas item (mostly square). + * + * See also KnotHolderEntity. + * See also ControlPoint (which does the same kind of things). + */ +class SPKnot { +public: + SPKnot(SPDesktop *desktop, char const *tip, Inkscape::CanvasItemCtrlType type, Glib::ustring const & name = Glib::ustring("unknown")); + virtual ~SPKnot(); + + SPKnot(SPKnot const&) = delete; + SPKnot& operator=(SPKnot const&) = delete; + + int ref_count; // FIXME encapsulation + + SPDesktop *desktop = nullptr; /**< Desktop we are on. */ + Inkscape::CanvasItemCtrl *ctrl = nullptr; /**< Our CanvasItemCtrl. */ + SPItem *owner = nullptr; /**< Optional Owner Item */ + SPItem *sub_owner = nullptr; /**< Optional SubOwner Item */ + unsigned int flags = SP_KNOT_VISIBLE; + + unsigned int size = 9; /**< Always square. Must be odd. */ + bool size_set = false; /**< Use default size unless explicitly set. */ + double angle = 0.0; /**< Angle of mesh handle. */ + Geom::Point pos; /**< Our desktop coordinates. */ + Geom::Point grabbed_rel_pos; /**< Grabbed relative position. */ + Geom::Point drag_origin; /**< Origin of drag. */ + SPAnchorType anchor = SP_ANCHOR_CENTER; /**< Anchor. */ + + bool grabbed = false; + bool moved = false; + int xp = 0.0; /**< Where drag started */ + int yp = 0.0; /**< Where drag started */ + int tolerance = 0; + bool within_tolerance = false; + bool transform_escaped = false; // true iff resize or rotate was cancelled by esc. + + Inkscape::CanvasItemCtrlShape shape = Inkscape::CANVAS_ITEM_CTRL_SHAPE_SQUARE; /**< Shape type. */ + bool shape_set = false; /**< Use default shape unless explicitly set. */ + Inkscape::CanvasItemCtrlMode mode = Inkscape::CANVAS_ITEM_CTRL_MODE_XOR; + + guint32 fill[SP_KNOT_VISIBLE_STATES]; + guint32 stroke[SP_KNOT_VISIBLE_STATES]; + unsigned char *image[SP_KNOT_VISIBLE_STATES]; + Glib::RefPtr<Gdk::Cursor> _cursors[SP_KNOT_VISIBLE_STATES]; + + void* pixbuf = nullptr; + char *tip = nullptr; + + sigc::connection _event_connection; + + double pressure = 0.0; /**< The tablet pen pressure when the knot is being dragged. */ + + // FIXME: signals should NOT need to emit the object they came from, the callee should + // be able to figure that out + sigc::signal<void, SPKnot *, unsigned int> click_signal; + sigc::signal<void, SPKnot*, unsigned int> doubleclicked_signal; + sigc::signal<void, SPKnot*, unsigned int> mousedown_signal; + sigc::signal<void, SPKnot*, unsigned int> grabbed_signal; + sigc::signal<void, SPKnot *, unsigned int> ungrabbed_signal; + sigc::signal<void, SPKnot *, Geom::Point const &, unsigned int> moved_signal; + sigc::signal<bool, SPKnot*, GdkEvent*> event_signal; + + sigc::signal<bool, SPKnot*, Geom::Point*, unsigned int> request_signal; + + + //TODO: all the members above should eventualle become private, accessible via setters/getters + void setSize(unsigned int i); + void setShape(Inkscape::CanvasItemCtrlShape s); + void setAnchor(unsigned int i); + void setMode(Inkscape::CanvasItemCtrlMode m); + void setPixbuf(void* p); + void setAngle(double i); + + void setFill(guint32 normal, guint32 mouseover, guint32 dragging, guint32 selected); + void setStroke(guint32 normal, guint32 mouseover, guint32 dragging, guint32 selected); + void setImage(unsigned char* normal, unsigned char* mouseover, unsigned char* dragging, unsigned char* selected); + + void setCursor(SPKnotStateType type, Glib::RefPtr<Gdk::Cursor> cursor); + + /** + * Show knot on its canvas. + */ + void show(); + + /** + * Hide knot on its canvas. + */ + void hide(); + + /** + * Set flag in knot, with side effects. + */ + void setFlag(unsigned int flag, bool set); + + /** + * Update knot's pixbuf and set its control state. + */ + void updateCtrl(); + + /** + * Request or set new position for knot. + */ + void requestPosition(Geom::Point const &pos, unsigned int state); + + /** + * Update knot for dragging and tell canvas an item was grabbed. + */ + void startDragging(Geom::Point const &p, int x, int y, guint32 etime); + + /** + * Move knot to new position and emits "moved" signal. + */ + void setPosition(Geom::Point const &p, unsigned int state); + + /** + * Move knot to new position, without emitting a MOVED signal. + */ + void moveto(Geom::Point const &p); + /** + * Select knot. + */ + void selectKnot(bool select); + + /** + * Returns position of knot. + */ + Geom::Point position() const; + + /** + * Event handler (from CanvasItem's). + */ + bool eventHandler(GdkEvent *event); + + bool is_visible() const { return (flags & SP_KNOT_VISIBLE) != 0; } + bool is_selected() const { return (flags & SP_KNOT_SELECTED) != 0; } + bool is_mouseover() const { return (flags & SP_KNOT_MOUSEOVER) != 0; } + bool is_dragging() const { return (flags & SP_KNOT_DRAGGING) != 0; } + bool is_grabbed() const { return (flags & SP_KNOT_GRABBED) != 0; } + +private: + /** + * Set knot control state (dragging/mouseover/normal). + */ + void _setCtrlState(); +}; + +void knot_ref(SPKnot* knot); +void knot_unref(SPKnot* knot); + +void sp_knot_handler_request_position(GdkEvent *event, SPKnot *knot); + +#endif // SEEN_SP_KNOT_H + +/* + 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 : |