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