1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3  * KnotHolderEntity definition.
4  *
5  * Authors:
6  *   Mitsuru Oka <oka326@parkcity.ne.jp>
7  *   Maximilian Albert <maximilian.albert@gmail.com>
8  *   Abhishek Sharma
9  *
10  * Copyright (C) 1999-2001 Lauris Kaplinski
11  * Copyright (C) 2000-2001 Ximian, Inc.
12  * Copyright (C) 2001 Mitsuru Oka
13  * Copyright (C) 2004 Monash University
14  * Copyright (C) 2008 Maximilian Albert
15  *
16  * Released under GNU GPL v2+, read the file 'COPYING' for more information.
17  */
18 
19 #include "knot-holder-entity.h"
20 
21 #include "knot-holder.h"
22 
23 #include "desktop.h"
24 #include "inkscape.h"
25 #include "preferences.h"
26 #include "snap.h"
27 #include "style.h"
28 
29 #include "live_effects/effect.h"
30 #include "object/sp-hatch.h"
31 #include "object/sp-item.h"
32 #include "object/sp-namedview.h"
33 #include "object/sp-pattern.h"
34 
35 #include "display/control/canvas-item-ctrl.h"
36 
create(SPDesktop * desktop,SPItem * item,KnotHolder * parent,Inkscape::CanvasItemCtrlType type,Glib::ustring const & name,const gchar * tip,guint32 color)37 void KnotHolderEntity::create(SPDesktop *desktop, SPItem *item, KnotHolder *parent,
38                               Inkscape::CanvasItemCtrlType type,
39                               Glib::ustring const & name,
40                               const gchar *tip, guint32 color)
41 {
42     if (!desktop) {
43         desktop = parent->getDesktop();
44     }
45 
46     g_assert(item == parent->getItem());
47     g_assert(desktop && desktop == parent->getDesktop());
48     g_assert(knot == nullptr);
49 
50     parent_holder = parent;
51     this->item = item; // TODO: remove the item either from here or from knotholder.cpp
52     this->desktop = desktop;
53 
54     my_counter = KnotHolderEntity::counter++;
55 
56     knot = new SPKnot(desktop, tip, type, name);
57     knot->fill [SP_KNOT_STATE_NORMAL] = color;
58     knot->ctrl->set_fill(color);
59     update_knot();
60     knot->show();
61 
62     _mousedown_connection = knot->mousedown_signal.connect(sigc::mem_fun(*parent_holder, &KnotHolder::knot_mousedown_handler));
63     _moved_connection = knot->moved_signal.connect(sigc::mem_fun(*parent_holder, &KnotHolder::knot_moved_handler));
64     _click_connection = knot->click_signal.connect(sigc::mem_fun(*parent_holder, &KnotHolder::knot_clicked_handler));
65     _ungrabbed_connection = knot->ungrabbed_signal.connect(sigc::mem_fun(*parent_holder, &KnotHolder::knot_ungrabbed_handler));
66 }
67 
~KnotHolderEntity()68 KnotHolderEntity::~KnotHolderEntity()
69 {
70     _mousedown_connection.disconnect();
71     _moved_connection.disconnect();
72     _click_connection.disconnect();
73     _ungrabbed_connection.disconnect();
74 
75     /* unref should call destroy */
76     if (knot) {
77         //g_object_unref(knot);
78         knot_unref(knot);
79     } else {
80         // FIXME: This shouldn't occur. Perhaps it is caused by LPE PointParams being knotholder entities, too
81         //        If so, it will likely be fixed with upcoming refactoring efforts.
82         g_return_if_fail(knot);
83     }
84 }
85 
86 void
update_knot()87 KnotHolderEntity::update_knot()
88 {
89     Geom::Point knot_pos(knot_get());
90     if (knot_pos.isFinite()) {
91         Geom::Point dp(knot_pos * parent_holder->getEditTransform() * item->i2dt_affine());
92 
93         _moved_connection.block();
94         knot->setPosition(dp, SP_KNOT_STATE_NORMAL);
95         _moved_connection.unblock();
96     } else {
97         // knot coords are non-finite, hide knot
98         knot->hide();
99     }
100 }
101 
102 Geom::Point
snap_knot_position(Geom::Point const & p,guint state)103 KnotHolderEntity::snap_knot_position(Geom::Point const &p, guint state)
104 {
105     if (state & GDK_SHIFT_MASK) { // Don't snap when shift-key is held
106         return p;
107     }
108 
109     Geom::Affine const i2dt (parent_holder->getEditTransform() * item->i2dt_affine());
110     Geom::Point s = p * i2dt;
111 
112     if (!desktop) std::cout << "No desktop" << std::endl;
113     if (!desktop->namedview) std::cout << "No named view" << std::endl;
114     SnapManager &m = desktop->namedview->snap_manager;
115     m.setup(desktop, true, item);
116     m.freeSnapReturnByRef(s, Inkscape::SNAPSOURCE_NODE_HANDLE);
117     m.unSetup();
118 
119     return s * i2dt.inverse();
120 }
121 
122 Geom::Point
snap_knot_position_constrained(Geom::Point const & p,Inkscape::Snapper::SnapConstraint const & constraint,guint state)123 KnotHolderEntity::snap_knot_position_constrained(Geom::Point const &p, Inkscape::Snapper::SnapConstraint const &constraint, guint state)
124 {
125     if (state & GDK_SHIFT_MASK) { // Don't snap when shift-key is held
126         return p;
127     }
128 
129     Geom::Affine const i2d (parent_holder->getEditTransform() * item->i2dt_affine());
130     Geom::Point s = p * i2d;
131 
132     SnapManager &m = desktop->namedview->snap_manager;
133     m.setup(desktop, true, item);
134 
135     // constrainedSnap() will first project the point p onto the constraint line and then try to snap along that line.
136     // This way the constraint is already enforced, no need to worry about that later on
137     Inkscape::Snapper::SnapConstraint transformed_constraint = Inkscape::Snapper::SnapConstraint(constraint.getPoint() * i2d, (constraint.getPoint() + constraint.getDirection()) * i2d - constraint.getPoint() * i2d);
138     m.constrainedSnapReturnByRef(s, Inkscape::SNAPSOURCE_NODE_HANDLE, transformed_constraint);
139     m.unSetup();
140 
141     return s * i2d.inverse();
142 }
143 
144 void
knot_ungrabbed(Geom::Point const & p,Geom::Point const & origin,guint state)145 LPEKnotHolderEntity::knot_ungrabbed(Geom::Point const &p, Geom::Point const &origin, guint state)
146 {
147     Inkscape::LivePathEffect::Effect *effect = _effect;
148     if (effect) {
149         effect->refresh_widgets = true;
150         effect->writeParamsToSVG();
151     }
152 }
153 
154 /* Pattern manipulation */
155 
156 /*  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. */
157 
158 void
knot_set(Geom::Point const & p,Geom::Point const & origin,guint state)159 PatternKnotHolderEntityXY::knot_set(Geom::Point const &p, Geom::Point const &origin, guint state)
160 {
161     // FIXME: this snapping should be done together with knowing whether control was pressed. If GDK_CONTROL_MASK, then constrained snapping should be used.
162     Geom::Point p_snapped = snap_knot_position(p, state);
163 
164     if ( state & GDK_CONTROL_MASK ) {
165         if (fabs((p - origin)[Geom::X]) > fabs((p - origin)[Geom::Y])) {
166             p_snapped[Geom::Y] = origin[Geom::Y];
167         } else {
168             p_snapped[Geom::X] = origin[Geom::X];
169         }
170     }
171 
172     if (state)  {
173         Geom::Point const q = p_snapped - knot_get();
174         item->adjust_pattern(Geom::Translate(q), false, _fill ? TRANSFORM_FILL : TRANSFORM_STROKE);
175     }
176 
177     item->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
178 }
179 
sp_pattern_knot_get(SPPattern const * pat,gdouble x,gdouble y)180 static Geom::Point sp_pattern_knot_get(SPPattern const *pat, gdouble x, gdouble y)
181 {
182     return Geom::Point(x, y) * pat->getTransform();
183 }
184 
185 bool
knot_missing() const186 PatternKnotHolderEntity::knot_missing() const
187 {
188     SPPattern *pat = _pattern();
189     return (pat == nullptr);
190 }
191 
192 SPPattern*
_pattern() const193 PatternKnotHolderEntity::_pattern() const
194 {
195     return _fill ? SP_PATTERN(item->style->getFillPaintServer()) : SP_PATTERN(item->style->getStrokePaintServer());
196 }
197 
198 Geom::Point
knot_get() const199 PatternKnotHolderEntityXY::knot_get() const
200 {
201     SPPattern *pat = _pattern();
202     return sp_pattern_knot_get(pat, 0, 0);
203 }
204 
205 Geom::Point
knot_get() const206 PatternKnotHolderEntityAngle::knot_get() const
207 {
208     SPPattern *pat = _pattern();
209     return sp_pattern_knot_get(pat, pat->width(), 0);
210 }
211 
212 Geom::Point
knot_get() const213 PatternKnotHolderEntityScale::knot_get() const
214 {
215     SPPattern *pat = _pattern();
216     return sp_pattern_knot_get(pat, pat->width(), pat->height());
217 }
218 
219 void
knot_set(Geom::Point const & p,Geom::Point const &,guint state)220 PatternKnotHolderEntityAngle::knot_set(Geom::Point const &p, Geom::Point const &/*origin*/, guint state)
221 {
222     Inkscape::Preferences *prefs = Inkscape::Preferences::get();
223     int const snaps = prefs->getInt("/options/rotationsnapsperpi/value", 12);
224 
225     SPPattern *pat = _fill ? SP_PATTERN(item->style->getFillPaintServer()) : SP_PATTERN(item->style->getStrokePaintServer());
226 
227     // get the angle from pattern 0,0 to the cursor pos
228     Geom::Point transform_origin = sp_pattern_knot_get(pat, 0, 0);
229     gdouble theta = atan2(p - transform_origin);
230     gdouble theta_old = atan2(knot_get() - transform_origin);
231 
232     if ( state & GDK_CONTROL_MASK ) {
233         /* Snap theta */
234         double snaps_radian = M_PI/snaps;
235         theta = std::round(theta/snaps_radian) * snaps_radian;
236     }
237 
238     Geom::Affine rot = Geom::Translate(-transform_origin)
239                      * Geom::Rotate(theta - theta_old)
240                      * Geom::Translate(transform_origin);
241     item->adjust_pattern(rot, false, _fill ? TRANSFORM_FILL : TRANSFORM_STROKE);
242     item->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
243 }
244 
245 void
knot_set(Geom::Point const & p,Geom::Point const & origin,guint state)246 PatternKnotHolderEntityScale::knot_set(Geom::Point const &p, Geom::Point const &origin, guint state)
247 {
248     SPPattern *pat = _pattern();
249 
250     // FIXME: this snapping should be done together with knowing whether control was pressed. If GDK_CONTROL_MASK, then constrained snapping should be used.
251     Geom::Point p_snapped = snap_knot_position(p, state);
252 
253     // Get the new scale from the position of the knotholder
254     Geom::Affine transform = pat->getTransform();
255     Geom::Affine transform_inverse = transform.inverse();
256     Geom::Point d = p_snapped * transform_inverse;
257     Geom::Point d_origin = origin * transform_inverse;
258     Geom::Point origin_dt;
259     gdouble pat_x = pat->width();
260     gdouble pat_y = pat->height();
261     if ( state & GDK_CONTROL_MASK ) {
262         // if ctrl is pressed: use 1:1 scaling
263         d = d_origin * (d.length() / d_origin.length());
264     }
265 
266     Geom::Affine rot = Geom::Translate(-origin_dt)
267                      * Geom::Scale(d.x() / pat_x, d.y() / pat_y)
268                      * Geom::Translate(origin_dt)
269                      * transform;
270 
271     item->adjust_pattern(rot, true, _fill ? TRANSFORM_FILL : TRANSFORM_STROKE);
272     item->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
273 }
274 
275 /* Hatch manipulation */
knot_missing() const276 bool HatchKnotHolderEntity::knot_missing() const
277 {
278     SPHatch *hatch = _hatch();
279     return (hatch == nullptr);
280 }
281 
_hatch() const282 SPHatch *HatchKnotHolderEntity::_hatch() const
283 {
284     return _fill ? SP_HATCH(item->style->getFillPaintServer()) : SP_HATCH(item->style->getStrokePaintServer());
285 }
286 
sp_hatch_knot_get(SPHatch const * hatch,gdouble x,gdouble y)287 static Geom::Point sp_hatch_knot_get(SPHatch const *hatch, gdouble x, gdouble y)
288 {
289     return Geom::Point(x, y) * hatch->hatchTransform();
290 }
291 
knot_get() const292 Geom::Point HatchKnotHolderEntityXY::knot_get() const
293 {
294     SPHatch *hatch = _hatch();
295     return sp_hatch_knot_get(hatch, 0, 0);
296 }
297 
knot_get() const298 Geom::Point HatchKnotHolderEntityAngle::knot_get() const
299 {
300     SPHatch *hatch = _hatch();
301     return sp_hatch_knot_get(hatch, hatch->pitch(), 0);
302 }
303 
knot_get() const304 Geom::Point HatchKnotHolderEntityScale::knot_get() const
305 {
306     SPHatch *hatch = _hatch();
307     return sp_hatch_knot_get(hatch, hatch->pitch(), hatch->pitch());
308 }
309 
knot_set(Geom::Point const & p,Geom::Point const & origin,unsigned int state)310 void HatchKnotHolderEntityXY::knot_set(Geom::Point const &p, Geom::Point const &origin, unsigned int state)
311 {
312     Geom::Point p_snapped = snap_knot_position(p, state);
313 
314     if (state & GDK_CONTROL_MASK) {
315         if (fabs((p - origin)[Geom::X]) > fabs((p - origin)[Geom::Y])) {
316             p_snapped[Geom::Y] = origin[Geom::Y];
317         } else {
318             p_snapped[Geom::X] = origin[Geom::X];
319         }
320     }
321 
322     if (state) {
323         Geom::Point const q = p_snapped - knot_get();
324         item->adjust_hatch(Geom::Translate(q), false, _fill ? TRANSFORM_FILL : TRANSFORM_STROKE);
325     }
326 
327     item->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
328 }
329 
knot_set(Geom::Point const & p,Geom::Point const & origin,unsigned int state)330 void HatchKnotHolderEntityAngle::knot_set(Geom::Point const &p, Geom::Point const &origin, unsigned int state)
331 {
332     Inkscape::Preferences *prefs = Inkscape::Preferences::get();
333     int const snaps = prefs->getInt("/options/rotationsnapsperpi/value", 12);
334 
335     SPHatch *hatch = _hatch();
336 
337     // get the angle from hatch 0,0 to the cursor pos
338     Geom::Point transform_origin = sp_hatch_knot_get(hatch, 0, 0);
339     gdouble theta = atan2(p - transform_origin);
340     gdouble theta_old = atan2(knot_get() - transform_origin);
341 
342     if (state & GDK_CONTROL_MASK) {
343         /* Snap theta */
344         double snaps_radian = M_PI/snaps;
345         theta = std::round(theta/snaps_radian) * snaps_radian;
346     }
347 
348     Geom::Affine rot =
349         Geom::Translate(-transform_origin) * Geom::Rotate(theta - theta_old) * Geom::Translate(transform_origin);
350     item->adjust_hatch(rot, false, _fill ? TRANSFORM_FILL : TRANSFORM_STROKE);
351     item->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
352 }
353 
knot_set(Geom::Point const & p,Geom::Point const & origin,unsigned int state)354 void HatchKnotHolderEntityScale::knot_set(Geom::Point const &p, Geom::Point const &origin, unsigned int state)
355 {
356     SPHatch *hatch = _hatch();
357 
358     // FIXME: this snapping should be done together with knowing whether control was pressed.
359     // If GDK_CONTROL_MASK, then constrained snapping should be used.
360     Geom::Point p_snapped = snap_knot_position(p, state);
361 
362     // Get the new scale from the position of the knotholder
363     Geom::Affine transform = hatch->hatchTransform();
364     Geom::Affine transform_inverse = transform.inverse();
365     Geom::Point d = p_snapped * transform_inverse;
366     Geom::Point d_origin = origin * transform_inverse;
367     Geom::Point origin_dt;
368     gdouble hatch_pitch = hatch->pitch();
369     if (state & GDK_CONTROL_MASK) {
370         // if ctrl is pressed: use 1:1 scaling
371         d = d_origin * (d.length() / d_origin.length());
372     }
373 
374     Geom::Affine scale = Geom::Translate(-origin_dt) * Geom::Scale(d.x() / hatch_pitch, d.y() / hatch_pitch) *
375                          Geom::Translate(origin_dt) * transform;
376 
377     item->adjust_hatch(scale, true, _fill ? TRANSFORM_FILL : TRANSFORM_STROKE);
378     item->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
379 }
380 
381 /* Filter manipulation */
knot_set(Geom::Point const & p,Geom::Point const & origin,unsigned int state)382 void FilterKnotHolderEntity::knot_set(Geom::Point const &p, Geom::Point const &origin, unsigned int state)
383 {
384     // FIXME: this snapping should be done together with knowing whether control was pressed. If GDK_CONTROL_MASK, then constrained snapping should be used.
385     Geom::Point p_snapped = snap_knot_position(p, state);
386 
387     if ( state & GDK_CONTROL_MASK ) {
388         if (fabs((p - origin)[Geom::X]) > fabs((p - origin)[Geom::Y])) {
389             p_snapped[Geom::Y] = origin[Geom::Y];
390         } else {
391             p_snapped[Geom::X] = origin[Geom::X];
392         }
393     }
394 
395     if (state)  {
396         SPFilter *filter = (item->style) ? item->style->getFilter() : nullptr;
397         if(!filter) return;
398         Geom::OptRect orig_bbox = item->visualBounds();
399         std::unique_ptr<Geom::Rect> new_bbox(_topleft ? new Geom::Rect(p,orig_bbox->max()) : new Geom::Rect(orig_bbox->min(), p));
400 
401         if (!filter->width._set) {
402             filter->width.set(SVGLength::PERCENT, 1.2);
403         }
404         if (!filter->height._set) {
405             filter->height.set(SVGLength::PERCENT, 1.2);
406         }
407         if (!filter->x._set) {
408             filter->x.set(SVGLength::PERCENT, -0.1);
409         }
410         if (!filter->y._set) {
411             filter->y.set(SVGLength::PERCENT, -0.1);
412         }
413 
414         if(_topleft) {
415             float x_a = filter->width.computed;
416             float y_a = filter->height.computed;
417             filter->height.scale(new_bbox->height()/orig_bbox->height());
418             filter->width.scale(new_bbox->width()/orig_bbox->width());
419             float x_b = filter->width.computed;
420             float y_b = filter->height.computed;
421             filter->x.set(filter->x.unit, filter->x.computed + x_a - x_b);
422             filter->y.set(filter->y.unit, filter->y.computed + y_a - y_b);
423         } else {
424             filter->height.scale(new_bbox->height()/orig_bbox->height());
425             filter->width.scale(new_bbox->width()/orig_bbox->width());
426         }
427         filter->auto_region = false;
428         filter->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
429 
430     }
431 
432     item->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
433 }
434 
knot_get() const435 Geom::Point FilterKnotHolderEntity::knot_get() const
436 {
437     SPFilter *filter = (item->style) ? item->style->getFilter() : nullptr;
438     if(!filter) return Geom::Point(Geom::infinity(), Geom::infinity());
439     Geom::OptRect r = item->visualBounds();
440     if (_topleft) return Geom::Point(r->min());
441     else return Geom::Point(r->max());
442 }
443 
444 /*
445   Local Variables:
446   mode:c++
447   c-file-style:"stroustrup"
448   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
449   indent-tabs-mode:nil
450   fill-column:99
451   End:
452 */
453 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
454