Inkscape
Vector Graphics Editor
Loading...
Searching...
No Matches
shape-editor-knotholders.cpp
Go to the documentation of this file.
1// SPDX-License-Identifier: GPL-2.0-or-later
2/*
3 * Node editing extension to objects
4 *
5 * Authors:
6 * Lauris Kaplinski <lauris@kaplinski.com>
7 * Mitsuru Oka
8 * Maximilian Albert <maximilian.albert@gmail.com>
9 * Abhishek Sharma
10 * Jon A. Cruz <jon@joncruz.org>
11 *
12 * Released under GNU GPL v2+, read the file 'COPYING' for more information.
13 */
14
15// Declared in shape-editor.cpp.
16
17#include <algorithm>
18#include <glibmm/i18n.h>
19
20#include "desktop.h"
21#include "document.h"
22#include "live_effects/effect.h"
23#include "object/box3d.h"
24#include "object/sp-ellipse.h"
25#include "object/sp-flowtext.h"
26#include "object/sp-item.h"
27#include "object/sp-marker.h"
28#include "object/sp-offset.h"
29#include "object/sp-pattern.h"
30#include "object/sp-rect.h"
31#include "object/sp-spiral.h"
32#include "object/sp-star.h"
33#include "object/sp-text.h"
34#include "object/sp-textpath.h"
35#include "preferences.h"
36#include "style.h"
39#include "ui/knot/knot-holder.h"
40
41class RectKnotHolder : public KnotHolder {
42public:
43 RectKnotHolder(SPDesktop *desktop, SPItem *item);
44 ~RectKnotHolder() override = default;;
45};
46
47class Box3DKnotHolder : public KnotHolder {
48public:
49 Box3DKnotHolder(SPDesktop *desktop, SPItem *item);
50 ~Box3DKnotHolder() override = default;;
51};
52
53class MarkerKnotHolder : public KnotHolder {
54public:
55 MarkerKnotHolder(SPDesktop *desktop, SPItem *item, double edit_rotation, int edit_marker_mode);
56 ~MarkerKnotHolder() override = default;;
57};
58
59class ArcKnotHolder : public KnotHolder {
60public:
61 ArcKnotHolder(SPDesktop *desktop, SPItem *item);
62 ~ArcKnotHolder() override = default;;
63};
64
65class StarKnotHolder : public KnotHolder {
66public:
67 StarKnotHolder(SPDesktop *desktop, SPItem *item);
68 ~StarKnotHolder() override = default;;
69};
70
71class SpiralKnotHolder : public KnotHolder {
72public:
73 SpiralKnotHolder(SPDesktop *desktop, SPItem *item);
74 ~SpiralKnotHolder() override = default;;
75};
76
77class OffsetKnotHolder : public KnotHolder {
78public:
79 OffsetKnotHolder(SPDesktop *desktop, SPItem *item);
80 ~OffsetKnotHolder() override = default;;
81};
82
83class TextKnotHolder : public KnotHolder {
84public:
85 TextKnotHolder(SPDesktop *desktop, SPItem *item);
86 ~TextKnotHolder() override = default;;
87};
88
89class FlowtextKnotHolder : public KnotHolder {
90public:
91 FlowtextKnotHolder(SPDesktop *desktop, SPItem *item);
92 ~FlowtextKnotHolder() override = default;;
93};
94
95class MiscKnotHolder : public KnotHolder {
96public:
97 MiscKnotHolder(SPDesktop *desktop, SPItem *item);
98 ~MiscKnotHolder() override = default;;
99};
100
101namespace {
102
103std::unique_ptr<KnotHolder> make_lpe_knot_holder(SPLPEItem *item, SPDesktop *desktop)
104{
105 auto knot_holder = std::make_unique<KnotHolder>(desktop, item);
106
107 item->getCurrentLPE()->addHandles(knot_holder.get(), item);
108 for (auto i : knot_holder->entity) {
109 i->knot->is_lpe = true;
110 }
111 return knot_holder;
112}
113} // namespace
114
115namespace Inkscape {
116namespace UI {
117
118std::unique_ptr<KnotHolder> create_knot_holder(SPItem *item, SPDesktop *desktop, double edit_rotation,
119 int edit_marker_mode)
120{
121 std::unique_ptr<KnotHolder> knotholder;
122
123 if (is<SPRect>(item)) {
124 knotholder = std::make_unique<RectKnotHolder>(desktop, item);
125 } else if (is<SPBox3D>(item)) {
126 knotholder = std::make_unique<Box3DKnotHolder>(desktop, item);
127 } else if (is<SPMarker>(item)) {
128 knotholder = std::make_unique<MarkerKnotHolder>(desktop, item, edit_rotation, edit_marker_mode);
129 } else if (is<SPGenericEllipse>(item)) {
130 knotholder = std::make_unique<ArcKnotHolder>(desktop, item);
131 } else if (is<SPStar>(item)) {
132 knotholder = std::make_unique<StarKnotHolder>(desktop, item);
133 } else if (is<SPSpiral>(item)) {
134 knotholder = std::make_unique<SpiralKnotHolder>(desktop, item);
135 } else if (is<SPOffset>(item)) {
136 knotholder = std::make_unique<OffsetKnotHolder>(desktop, item);
137 } else if (auto text = cast<SPText>(item)) {
138 // Do not allow conversion to 'inline-size' wrapped text if on path!
139 // <textPath> might not be first child if <title> or <desc> is present.
140 auto const text_children = text->childList(false);
141 bool const is_on_path = std::any_of(text_children.begin(), text_children.end(), is<SPTextPath, SPObject>);
142 if (!is_on_path) {
143 knotholder = std::make_unique<TextKnotHolder>(desktop, item);
144 }
145 } else {
146 auto flowtext = cast<SPFlowtext>(item);
147 if (flowtext && flowtext->has_internal_frame()) {
148 knotholder = std::make_unique<FlowtextKnotHolder>(desktop, flowtext->get_frame(nullptr));
149 } else if ((item->style->fill.isPaintserver() && cast<SPPattern>(item->style->getFillPaintServer())) ||
150 (item->style->stroke.isPaintserver() && cast<SPPattern>(item->style->getStrokePaintServer()))) {
151 knotholder = std::make_unique<KnotHolder>(desktop, item);
152 knotholder->add_pattern_knotholder();
153 }
154 }
155 if (!knotholder) {
156 knotholder = std::make_unique<KnotHolder>(desktop, item);
157 }
158 knotholder->add_filter_knotholder();
159
160 return knotholder;
161}
162
163std::unique_ptr<KnotHolder> create_LPE_knot_holder(SPItem *item, SPDesktop *desktop)
164{
165 auto lpe = cast<SPLPEItem>(item);
166 if (lpe &&
167 lpe->getCurrentLPE() &&
168 lpe->getCurrentLPE()->isVisible() &&
169 lpe->getCurrentLPE()->providesKnotholder()) {
170 return make_lpe_knot_holder(lpe, desktop);
171 }
172 return {};
173}
174
175}
176} // namespace Inkscape
177
178/* SPRect */
179
180/* handle for horizontal rounding radius */
181class RectKnotHolderEntityRX : public KnotHolderEntity {
182public:
183 Geom::Point knot_get() const override;
184 void knot_set(Geom::Point const &p, Geom::Point const &origin, unsigned int state) override;
185 void knot_ungrabbed(Geom::Point const &p, Geom::Point const &origin, guint state) override {};
186 void knot_click(unsigned int state) override;
187};
188
189/* handle for vertical rounding radius */
190class RectKnotHolderEntityRY : public KnotHolderEntity {
191public:
192 Geom::Point knot_get() const override;
193 void knot_set(Geom::Point const &p, Geom::Point const &origin, unsigned int state) override;
194 void knot_ungrabbed(Geom::Point const &p, Geom::Point const &origin, guint state) override {};
195 void knot_click(unsigned int state) override;
196};
197
198/* handle for width/height adjustment */
199class RectKnotHolderEntityWH : public KnotHolderEntity {
200public:
201 Geom::Point knot_get() const override;
202 void knot_ungrabbed(Geom::Point const &p, Geom::Point const &origin, guint state) override {};
203 void knot_set(Geom::Point const &p, Geom::Point const &origin, unsigned int state) override;
204
205protected:
206 void set_internal(Geom::Point const &p, Geom::Point const &origin, unsigned int state);
207};
208
209/* handle for x/y adjustment */
210class RectKnotHolderEntityXY : public KnotHolderEntity {
211public:
212 Geom::Point knot_get() const override;
213 void knot_ungrabbed(Geom::Point const &p, Geom::Point const &origin, guint state) override {};
214 void knot_set(Geom::Point const &p, Geom::Point const &origin, unsigned int state) override;
215};
216
217/* handle for position */
218class RectKnotHolderEntityCenter : public KnotHolderEntity {
219public:
220 Geom::Point knot_get() const override;
221 void knot_ungrabbed(Geom::Point const &p, Geom::Point const &origin, guint state) override {};
222 void knot_set(Geom::Point const &p, Geom::Point const &origin, unsigned int state) override;
223};
224
226RectKnotHolderEntityRX::knot_get() const
227{
228 auto rect = cast<SPRect>(item);
229 g_assert(rect != nullptr);
230
231 return Geom::Point(rect->x.computed + rect->width.computed - rect->rx.computed, rect->y.computed);
232}
233
234void
235RectKnotHolderEntityRX::knot_set(Geom::Point const &p, Geom::Point const &/*origin*/, unsigned int state)
236{
237 auto rect = cast<SPRect>(item);
238 g_assert(rect != nullptr);
239
240 //In general we cannot just snap this radius to an arbitrary point, as we have only a single
241 //degree of freedom. For snapping to an arbitrary point we need two DOF. If we're going to snap
242 //the radius then we should have a constrained snap. snap_knot_position() is unconstrained
243 Geom::Point const s = snap_knot_position_constrained(p, Inkscape::Snapper::SnapConstraint(Geom::Point(rect->x.computed + rect->width.computed, rect->y.computed), Geom::Point(-1, 0)), state);
244
245 if (state & GDK_CONTROL_MASK) {
246 gdouble temp = MIN(rect->height.computed, rect->width.computed) / 2.0;
247 rect->rx = rect->ry = CLAMP(rect->x.computed + rect->width.computed - s[Geom::X], 0.0, temp);
248 } else {
249 rect->rx = CLAMP(rect->x.computed + rect->width.computed - s[Geom::X], 0.0, rect->width.computed / 2.0);
250 }
251
252 update_knot();
253
254 rect->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
255}
256
257void
258RectKnotHolderEntityRX::knot_click(unsigned int state)
259{
260 auto rect = cast<SPRect>(item);
261 g_assert(rect != nullptr);
262
263 if (state & GDK_SHIFT_MASK) {
264 /* remove rounding from rectangle */
265 rect->getRepr()->removeAttribute("rx");
266 rect->getRepr()->removeAttribute("ry");
267 } else if (state & GDK_CONTROL_MASK) {
268 /* Ctrl-click sets the vertical rounding to be the same as the horizontal */
269 rect->getRepr()->setAttribute("ry", rect->getRepr()->attribute("rx"));
270 }
271
272}
273
275RectKnotHolderEntityRY::knot_get() const
276{
277 auto rect = cast<SPRect>(item);
278 g_assert(rect != nullptr);
279
280 return Geom::Point(rect->x.computed + rect->width.computed, rect->y.computed + rect->ry.computed);
281}
282
283void
284RectKnotHolderEntityRY::knot_set(Geom::Point const &p, Geom::Point const &/*origin*/, unsigned int state)
285{
286 auto rect = cast<SPRect>(item);
287 g_assert(rect != nullptr);
288
289 //In general we cannot just snap this radius to an arbitrary point, as we have only a single
290 //degree of freedom. For snapping to an arbitrary point we need two DOF. If we're going to snap
291 //the radius then we should have a constrained snap. snap_knot_position() is unconstrained
292 Geom::Point const s = snap_knot_position_constrained(p, Inkscape::Snapper::SnapConstraint(Geom::Point(rect->x.computed + rect->width.computed, rect->y.computed), Geom::Point(0, 1)), state);
293
294 if (state & GDK_CONTROL_MASK) { // When holding control then rx will be kept equal to ry,
295 // resulting in a perfect circle (and not an ellipse)
296 gdouble temp = MIN(rect->height.computed, rect->width.computed) / 2.0;
297 rect->rx = rect->ry = CLAMP(s[Geom::Y] - rect->y.computed, 0.0, temp);
298 } else {
299 if (!rect->rx._set || rect->rx.computed == 0) {
300 rect->ry = CLAMP(s[Geom::Y] - rect->y.computed,
301 0.0,
302 MIN(rect->height.computed / 2.0, rect->width.computed / 2.0));
303 } else {
304 rect->ry = CLAMP(s[Geom::Y] - rect->y.computed,
305 0.0,
306 rect->height.computed / 2.0);
307 }
308 }
309
310 update_knot();
311
312 rect->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
313}
314
315void
316RectKnotHolderEntityRY::knot_click(unsigned int state)
317{
318 auto rect = cast<SPRect>(item);
319 g_assert(rect != nullptr);
320
321 if (state & GDK_SHIFT_MASK) {
322 /* remove rounding */
323 rect->getRepr()->removeAttribute("rx");
324 rect->getRepr()->removeAttribute("ry");
325 } else if (state & GDK_CONTROL_MASK) {
326 /* Ctrl-click sets the vertical rounding to be the same as the horizontal */
327 rect->getRepr()->setAttribute("rx", rect->getRepr()->attribute("ry"));
328 }
329}
330
331#define SGN(x) ((x)>0?1:((x)<0?-1:0))
332
333static void sp_rect_clamp_radii(SPRect *rect)
334{
335 // clamp rounding radii so that they do not exceed width/height
336 if (2 * rect->rx.computed > rect->width.computed) {
337 rect->rx = 0.5 * rect->width.computed;
338 }
339 if (2 * rect->ry.computed > rect->height.computed) {
340 rect->ry = 0.5 * rect->height.computed;
341 }
342}
343
345RectKnotHolderEntityWH::knot_get() const
346{
347 auto rect = cast<SPRect>(item);
348 g_assert(rect != nullptr);
349
350 return Geom::Point(rect->x.computed + rect->width.computed, rect->y.computed + rect->height.computed);
351}
352
353void
354RectKnotHolderEntityWH::set_internal(Geom::Point const &p, Geom::Point const &origin, unsigned int state)
355{
356 auto rect = cast<SPRect>(item);
357 g_assert(rect != nullptr);
358
359 Geom::Point s = p;
360
361 if (state & GDK_CONTROL_MASK) {
362 // original width/height when drag started
363 gdouble const w_orig = (origin[Geom::X] - rect->x.computed);
364 gdouble const h_orig = (origin[Geom::Y] - rect->y.computed);
365
366 //original ratio
367 gdouble ratio = (w_orig / h_orig);
368
369 // mouse displacement since drag started
370 gdouble minx = p[Geom::X] - origin[Geom::X];
371 gdouble miny = p[Geom::Y] - origin[Geom::Y];
372
373 Geom::Point p_handle(rect->x.computed + rect->width.computed, rect->y.computed + rect->height.computed);
374
375 if (fabs(minx) > fabs(miny)) {
376 // snap to horizontal or diagonal
377 if (minx != 0 && fabs(miny/minx) > 0.5 * 1/ratio && (SGN(minx) == SGN(miny))) {
378 // closer to the diagonal and in same-sign quarters, change both using ratio
379 s = snap_knot_position_constrained(p, Inkscape::Snapper::SnapConstraint(p_handle, Geom::Point(-ratio, -1)), state);
380 minx = s[Geom::X] - origin[Geom::X];
381 // Dead assignment: Value stored to 'miny' is never read
382 //miny = s[Geom::Y] - origin[Geom::Y];
383 rect->height = MAX(h_orig + minx / ratio, 0);
384 } else {
385 // closer to the horizontal, change only width, height is h_orig
386 s = snap_knot_position_constrained(p, Inkscape::Snapper::SnapConstraint(p_handle, Geom::Point(-1, 0)), state);
387 minx = s[Geom::X] - origin[Geom::X];
388 // Dead assignment: Value stored to 'miny' is never read
389 //miny = s[Geom::Y] - origin[Geom::Y];
390 rect->height = MAX(h_orig, 0);
391 }
392 rect->width = MAX(w_orig + minx, 0);
393
394 } else {
395 // snap to vertical or diagonal
396 if (miny != 0 && fabs(minx/miny) > 0.5 * ratio && (SGN(minx) == SGN(miny))) {
397 // closer to the diagonal and in same-sign quarters, change both using ratio
398 s = snap_knot_position_constrained(p, Inkscape::Snapper::SnapConstraint(p_handle, Geom::Point(-ratio, -1)), state);
399 // Dead assignment: Value stored to 'minx' is never read
400 //minx = s[Geom::X] - origin[Geom::X];
401 miny = s[Geom::Y] - origin[Geom::Y];
402 rect->width = MAX(w_orig + miny * ratio, 0);
403 } else {
404 // closer to the vertical, change only height, width is w_orig
405 s = snap_knot_position_constrained(p, Inkscape::Snapper::SnapConstraint(p_handle, Geom::Point(0, -1)), state);
406 // Dead assignment: Value stored to 'minx' is never read
407 //minx = s[Geom::X] - origin[Geom::X];
408 miny = s[Geom::Y] - origin[Geom::Y];
409 rect->width = MAX(w_orig, 0);
410 }
411 rect->height = MAX(h_orig + miny, 0);
412
413 }
414
415 } else {
416 // move freely
417 s = snap_knot_position(p, state);
418 rect->width = MAX(s[Geom::X] - rect->x.computed, 0);
419 rect->height = MAX(s[Geom::Y] - rect->y.computed, 0);
420 }
421
423
424 rect->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
425}
426
427void
428RectKnotHolderEntityWH::knot_set(Geom::Point const &p, Geom::Point const &origin, unsigned int state)
429{
430 set_internal(p, origin, state);
431 update_knot();
432}
433
435RectKnotHolderEntityXY::knot_get() const
436{
437 auto rect = cast<SPRect>(item);
438 g_assert(rect != nullptr);
439
440 return Geom::Point(rect->x.computed, rect->y.computed);
441}
442
443void
444RectKnotHolderEntityXY::knot_set(Geom::Point const &p, Geom::Point const &origin, unsigned int state)
445{
446 auto rect = cast<SPRect>(item);
447 g_assert(rect != nullptr);
448
449 // opposite corner (unmoved)
450 gdouble opposite_x = (rect->x.computed + rect->width.computed);
451 gdouble opposite_y = (rect->y.computed + rect->height.computed);
452
453 // original width/height when drag started
454 gdouble w_orig = opposite_x - origin[Geom::X];
455 gdouble h_orig = opposite_y - origin[Geom::Y];
456
457 Geom::Point s = p;
458 Geom::Point p_handle(rect->x.computed, rect->y.computed);
459
460 // mouse displacement since drag started
461 gdouble minx = p[Geom::X] - origin[Geom::X];
462 gdouble miny = p[Geom::Y] - origin[Geom::Y];
463
464 if (state & GDK_CONTROL_MASK) {
465 //original ratio
466 gdouble ratio = (w_orig / h_orig);
467
468 if (fabs(minx) > fabs(miny)) {
469 // snap to horizontal or diagonal
470 if (minx != 0 && fabs(miny/minx) > 0.5 * 1/ratio && (SGN(minx) == SGN(miny))) {
471 // closer to the diagonal and in same-sign quarters, change both using ratio
472 s = snap_knot_position_constrained(p, Inkscape::Snapper::SnapConstraint(p_handle, Geom::Point(-ratio, -1)), state);
473 minx = s[Geom::X] - origin[Geom::X];
474 // Dead assignment: Value stored to 'miny' is never read
475 //miny = s[Geom::Y] - origin[Geom::Y];
476 rect->y = MIN(origin[Geom::Y] + minx / ratio, opposite_y);
477 rect->height = MAX(h_orig - minx / ratio, 0);
478 } else {
479 // closer to the horizontal, change only width, height is h_orig
480 s = snap_knot_position_constrained(p, Inkscape::Snapper::SnapConstraint(p_handle, Geom::Point(-1, 0)), state);
481 minx = s[Geom::X] - origin[Geom::X];
482 // Dead assignment: Value stored to 'miny' is never read
483 //miny = s[Geom::Y] - origin[Geom::Y];
484 rect->y = MIN(origin[Geom::Y], opposite_y);
485 rect->height = MAX(h_orig, 0);
486 }
487 rect->x = MIN(s[Geom::X], opposite_x);
488 rect->width = MAX(w_orig - minx, 0);
489 } else {
490 // snap to vertical or diagonal
491 if (miny != 0 && fabs(minx/miny) > 0.5 *ratio && (SGN(minx) == SGN(miny))) {
492 // closer to the diagonal and in same-sign quarters, change both using ratio
493 s = snap_knot_position_constrained(p, Inkscape::Snapper::SnapConstraint(p_handle, Geom::Point(-ratio, -1)), state);
494 // Dead assignment: Value stored to 'minx' is never read
495 //minx = s[Geom::X] - origin[Geom::X];
496 miny = s[Geom::Y] - origin[Geom::Y];
497 rect->x = MIN(origin[Geom::X] + miny * ratio, opposite_x);
498 rect->width = MAX(w_orig - miny * ratio, 0);
499 } else {
500 // closer to the vertical, change only height, width is w_orig
501 s = snap_knot_position_constrained(p, Inkscape::Snapper::SnapConstraint(p_handle, Geom::Point(0, -1)), state);
502 // Dead assignment: Value stored to 'minx' is never read
503 //minx = s[Geom::X] - origin[Geom::X];
504 miny = s[Geom::Y] - origin[Geom::Y];
505 rect->x = MIN(origin[Geom::X], opposite_x);
506 rect->width = MAX(w_orig, 0);
507 }
508 rect->y = MIN(s[Geom::Y], opposite_y);
509 rect->height = MAX(h_orig - miny, 0);
510 }
511
512 } else {
513 // move freely
514 s = snap_knot_position(p, state);
515 minx = s[Geom::X] - origin[Geom::X];
516 miny = s[Geom::Y] - origin[Geom::Y];
517
518 rect->x = MIN(s[Geom::X], opposite_x);
519 rect->y = MIN(s[Geom::Y], opposite_y);
520 rect->width = MAX(w_orig - minx, 0);
521 rect->height = MAX(h_orig - miny, 0);
522 }
523
525
526 update_knot();
527
528 rect->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
529}
530
532RectKnotHolderEntityCenter::knot_get() const
533{
534 auto rect = cast<SPRect>(item);
535 g_assert(rect != nullptr);
536
537 return Geom::Point(rect->x.computed + (rect->width.computed / 2.), rect->y.computed + (rect->height.computed / 2.));
538}
539
540void
541RectKnotHolderEntityCenter::knot_set(Geom::Point const &p, Geom::Point const &/*origin*/, unsigned int state)
542{
543 auto rect = cast<SPRect>(item);
544 g_assert(rect != nullptr);
545
546 Geom::Point const s = snap_knot_position(p, state);
547
548 rect->x = s[Geom::X] - (rect->width.computed / 2.);
549 rect->y = s[Geom::Y] - (rect->height.computed / 2.);
550
551 // No need to call sp_rect_clamp_radii(): width and height haven't changed.
552 // No need to call update_knot(): the knot is set directly by the user.
553
554 rect->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
555}
556
557RectKnotHolder::RectKnotHolder(SPDesktop *desktop, SPItem *item)
559{
560 RectKnotHolderEntityRX *entity_rx = new RectKnotHolderEntityRX();
561 RectKnotHolderEntityRY *entity_ry = new RectKnotHolderEntityRY();
562 RectKnotHolderEntityWH *entity_wh = new RectKnotHolderEntityWH();
563 RectKnotHolderEntityXY *entity_xy = new RectKnotHolderEntityXY();
564 RectKnotHolderEntityCenter *entity_center = new RectKnotHolderEntityCenter();
565
566 entity_rx->create(desktop, item, this, Inkscape::CANVAS_ITEM_CTRL_TYPE_ROTATE, "Rect:rx",
567 _("Adjust the <b>horizontal rounding</b> radius; with <b>Ctrl</b> "
568 "to make the vertical radius the same"));
569
570 entity_ry->create(desktop, item, this, Inkscape::CANVAS_ITEM_CTRL_TYPE_ROTATE, "Rect:ry",
571 _("Adjust the <b>vertical rounding</b> radius; with <b>Ctrl</b> "
572 "to make the horizontal radius the same"));
573
574 entity_wh->create(desktop, item, this, Inkscape::CANVAS_ITEM_CTRL_TYPE_SIZER, "Rect:wh",
575 _("Adjust the <b>width and height</b> of the rectangle; with <b>Ctrl</b> "
576 "to lock ratio or stretch in one dimension only"));
577
578 entity_xy->create(desktop, item, this, Inkscape::CANVAS_ITEM_CTRL_TYPE_SIZER, "Rect:xy",
579 _("Adjust the <b>width and height</b> of the rectangle; with <b>Ctrl</b> "
580 "to lock ratio or stretch in one dimension only"));
581
582 entity_center->create(desktop, item, this, Inkscape::CANVAS_ITEM_CTRL_TYPE_MARKER, "Rect:center",
583 _("Drag to move the rectangle"));
584
585 entity.push_back(entity_rx);
586 entity.push_back(entity_ry);
587 entity.push_back(entity_wh);
588 entity.push_back(entity_xy);
589 entity.push_back(entity_center);
590
591 add_pattern_knotholder();
592 add_hatch_knotholder();
593}
594
595/* Box3D (= the new 3D box structure) */
596
597class Box3DKnotHolderEntity : public KnotHolderEntity {
598public:
599 Geom::Point knot_get() const override = 0;
600 void knot_set(Geom::Point const &p, Geom::Point const &origin, unsigned int state) override = 0;
601
602 Geom::Point knot_get_generic(SPItem *item, unsigned int knot_id) const;
603 void knot_set_generic(SPItem *item, unsigned int knot_id, Geom::Point const &p, unsigned int state);
604};
605
607Box3DKnotHolderEntity::knot_get_generic(SPItem *item, unsigned int knot_id) const
608{
609 auto box = cast<SPBox3D>(item);
610 if (box) {
611 return box->get_corner_screen(knot_id);
612 } else {
613 return Geom::Point(); // TODO investigate proper fallback
614 }
615}
616
617void
618Box3DKnotHolderEntity::knot_set_generic(SPItem *item, unsigned int knot_id, Geom::Point const &new_pos, unsigned int state)
619{
620 Geom::Point const s = snap_knot_position(new_pos, state);
621
622 g_assert(item != nullptr);
623 auto box = cast<SPBox3D>(item);
624 g_assert(box != nullptr);
625 Geom::Affine const i2dt (item->i2dt_affine ());
626
627 Box3D::Axis movement;
628 if ((knot_id < 4) != (state & GDK_SHIFT_MASK)) {
629 movement = Box3D::XY;
630 } else {
631 movement = Box3D::Z;
632 }
633
634 box->set_corner (knot_id, s * i2dt, movement, (state & GDK_CONTROL_MASK));
635 box->set_z_orders();
636 box->position_set();
637}
638
639class Box3DKnotHolderEntity0 : public Box3DKnotHolderEntity {
640public:
641 Geom::Point knot_get() const override;
642 void knot_ungrabbed(Geom::Point const &p, Geom::Point const &origin, guint state) override {};
643 void knot_set(Geom::Point const &p, Geom::Point const &origin, unsigned int state) override;
644};
645
646class Box3DKnotHolderEntity1 : public Box3DKnotHolderEntity {
647public:
648 Geom::Point knot_get() const override;
649 void knot_ungrabbed(Geom::Point const &p, Geom::Point const &origin, guint state) override {};
650 void knot_set(Geom::Point const &p, Geom::Point const &origin, unsigned int state) override;
651};
652
653class Box3DKnotHolderEntity2 : public Box3DKnotHolderEntity {
654public:
655 Geom::Point knot_get() const override;
656 void knot_ungrabbed(Geom::Point const &p, Geom::Point const &origin, guint state) override {};
657 void knot_set(Geom::Point const &p, Geom::Point const &origin, unsigned int state) override;
658};
659
660class Box3DKnotHolderEntity3 : public Box3DKnotHolderEntity {
661public:
662 Geom::Point knot_get() const override;
663 void knot_ungrabbed(Geom::Point const &p, Geom::Point const &origin, guint state) override {};
664 void knot_set(Geom::Point const &p, Geom::Point const &origin, unsigned int state) override;
665};
666
667class Box3DKnotHolderEntity4 : public Box3DKnotHolderEntity {
668public:
669 Geom::Point knot_get() const override;
670 void knot_ungrabbed(Geom::Point const &p, Geom::Point const &origin, guint state) override {};
671 void knot_set(Geom::Point const &p, Geom::Point const &origin, unsigned int state) override;
672};
673
674class Box3DKnotHolderEntity5 : public Box3DKnotHolderEntity {
675public:
676 Geom::Point knot_get() const override;
677 void knot_ungrabbed(Geom::Point const &p, Geom::Point const &origin, guint state) override {};
678 void knot_set(Geom::Point const &p, Geom::Point const &origin, unsigned int state) override;
679};
680
681class Box3DKnotHolderEntity6 : public Box3DKnotHolderEntity {
682public:
683 Geom::Point knot_get() const override;
684 void knot_ungrabbed(Geom::Point const &p, Geom::Point const &origin, guint state) override {};
685 void knot_set(Geom::Point const &p, Geom::Point const &origin, unsigned int state) override;
686};
687
688class Box3DKnotHolderEntity7 : public Box3DKnotHolderEntity {
689public:
690 Geom::Point knot_get() const override;
691 void knot_ungrabbed(Geom::Point const &p, Geom::Point const &origin, guint state) override {};
692 void knot_set(Geom::Point const &p, Geom::Point const &origin, unsigned int state) override;
693};
694
695class Box3DKnotHolderEntityCenter : public KnotHolderEntity {
696public:
697 Geom::Point knot_get() const override;
698 void knot_ungrabbed(Geom::Point const &p, Geom::Point const &origin, guint state) override {};
699 void knot_set(Geom::Point const &p, Geom::Point const &origin, unsigned int state) override;
700};
701
703Box3DKnotHolderEntity0::knot_get() const
704{
705 return knot_get_generic(item, 0);
706}
707
709Box3DKnotHolderEntity1::knot_get() const
710{
711 return knot_get_generic(item, 1);
712}
713
715Box3DKnotHolderEntity2::knot_get() const
716{
717 return knot_get_generic(item, 2);
718}
719
721Box3DKnotHolderEntity3::knot_get() const
722{
723 return knot_get_generic(item, 3);
724}
725
727Box3DKnotHolderEntity4::knot_get() const
728{
729 return knot_get_generic(item, 4);
730}
731
733Box3DKnotHolderEntity5::knot_get() const
734{
735 return knot_get_generic(item, 5);
736}
737
739Box3DKnotHolderEntity6::knot_get() const
740{
741 return knot_get_generic(item, 6);
742}
743
745Box3DKnotHolderEntity7::knot_get() const
746{
747 return knot_get_generic(item, 7);
748}
749
751Box3DKnotHolderEntityCenter::knot_get() const
752{
753 auto box = cast<SPBox3D>(item);
754 if (box) {
755 return box->get_center_screen();
756 } else {
757 return Geom::Point(); // TODO investigate proper fallback
758 }
759}
760
761void
762Box3DKnotHolderEntity0::knot_set(Geom::Point const &new_pos, Geom::Point const &/*origin*/, unsigned int state)
763{
764 knot_set_generic(item, 0, new_pos, state);
765}
766
767void
768Box3DKnotHolderEntity1::knot_set(Geom::Point const &new_pos, Geom::Point const &/*origin*/, unsigned int state)
769{
770 knot_set_generic(item, 1, new_pos, state);
771}
772
773void
774Box3DKnotHolderEntity2::knot_set(Geom::Point const &new_pos, Geom::Point const &/*origin*/, unsigned int state)
775{
776 knot_set_generic(item, 2, new_pos, state);
777}
778
779void
780Box3DKnotHolderEntity3::knot_set(Geom::Point const &new_pos, Geom::Point const &/*origin*/, unsigned int state)
781{
782 knot_set_generic(item, 3, new_pos, state);
783}
784
785void
786Box3DKnotHolderEntity4::knot_set(Geom::Point const &new_pos, Geom::Point const &/*origin*/, unsigned int state)
787{
788 knot_set_generic(item, 4, new_pos, state);
789}
790
791void
792Box3DKnotHolderEntity5::knot_set(Geom::Point const &new_pos, Geom::Point const &/*origin*/, unsigned int state)
793{
794 knot_set_generic(item, 5, new_pos, state);
795}
796
797void
798Box3DKnotHolderEntity6::knot_set(Geom::Point const &new_pos, Geom::Point const &/*origin*/, unsigned int state)
799{
800 knot_set_generic(item, 6, new_pos, state);
801}
802
803void
804Box3DKnotHolderEntity7::knot_set(Geom::Point const &new_pos, Geom::Point const &/*origin*/, unsigned int state)
805{
806 knot_set_generic(item, 7, new_pos, state);
807}
808
809void
810Box3DKnotHolderEntityCenter::knot_set(Geom::Point const &new_pos, Geom::Point const &origin, unsigned int state)
811{
812 Geom::Point const s = snap_knot_position(new_pos, state);
813
814 auto box = cast<SPBox3D>(item);
815 g_assert(box != nullptr);
816 Geom::Affine const i2dt (item->i2dt_affine ());
817
818 box->set_center(s * i2dt, origin * i2dt, !(state & GDK_SHIFT_MASK) ? Box3D::XY : Box3D::Z,
819 state & GDK_CONTROL_MASK);
820
821 box->set_z_orders();
822 box->position_set();
823}
824
825Box3DKnotHolder::Box3DKnotHolder(SPDesktop *desktop, SPItem *item)
827{
828 Box3DKnotHolderEntity0 *entity_corner0 = new Box3DKnotHolderEntity0();
829 Box3DKnotHolderEntity1 *entity_corner1 = new Box3DKnotHolderEntity1();
830 Box3DKnotHolderEntity2 *entity_corner2 = new Box3DKnotHolderEntity2();
831 Box3DKnotHolderEntity3 *entity_corner3 = new Box3DKnotHolderEntity3();
832 Box3DKnotHolderEntity4 *entity_corner4 = new Box3DKnotHolderEntity4();
833 Box3DKnotHolderEntity5 *entity_corner5 = new Box3DKnotHolderEntity5();
834 Box3DKnotHolderEntity6 *entity_corner6 = new Box3DKnotHolderEntity6();
835 Box3DKnotHolderEntity7 *entity_corner7 = new Box3DKnotHolderEntity7();
836 Box3DKnotHolderEntityCenter *entity_center = new Box3DKnotHolderEntityCenter();
837
838 entity_corner0->create(desktop, item, this, Inkscape::CANVAS_ITEM_CTRL_TYPE_SHAPER, "Box3D:corner0",
839 _("Resize box in X/Y direction; with <b>Shift</b> along the Z axis; "
840 "with <b>Ctrl</b> to constrain to the directions of edges or diagonals"));
841
842 entity_corner1->create(desktop, item, this, Inkscape::CANVAS_ITEM_CTRL_TYPE_SHAPER, "Box3D:corner1",
843 _("Resize box in X/Y direction; with <b>Shift</b> along the Z axis; "
844 "with <b>Ctrl</b> to constrain to the directions of edges or diagonals"));
845
846 entity_corner2->create(desktop, item, this, Inkscape::CANVAS_ITEM_CTRL_TYPE_SHAPER, "Box3D:corner2",
847 _("Resize box in X/Y direction; with <b>Shift</b> along the Z axis; "
848 "with <b>Ctrl</b> to constrain to the directions of edges or diagonals"));
849
850 entity_corner3->create(desktop, item, this, Inkscape::CANVAS_ITEM_CTRL_TYPE_SHAPER, "Box3D:corner3",
851 _("Resize box in X/Y direction; with <b>Shift</b> along the Z axis; "
852 "with <b>Ctrl</b> to constrain to the directions of edges or diagonals"));
853
854 entity_corner4->create(desktop, item, this, Inkscape::CANVAS_ITEM_CTRL_TYPE_SHAPER, "Box3D:corner4",
855 _("Resize box along the Z axis; with <b>Shift</b> in X/Y direction; "
856 "with <b>Ctrl</b> to constrain to the directions of edges or diagonals"));
857
858 entity_corner5->create(desktop, item, this, Inkscape::CANVAS_ITEM_CTRL_TYPE_SHAPER, "Box3D:corner5",
859 _("Resize box along the Z axis; with <b>Shift</b> in X/Y direction; "
860 "with <b>Ctrl</b> to constrain to the directions of edges or diagonals"));
861
862 entity_corner6->create(desktop, item, this, Inkscape::CANVAS_ITEM_CTRL_TYPE_SHAPER, "Box3D:corner6",
863 _("Resize box along the Z axis; with <b>Shift</b> in X/Y direction; "
864 "with <b>Ctrl</b> to constrain to the directions of edges or diagonals"));
865
866 entity_corner7->create(desktop, item, this, Inkscape::CANVAS_ITEM_CTRL_TYPE_SHAPER, "Box3D:corner7",
867 _("Resize box along the Z axis; with <b>Shift</b> in X/Y direction; "
868 "with <b>Ctrl</b> to constrain to the directions of edges or diagonals"));
869
870 entity_center->create(desktop, item, this, Inkscape::CANVAS_ITEM_CTRL_TYPE_MARKER, "Box3D:center",
871 _("Move the box in perspective"));
872
873 entity.push_back(entity_corner0);
874 entity.push_back(entity_corner1);
875 entity.push_back(entity_corner2);
876 entity.push_back(entity_corner3);
877 entity.push_back(entity_corner4);
878 entity.push_back(entity_corner5);
879 entity.push_back(entity_corner6);
880 entity.push_back(entity_corner7);
881 entity.push_back(entity_center);
882
883 add_pattern_knotholder();
884 add_hatch_knotholder();
885}
886
887/* SPMarker */
888
889// marker x scale = (marker width)/(view box width)
890double
892
893 auto sp_marker = cast<SPMarker>(item);
894 g_assert(sp_marker != nullptr);
895
896 return ((sp_marker->viewBox.width() != 0) ? sp_marker->markerWidth.computed/sp_marker->viewBox.width() : 1.0);
897}
898
899double
901
902 auto sp_marker = cast<SPMarker>(item);
903 g_assert(sp_marker != nullptr);
904
905 return ((sp_marker->viewBox.height() != 0) ? sp_marker->markerHeight.computed/sp_marker->viewBox.height() : 1.0);
906}
907
908/*
909- edit_rotation is the tangent angle that is used in orient auto mode.
910- edit_rotation is applied in the edit_transform, it needs to be undone and then the orient.computed can be applied.
911*/
913getMarkerRotation(SPItem* item, double edit_rotation, int edit_marker_mode, bool reverse = false){
914
915 auto sp_marker = cast<SPMarker>(item);
916 g_assert(sp_marker != nullptr);
917
919
920 if ((sp_marker->orient_mode == MARKER_ORIENT_AUTO_START_REVERSE) && (edit_marker_mode == SP_MARKER_LOC_START)) {
921 rot = Geom::Rotate::from_degrees(180.0);
922 } else if (sp_marker->orient_mode == MARKER_ORIENT_ANGLE) {
923 rot = reverse? Geom::Rotate::from_degrees(edit_rotation - sp_marker->orient.computed) : Geom::Rotate::from_degrees(sp_marker->orient.computed - edit_rotation);
924 }
925
926 return rot;
927}
928
929// used to translate the knots when the marker's minimum bounds are less than zero.
932 auto sp_marker = cast<SPMarker>(item);
934
935 g_assert(sp_marker != nullptr);
936 g_assert(doc != nullptr);
937
938 std::vector<SPObject*> items = sp_marker->childList(false, SPObject::ActionBBox);
940
941 for (auto *i : items) {
942 auto item = cast<SPItem>(i);
944 }
945 Geom::Rect bounds(r->min() * doc->dt2doc(), r->max() * doc->dt2doc());
946 return bounds;
947}
948
949/*
950- this knot sets the refX/refY attributes of the marker
951- this knot is actually shown in the center of the shape vs the actual
952refX/refY position to make it more intuitive
953*/
954
955class MarkerKnotHolderEntityReference : public KnotHolderEntity {
956public:
957 double _edit_rotation = 0.0;
958 int _edit_marker_mode = -1;
959
960 MarkerKnotHolderEntityReference(double edit_rotation, int edit_marker_mode)
961 : _edit_rotation(edit_rotation),
962 _edit_marker_mode(edit_marker_mode)
963 {
964 }
965
966 Geom::Point knot_get() const override;
967 void knot_ungrabbed(Geom::Point const &p, Geom::Point const &origin, guint state) override {};
968 void knot_set(Geom::Point const &p, Geom::Point const &origin, unsigned int state) override;
969};
970
971
973MarkerKnotHolderEntityReference::knot_get() const
974{
975 auto sp_marker = cast<SPMarker>(item);
976 g_assert(sp_marker != nullptr);
977
978 // knot is actually shown at center of marker, not at its reference point
979 return Geom::Point((-sp_marker->refX.computed + getMarkerBounds(item, desktop).min()[Geom::X] + sp_marker->viewBox.width()/2) * getMarkerXScale(item),
980 (-sp_marker->refY.computed + getMarkerBounds(item, desktop).min()[Geom::Y] + sp_marker->viewBox.height()/2) * getMarkerYScale(item))
981 * getMarkerRotation(item, _edit_rotation, _edit_marker_mode);
982}
983
984void
985MarkerKnotHolderEntityReference::knot_set(Geom::Point const &p, Geom::Point const &/*origin*/, unsigned int state)
986{
987 auto sp_marker = cast<SPMarker>(item);
988 g_assert(sp_marker != nullptr);
989
990 Geom::Point s = -p;
991 s = s * getMarkerRotation(item, _edit_rotation, _edit_marker_mode, true);
992 sp_marker->refX = (s[Geom::X]/ getMarkerXScale(item)) + getMarkerBounds(item, desktop).min()[Geom::X] + sp_marker->viewBox.width()/2;
993 sp_marker->refY = (s[Geom::Y]/ getMarkerYScale(item)) + getMarkerBounds(item, desktop).min()[Geom::Y] + sp_marker->viewBox.height()/2;
994
995 sp_marker->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
996}
997
998// marker orient section - handles rotation
999
1000class MarkerKnotHolderEntityOrient : public KnotHolderEntity {
1001public:
1002 double _edit_rotation = 0.0;
1003 int _edit_marker_mode = -1;
1004
1005 bool originals_set = false;
1006
1007 // angle that the center of the marker makes with the orient knot
1008 double original_center_angle = 0;
1009 double original_radius = 0;
1010 Geom::Point original_center = Geom::Point(0, 0);
1011
1012 MarkerKnotHolderEntityOrient(double edit_rotation, int edit_marker_mode)
1013 : _edit_rotation(edit_rotation),
1014 _edit_marker_mode(edit_marker_mode)
1015 {
1016 }
1017
1018 Geom::Point knot_get() const override;
1019 void knot_ungrabbed(Geom::Point const &p, Geom::Point const &origin, guint state) override;
1020 void knot_set(Geom::Point const &p, Geom::Point const &origin, unsigned int state) override;
1021
1022protected:
1023 void set_internal(Geom::Point const &p, Geom::Point const &origin, unsigned int state);
1024
1025};
1026
1028 originals_set = false;
1029}
1030
1032MarkerKnotHolderEntityOrient::knot_get() const
1033{
1034 auto sp_marker = cast<SPMarker>(item);
1035 g_assert(sp_marker != nullptr);
1036
1037 return Geom::Point(
1038 (-sp_marker->refX.computed + sp_marker->viewBox.width() + getMarkerBounds(item, desktop).min()[Geom::X]) * getMarkerXScale(item),
1039 (-sp_marker->refY.computed + getMarkerBounds(item, desktop).min()[Geom::Y]) * getMarkerYScale(item))
1040 * getMarkerRotation(item, _edit_rotation, _edit_marker_mode);
1041}
1042
1043void
1044MarkerKnotHolderEntityOrient::knot_set(Geom::Point const &p, Geom::Point const &origin, unsigned int state)
1045{
1046 if(!originals_set) {
1047 auto sp_marker = cast<SPMarker>(item);
1048 g_assert(sp_marker != nullptr);
1049
1050 /*
1051 - if the marker is set to auto or auto-start-reverse, set its type to orient
1052 - calculate and set the default angle for the orient mode
1053 */
1054 if (sp_marker->orient_mode != MARKER_ORIENT_ANGLE) {
1055 sp_marker->orient = (((sp_marker->orient_mode == MARKER_ORIENT_AUTO_START_REVERSE) && (_edit_marker_mode == SP_MARKER_LOC_START)) ? _edit_rotation + 180.0 : _edit_rotation);
1056 sp_marker->orient_mode = MARKER_ORIENT_ANGLE;
1057 sp_marker->orient_set = true;
1058 }
1059
1060 /*
1061 - the original marker center is used to calculate the angle with mouse
1062 - the refX/refY will be changing to adjust for the new rotation to give appearance that it is stationary onCanvas while editing.
1063 */
1064 original_center = Geom::Point(
1065 (-sp_marker->refX.computed + getMarkerBounds(item, desktop).min()[Geom::X] + sp_marker->viewBox.width()/2) * getMarkerXScale(item),
1066 (-sp_marker->refY.computed + getMarkerBounds(item, desktop).min()[Geom::Y] + sp_marker->viewBox.height()/2) * getMarkerYScale(item))
1067 * getMarkerRotation(item, _edit_rotation, _edit_marker_mode);
1068
1069 original_center_angle = atan2(
1070 sp_marker->markerHeight.computed - sp_marker->markerHeight.computed/2,
1071 sp_marker->markerWidth.computed - sp_marker->markerWidth.computed/2
1072 ) * 180.0/M_PI;
1073
1074 original_radius = L2(original_center);
1075 originals_set = true;
1076 }
1077
1078 set_internal(p, origin, state);
1079 update_knot();
1080}
1081
1082void
1083MarkerKnotHolderEntityOrient::set_internal(Geom::Point const &p, Geom::Point const &origin, unsigned int state)
1084{
1085 auto sp_marker = cast<SPMarker>(item);
1086 g_assert(sp_marker != nullptr);
1087
1088 // edit_rotation is the tangest angle to the shapes and needs to be taken into account while setting the orient angle
1089 double new_angle = atan2(p[Geom::Y] - original_center[Geom::Y], p[Geom::X] - original_center[Geom::X]) * 180.0/M_PI;
1090 new_angle = new_angle + _edit_rotation + original_center_angle;
1091
1092 double axis_angle = -((atan2(original_center) * 180.0/M_PI) + _edit_rotation);
1093
1094 sp_marker->orient = new_angle;
1095 sp_marker->orient_mode = MARKER_ORIENT_ANGLE;
1096 sp_marker->orient_set = true;
1097
1099 (-(original_radius * cos(-(axis_angle + sp_marker->orient.computed) * M_PI/180.0))/getMarkerXScale(item)) + getMarkerBounds(item, desktop).min()[Geom::X] + sp_marker->viewBox.width()/2,
1100 (-(original_radius * sin(-(axis_angle + sp_marker->orient.computed) * M_PI/180.0))/getMarkerYScale(item)) + getMarkerBounds(item, desktop).min()[Geom::Y] + sp_marker->viewBox.height()/2);
1101
1102 sp_marker->refX = ref[Geom::X];
1103 sp_marker->refY = ref[Geom::Y];
1104
1105 sp_marker->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
1106}
1107
1108// marker has multiple scaling knots at its corners
1109
1110class MarkerKnotHolderEntityScale : public KnotHolderEntity {
1111public:
1112 double _edit_rotation = 0.0;
1113 int _edit_marker_mode = -1;
1114
1115 /*
1116 - related to the position(+/-) of the scaling knot in reference to the center
1117 - makes sure scaling works correctly for derived classes
1118 */
1119 int _x_Sign = 1;
1120 int _y_Sign = 1;
1121
1122 bool originals_set = false;
1123
1124 double original_scaleX = 1;
1125 double original_scaleY = 1;
1126
1127 double original_refX = 0;
1128 double original_refY = 0;
1129
1130 double original_width = 0;
1131 double original_height = 0;
1132
1133 MarkerKnotHolderEntityScale(double edit_rotation, int edit_marker_mode, int x_Sign, int y_Sign)
1134 : _edit_rotation(edit_rotation),
1135 _edit_marker_mode(edit_marker_mode),
1136 _x_Sign(x_Sign),
1137 _y_Sign(y_Sign)
1138 {
1139 }
1140
1141 void knot_ungrabbed(Geom::Point const &p, Geom::Point const &origin, guint state) override;
1142 void knot_set(Geom::Point const &p, Geom::Point const &origin, unsigned int state) override;
1143 Geom::Point knot_get() const override;
1144
1145protected:
1146 void set_internal(Geom::Point const &p, Geom::Point const &origin, unsigned int state);
1147
1148};
1149
1150void
1152 originals_set = false;
1153}
1154
1156MarkerKnotHolderEntityScale::knot_get() const
1157{
1158 auto sp_marker = cast<SPMarker>(item);
1159 g_assert(sp_marker != nullptr);
1160
1161 return Geom::Point(
1162 (-sp_marker->refX.computed + sp_marker->viewBox.width() + getMarkerBounds(item, desktop).min()[Geom::X]) * getMarkerXScale(item),
1163 (-sp_marker->refY.computed + sp_marker->viewBox.height() + getMarkerBounds(item, desktop).min()[Geom::Y]) * getMarkerYScale(item))
1164 * getMarkerRotation(item, _edit_rotation, _edit_marker_mode);
1165}
1166
1167void
1168MarkerKnotHolderEntityScale::knot_set(Geom::Point const &p, Geom::Point const &origin, unsigned int state)
1169{
1170 // keep track of the original values before the knot/mouse position is being moved
1171 if(!originals_set) {
1172
1173 auto sp_marker = cast<SPMarker>(item);
1174 g_assert(sp_marker != nullptr);
1175
1176 original_scaleX = getMarkerXScale(item);
1177 original_scaleY = getMarkerYScale(item);
1178
1179 original_refX = sp_marker->refX.computed;
1180 original_refY = sp_marker->refY.computed;
1181
1182 original_width = sp_marker->viewBox.width();
1183 original_height = sp_marker->viewBox.height();
1184
1185 originals_set = true;
1186 }
1187
1188 set_internal(p, origin, state);
1189 update_knot();
1190}
1191
1192// scaling takes place around center of marker, not its reference point
1193void
1194MarkerKnotHolderEntityScale::set_internal(Geom::Point const &p, Geom::Point const &origin, unsigned int state)
1195{
1196 auto sp_marker = cast<SPMarker>(item);
1197 g_assert(sp_marker != nullptr);
1198
1199 Geom::Point adjusted_origin = origin;
1200 Geom::Point adjusted_p = p;
1201
1202 if(sp_marker->orient_mode == MARKER_ORIENT_ANGLE) {
1203
1204 adjusted_origin = adjusted_origin
1206 * Geom::Rotate::from_degrees(_edit_rotation - sp_marker->orient.computed);
1207
1208 adjusted_p = adjusted_p
1210 * Geom::Rotate::from_degrees(_edit_rotation - sp_marker->orient.computed);
1211
1212 } else if ((sp_marker->orient_mode == MARKER_ORIENT_AUTO_START_REVERSE) && (_edit_marker_mode == SP_MARKER_LOC_START)) {
1213
1214 adjusted_origin = adjusted_origin
1217
1218 adjusted_p = adjusted_p
1221 }
1222
1223 // x_Sign and y_Sign are (+/- 1) to set the appropriate sign for derived classes
1224 double orig_width = _x_Sign*((original_width * original_scaleX)/2);
1225 double orig_height = _y_Sign*((original_height * original_scaleY)/2);
1226
1227 // x & y displacement between origin and new mouse displacement
1228 double dx = adjusted_p[Geom::X] - adjusted_origin[Geom::X];
1229 double dy = adjusted_p[Geom::Y] - adjusted_origin[Geom::Y];
1230 double adjusted_scaleX = 0.0;
1231 double adjusted_scaleY = 0.0;
1232
1233 adjusted_scaleX = (dx/orig_width) + 1;
1234 adjusted_scaleY = (dy/orig_height) + 1;
1235
1236 // uniform scaling when ctrl+key is pressed
1237 if(state & GDK_CONTROL_MASK) {
1238 adjusted_scaleX = fabs(adjusted_scaleX);
1239 adjusted_scaleY = fabs(adjusted_scaleY);
1240
1241 // possible areas based on which x/y coord is used to calculate uniform scale
1242 double dx_area = (sp_marker->viewBox.width()*adjusted_scaleX) * (sp_marker->viewBox.height()*adjusted_scaleX); // A = W*H
1243 double dy_area = (sp_marker->viewBox.width()*adjusted_scaleY) * (sp_marker->viewBox.height()*adjusted_scaleY);
1244
1245 if (dy_area > dx_area) {
1246 adjusted_scaleX = adjusted_scaleY;
1247 } else if (dx_area > dy_area) {
1248 adjusted_scaleY = adjusted_scaleX;
1249 }
1250
1251 adjusted_scaleX = adjusted_scaleX * original_scaleX;
1252 adjusted_scaleY = adjusted_scaleY * original_scaleY;
1253
1254 sp_marker->markerWidth = sp_marker->viewBox.width() * adjusted_scaleX;
1255 sp_marker->markerHeight = sp_marker->viewBox.height() * adjusted_scaleY;
1256
1257 sp_marker->refX = ((original_refX * original_scaleX)/adjusted_scaleX) - ((getMarkerBounds(item, desktop).min()[Geom::X] + sp_marker->viewBox.width()/2) * (original_scaleX/adjusted_scaleX - 1));
1258 sp_marker->refY = ((original_refY * original_scaleY)/adjusted_scaleY) - ((getMarkerBounds(item, desktop).min()[Geom::Y] + sp_marker->viewBox.height()/2) * (original_scaleY/adjusted_scaleY - 1));
1259 } else {
1260
1261 adjusted_scaleX = adjusted_scaleX * original_scaleX;
1262 adjusted_scaleY = adjusted_scaleY * original_scaleY;
1263
1264 // make sure the preserveAspectRatio is none when the user wants to use non-uniform scaling
1265 if (sp_marker->aspect_align != SP_ASPECT_NONE) {
1266 sp_marker->setAttribute("preserveAspectRatio", "none");
1267 }
1268
1269 if(adjusted_scaleX > 0.0 && adjusted_scaleY > 0.0) {
1270 sp_marker->markerWidth = sp_marker->viewBox.width() * adjusted_scaleX;
1271 sp_marker->markerHeight = sp_marker->viewBox.height() * adjusted_scaleY;
1272
1273 sp_marker->refX = ((original_refX * original_scaleX)/adjusted_scaleX) - ((getMarkerBounds(item, desktop).min()[Geom::X] + sp_marker->viewBox.width()/2) * (original_scaleX/adjusted_scaleX - 1));
1274 sp_marker->refY = ((original_refY * original_scaleY)/adjusted_scaleY) - ((getMarkerBounds(item, desktop).min()[Geom::Y] + sp_marker->viewBox.height()/2) * (original_scaleY/adjusted_scaleY - 1));
1275 }
1276 }
1277
1278 sp_marker->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_VIEWPORT_MODIFIED_FLAG);
1279}
1280
1281class MarkerKnotHolderEntityScale2 : public MarkerKnotHolderEntityScale {
1282public:
1283 MarkerKnotHolderEntityScale2(double edit_rotation, int edit_marker_mode, int x_Sign, int y_Sign)
1284 : MarkerKnotHolderEntityScale(edit_rotation, edit_marker_mode, x_Sign, y_Sign)
1285 {
1286 }
1287
1288 Geom::Point knot_get() const override;
1289};
1290
1292MarkerKnotHolderEntityScale2::knot_get() const
1293{
1294 auto sp_marker = cast<SPMarker>(item);
1295 g_assert(sp_marker != nullptr);
1296
1297 // this corresponds to the reference point
1298 return Geom::Point((-sp_marker->refX.computed + getMarkerBounds(item, desktop).min()[Geom::X]) * getMarkerXScale(item),
1299 (-sp_marker->refY.computed + getMarkerBounds(item, desktop).min()[Geom::Y]) * getMarkerYScale(item))
1300 * getMarkerRotation(item, _edit_rotation, _edit_marker_mode);
1301}
1302
1303
1304class MarkerKnotHolderEntityScale3 : public MarkerKnotHolderEntityScale {
1305public:
1306 MarkerKnotHolderEntityScale3(double edit_rotation, int edit_marker_mode, int x_Sign, int y_Sign)
1307 : MarkerKnotHolderEntityScale(edit_rotation, edit_marker_mode, x_Sign, y_Sign)
1308 {
1309 }
1310
1311 Geom::Point knot_get() const override;
1312};
1313
1315MarkerKnotHolderEntityScale3::knot_get() const
1316{
1317 auto sp_marker = cast<SPMarker>(item);
1318 g_assert(sp_marker != nullptr);
1319
1320 return Geom::Point(
1321 (-sp_marker->refX.computed + getMarkerBounds(item, desktop).min()[Geom::X]) * getMarkerXScale(item),
1322 (-sp_marker->refY.computed + sp_marker->viewBox.height() + getMarkerBounds(item, desktop).min()[Geom::Y]) * getMarkerYScale(item))
1323 * getMarkerRotation(item, _edit_rotation, _edit_marker_mode);
1324}
1325
1326MarkerKnotHolder::MarkerKnotHolder(SPDesktop *desktop, SPItem *item, double edit_rotation, int edit_marker_mode)
1328{
1329 MarkerKnotHolderEntityReference *entity_reference = new MarkerKnotHolderEntityReference(edit_rotation, edit_marker_mode);
1330 MarkerKnotHolderEntityOrient *entity_orient = new MarkerKnotHolderEntityOrient(edit_rotation, edit_marker_mode);
1331
1332 MarkerKnotHolderEntityScale *entity_scale = new MarkerKnotHolderEntityScale(edit_rotation, edit_marker_mode, 1, 1);
1333 // these two additional knots have the same scaling functionality but also serve as a fill in for the empty corners of the marker bounding box
1334 MarkerKnotHolderEntityScale2 *entity_scale2 = new MarkerKnotHolderEntityScale2(edit_rotation, edit_marker_mode, -1, -1);
1335 MarkerKnotHolderEntityScale3 *entity_scale3 = new MarkerKnotHolderEntityScale3(edit_rotation, edit_marker_mode, -1, 1);
1336
1337 entity_reference->create(desktop, item, this, Inkscape::CANVAS_ITEM_CTRL_TYPE_SHAPER, "Marker:reference",
1338 _("Drag to adjust the refX/refY position of the marker"));
1339
1340 entity_orient->create(desktop, item, this, Inkscape::CANVAS_ITEM_CTRL_TYPE_ROTATE, "Marker:orient",
1341 _("Adjust marker orientation through rotation"));
1342
1343 entity_scale->create(desktop, item, this, Inkscape::CANVAS_ITEM_CTRL_TYPE_SIZER, "Marker:scale",
1344 _("Adjust the <b>size</b> of the marker"));
1345
1346 entity_scale2->create(desktop, item, this, Inkscape::CANVAS_ITEM_CTRL_TYPE_SIZER, "Marker:scale",
1347 _("Adjust the <b>size</b> of the marker"));
1348
1349 entity_scale3->create(desktop, item, this, Inkscape::CANVAS_ITEM_CTRL_TYPE_SIZER, "Marker:scale",
1350 _("Adjust the <b>size</b> of the marker"));
1351
1352 entity.push_back(entity_reference);
1353 entity.push_back(entity_orient);
1354 entity.push_back(entity_scale);
1355 entity.push_back(entity_scale2);
1356 entity.push_back(entity_scale3);
1357
1358 add_pattern_knotholder();
1359 add_hatch_knotholder();
1360}
1361
1362/* SPArc */
1363
1364class ArcKnotHolderEntityStart : public KnotHolderEntity {
1365public:
1366 Geom::Point knot_get() const override;
1367 void knot_set(Geom::Point const &p, Geom::Point const &origin, unsigned int state) override;
1368 void knot_ungrabbed(Geom::Point const &p, Geom::Point const &origin, guint state) override {};
1369 void knot_click(unsigned int state) override;
1370};
1371
1372class ArcKnotHolderEntityEnd : public KnotHolderEntity {
1373public:
1374 Geom::Point knot_get() const override;
1375 void knot_set(Geom::Point const &p, Geom::Point const &origin, unsigned int state) override;
1376 void knot_ungrabbed(Geom::Point const &p, Geom::Point const &origin, guint state) override {};
1377 void knot_click(unsigned int state) override;
1378};
1379
1380class ArcKnotHolderEntityRX : public KnotHolderEntity {
1381public:
1382 Geom::Point knot_get() const override;
1383 void knot_set(Geom::Point const &p, Geom::Point const &origin, unsigned int state) override;
1384 void knot_ungrabbed(Geom::Point const &p, Geom::Point const &origin, guint state) override {};
1385 void knot_click(unsigned int state) override;
1386};
1387
1388class ArcKnotHolderEntityRY : public KnotHolderEntity {
1389public:
1390 Geom::Point knot_get() const override;
1391 void knot_set(Geom::Point const &p, Geom::Point const &origin, unsigned int state) override;
1392 void knot_ungrabbed(Geom::Point const &p, Geom::Point const &origin, guint state) override {};
1393 void knot_click(unsigned int state) override;
1394};
1395
1396class ArcKnotHolderEntityCenter : public KnotHolderEntity {
1397public:
1398 Geom::Point knot_get() const override;
1399 void knot_ungrabbed(Geom::Point const &p, Geom::Point const &origin, guint state) override {};
1400 void knot_set(Geom::Point const &p, Geom::Point const &origin, unsigned int state) override;
1401};
1402
1403/*
1404 * return values:
1405 * 1 : inside
1406 * 0 : on the curves
1407 * -1 : outside
1408 */
1409static gint
1411{
1412 gdouble dx = (p[Geom::X] - ellipse->cx.computed) / ellipse->rx.computed;
1413 gdouble dy = (p[Geom::Y] - ellipse->cy.computed) / ellipse->ry.computed;
1414
1415 gdouble s = dx * dx + dy * dy;
1416 // We add a bit of a buffer, so there's a decent chance the user will
1417 // be able to adjust the arc without the closed status flipping between
1418 // open and closed during micro mouse movements.
1419 if (s < 0.75) return 1;
1420 if (s > 1.25) return -1;
1421 return 0;
1422}
1423
1424void
1425ArcKnotHolderEntityStart::knot_set(Geom::Point const &p, Geom::Point const &/*origin*/, unsigned int state)
1426{
1427 int snaps = Inkscape::Preferences::get()->getInt("/options/rotationsnapsperpi/value", 12);
1428
1429 auto arc = cast<SPGenericEllipse>(item);
1430 g_assert(arc != nullptr);
1431
1432 gint side = sp_genericellipse_side(arc, p);
1433 if(side != 0) { arc->setArcType( (side == -1) ?
1436
1437 Geom::Point delta = p - Geom::Point(arc->cx.computed, arc->cy.computed);
1438 Geom::Scale sc(arc->rx.computed, arc->ry.computed);
1439
1440 double offset = arc->start - atan2(delta * sc.inverse());
1441 arc->start -= offset;
1442
1443 if ((state & GDK_CONTROL_MASK) && snaps) {
1444 double snaps_radian = M_PI/snaps;
1445 arc->start = std::round(arc->start/snaps_radian) * snaps_radian;
1446 }
1447 if (state & GDK_SHIFT_MASK) {
1448 arc->end -= offset;
1449 }
1450
1451 arc->normalize();
1452 arc->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
1453}
1454
1456ArcKnotHolderEntityStart::knot_get() const
1457{
1458 SPGenericEllipse const *ge = cast<SPGenericEllipse>(item);
1459 g_assert(ge != nullptr);
1460
1461 return ge->getPointAtAngle(ge->start);
1462}
1463
1464void
1465ArcKnotHolderEntityStart::knot_click(unsigned int state)
1466{
1467 auto ge = cast<SPGenericEllipse>(item);
1468 g_assert(ge != nullptr);
1469
1470 if (state & GDK_SHIFT_MASK) {
1471 ge->end = ge->start = 0;
1472 ge->updateRepr();
1473 }
1474}
1475
1476void
1477ArcKnotHolderEntityEnd::knot_set(Geom::Point const &p, Geom::Point const &/*origin*/, unsigned int state)
1478{
1479 int snaps = Inkscape::Preferences::get()->getInt("/options/rotationsnapsperpi/value", 12);
1480
1481 auto arc = cast<SPGenericEllipse>(item);
1482 g_assert(arc != nullptr);
1483
1484 gint side = sp_genericellipse_side(arc, p);
1485 if(side != 0) { arc->setArcType( (side == -1) ?
1488
1489 Geom::Point delta = p - Geom::Point(arc->cx.computed, arc->cy.computed);
1490 Geom::Scale sc(arc->rx.computed, arc->ry.computed);
1491
1492 double offset = arc->end - atan2(delta * sc.inverse());
1493 arc->end -= offset;
1494
1495 if ((state & GDK_CONTROL_MASK) && snaps) {
1496 double snaps_radian = M_PI/snaps;
1497 arc->end = std::round(arc->end/snaps_radian) * snaps_radian;
1498 }
1499 if (state & GDK_SHIFT_MASK) {
1500 arc->start -= offset;
1501 }
1502
1503 arc->normalize();
1504 arc->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
1505}
1506
1508ArcKnotHolderEntityEnd::knot_get() const
1509{
1510 SPGenericEllipse const *ge = cast<SPGenericEllipse>(item);
1511 g_assert(ge != nullptr);
1512
1513 return ge->getPointAtAngle(ge->end);
1514}
1515
1516
1517void
1518ArcKnotHolderEntityEnd::knot_click(unsigned int state)
1519{
1520 auto ge = cast<SPGenericEllipse>(item);
1521 g_assert(ge != nullptr);
1522
1523 if (state & GDK_SHIFT_MASK) {
1524 ge->end = ge->start = 0;
1525 ge->updateRepr();
1526 }
1527}
1528
1529
1530void
1531ArcKnotHolderEntityRX::knot_set(Geom::Point const &p, Geom::Point const &/*origin*/, unsigned int state)
1532{
1533 auto ge = cast<SPGenericEllipse>(item);
1534 g_assert(ge != nullptr);
1535
1536 Geom::Point const s = snap_knot_position(p, state);
1537
1538 ge->rx = fabs( ge->cx.computed - s[Geom::X] );
1539
1540 if ( state & GDK_CONTROL_MASK ) {
1541 ge->ry = ge->rx.computed;
1542 }
1543
1544 item->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
1545}
1546
1548ArcKnotHolderEntityRX::knot_get() const
1549{
1550 SPGenericEllipse const *ge = cast<SPGenericEllipse>(item);
1551 g_assert(ge != nullptr);
1552
1553 return (Geom::Point(ge->cx.computed, ge->cy.computed) - Geom::Point(ge->rx.computed, 0));
1554}
1555
1556void
1557ArcKnotHolderEntityRX::knot_click(unsigned int state)
1558{
1559 auto ge = cast<SPGenericEllipse>(item);
1560 g_assert(ge != nullptr);
1561
1562 if (state & GDK_CONTROL_MASK) {
1563 ge->ry = ge->rx.computed;
1564 ge->updateRepr();
1565 }
1566}
1567
1568void
1569ArcKnotHolderEntityRY::knot_set(Geom::Point const &p, Geom::Point const &/*origin*/, unsigned int state)
1570{
1571 auto ge = cast<SPGenericEllipse>(item);
1572 g_assert(ge != nullptr);
1573
1574 Geom::Point const s = snap_knot_position(p, state);
1575
1576 ge->ry = fabs( ge->cy.computed - s[Geom::Y] );
1577
1578 if ( state & GDK_CONTROL_MASK ) {
1579 ge->rx = ge->ry.computed;
1580 }
1581
1582 item->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
1583}
1584
1586ArcKnotHolderEntityRY::knot_get() const
1587{
1588 SPGenericEllipse const *ge = cast<SPGenericEllipse>(item);
1589 g_assert(ge != nullptr);
1590
1591 return (Geom::Point(ge->cx.computed, ge->cy.computed) - Geom::Point(0, ge->ry.computed));
1592}
1593
1594void
1595ArcKnotHolderEntityRY::knot_click(unsigned int state)
1596{
1597 auto ge = cast<SPGenericEllipse>(item);
1598 g_assert(ge != nullptr);
1599
1600 if (state & GDK_CONTROL_MASK) {
1601 ge->rx = ge->ry.computed;
1602 ge->updateRepr();
1603 }
1604}
1605
1606void
1607ArcKnotHolderEntityCenter::knot_set(Geom::Point const &p, Geom::Point const &/*origin*/, unsigned int state)
1608{
1609 auto ge = cast<SPGenericEllipse>(item);
1610 g_assert(ge != nullptr);
1611
1612 Geom::Point const s = snap_knot_position(p, state);
1613
1614 ge->cx = s[Geom::X];
1615 ge->cy = s[Geom::Y];
1616
1617 item->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
1618}
1619
1621ArcKnotHolderEntityCenter::knot_get() const
1622{
1623 SPGenericEllipse const *ge = cast<SPGenericEllipse>(item);
1624 g_assert(ge != nullptr);
1625
1626 return Geom::Point(ge->cx.computed, ge->cy.computed);
1627}
1628
1629ArcKnotHolder::ArcKnotHolder(SPDesktop *desktop, SPItem *item)
1631{
1632 ArcKnotHolderEntityRX *entity_rx = new ArcKnotHolderEntityRX();
1633 ArcKnotHolderEntityRY *entity_ry = new ArcKnotHolderEntityRY();
1634 ArcKnotHolderEntityStart *entity_start = new ArcKnotHolderEntityStart();
1635 ArcKnotHolderEntityEnd *entity_end = new ArcKnotHolderEntityEnd();
1636 ArcKnotHolderEntityCenter *entity_center = new ArcKnotHolderEntityCenter();
1637
1638 entity_rx->create(desktop, item, this, Inkscape::CANVAS_ITEM_CTRL_TYPE_SIZER, "Arc:rx",
1639 _("Adjust ellipse <b>width</b>, with <b>Ctrl</b> to make circle"));
1640
1641 entity_ry->create(desktop, item, this, Inkscape::CANVAS_ITEM_CTRL_TYPE_SIZER, "Arc:ry",
1642 _("Adjust ellipse <b>height</b>, with <b>Ctrl</b> to make circle"));
1643
1644 entity_start->create(desktop, item, this, Inkscape::CANVAS_ITEM_CTRL_TYPE_ROTATE, "Arc:start",
1645 _("Position the <b>start point</b> of the arc or segment; with <b>Shift</b> to move "
1646 "with <b>end point</b>; with <b>Ctrl</b> to snap angle; drag <b>inside</b> the "
1647 "ellipse for arc, <b>outside</b> for segment"));
1648
1649 entity_end->create(desktop, item, this, Inkscape::CANVAS_ITEM_CTRL_TYPE_ROTATE, "Arc:end",
1650 _("Position the <b>end point</b> of the arc or segment; with <b>Shift</b> to move "
1651 "with <b>start point</b>; with <b>Ctrl</b> to snap angle; drag <b>inside</b> the "
1652 "ellipse for arc, <b>outside</b> for segment"));
1653
1654 entity_center->create(desktop, item, this, Inkscape::CANVAS_ITEM_CTRL_TYPE_MARKER, "Arc:center",
1655 _("Drag to move the ellipse"));
1656
1657 entity.push_back(entity_rx);
1658 entity.push_back(entity_ry);
1659 entity.push_back(entity_start);
1660 entity.push_back(entity_end);
1661 entity.push_back(entity_center);
1662
1663 add_pattern_knotholder();
1664 add_hatch_knotholder();
1665}
1666
1667/* SPStar */
1668
1669class StarKnotHolderEntity1 : public KnotHolderEntity {
1670public:
1671 Geom::Point knot_get() const override;
1672 void knot_set(Geom::Point const &p, Geom::Point const &origin, unsigned int state) override;
1673 void knot_ungrabbed(Geom::Point const &p, Geom::Point const &origin, guint state) override {};
1674 void knot_click(unsigned int state) override;
1675};
1676
1677class StarKnotHolderEntity2 : public KnotHolderEntity {
1678public:
1679 Geom::Point knot_get() const override;
1680 void knot_set(Geom::Point const &p, Geom::Point const &origin, unsigned int state) override;
1681 void knot_ungrabbed(Geom::Point const &p, Geom::Point const &origin, guint state) override {};
1682 void knot_click(unsigned int state) override;
1683};
1684
1685class StarKnotHolderEntityCenter : public KnotHolderEntity {
1686public:
1687 Geom::Point knot_get() const override;
1688 void knot_ungrabbed(Geom::Point const &p, Geom::Point const &origin, guint state) override {};
1689 void knot_set(Geom::Point const &p, Geom::Point const &origin, unsigned int state) override;
1690};
1691
1692void
1693StarKnotHolderEntity1::knot_set(Geom::Point const &p, Geom::Point const &/*origin*/, unsigned int state)
1694{
1695 auto star = cast<SPStar>(item);
1696 g_assert(star != nullptr);
1697
1698 Geom::Point const s = snap_knot_position(p, state);
1699
1700 Geom::Point d = s - star->center;
1701
1702 double arg1 = atan2(d);
1703 double darg1 = arg1 - star->arg[0];
1704
1705 if (state & GDK_ALT_MASK) {
1706 star->randomized = darg1/(star->arg[0] - star->arg[1]);
1707 } else if (state & GDK_SHIFT_MASK) {
1708 star->rounded = darg1/(star->arg[0] - star->arg[1]);
1709 } else if (state & GDK_CONTROL_MASK) {
1710 star->r[0] = L2(d);
1711 } else {
1712 star->r[0] = L2(d);
1713 star->arg[0] = arg1;
1714 star->arg[1] += darg1;
1715 }
1716 star->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
1717}
1718
1719void
1720StarKnotHolderEntity2::knot_set(Geom::Point const &p, Geom::Point const &/*origin*/, unsigned int state)
1721{
1722 auto star = cast<SPStar>(item);
1723 g_assert(star != nullptr);
1724
1725 Geom::Point const s = snap_knot_position(p, state);
1726
1727 if (star->flatsided == false) {
1728 Geom::Point d = s - star->center;
1729
1730 double arg1 = atan2(d);
1731 double darg1 = arg1 - star->arg[1];
1732
1733 if (state & GDK_ALT_MASK) {
1734 star->randomized = darg1/(star->arg[0] - star->arg[1]);
1735 } else if (state & GDK_SHIFT_MASK) {
1736 star->rounded = fabs(darg1/(star->arg[0] - star->arg[1]));
1737 } else if (state & GDK_CONTROL_MASK) {
1738 star->r[1] = L2(d);
1739 star->arg[1] = star->arg[0] + M_PI / star->sides;
1740 }
1741 else {
1742 star->r[1] = L2(d);
1743 star->arg[1] = atan2(d);
1744 }
1745 star->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
1746 }
1747}
1748
1749void
1750StarKnotHolderEntityCenter::knot_set(Geom::Point const &p, Geom::Point const &/*origin*/, unsigned int state)
1751{
1752 auto star = cast<SPStar>(item);
1753 g_assert(star != nullptr);
1754
1755 star->center = snap_knot_position(p, state);
1756
1757 item->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
1758}
1759
1761StarKnotHolderEntity1::knot_get() const
1762{
1763 g_assert(item != nullptr);
1764
1765 SPStar const *star = cast<SPStar>(item);
1766 g_assert(star != nullptr);
1767
1768 return sp_star_get_xy(star, SP_STAR_POINT_KNOT1, 0);
1769
1770}
1771
1773StarKnotHolderEntity2::knot_get() const
1774{
1775 g_assert(item != nullptr);
1776
1777 SPStar const *star = cast<SPStar>(item);
1778 g_assert(star != nullptr);
1779
1780 return sp_star_get_xy(star, SP_STAR_POINT_KNOT2, 0);
1781}
1782
1784StarKnotHolderEntityCenter::knot_get() const
1785{
1786 g_assert(item != nullptr);
1787
1788 SPStar const *star = cast<SPStar>(item);
1789 g_assert(star != nullptr);
1790
1791 return star->center;
1792}
1793
1794static void
1795sp_star_knot_click(SPItem *item, unsigned int state)
1796{
1797 auto star = cast<SPStar>(item);
1798 g_assert(star != nullptr);
1799
1800 if (state & GDK_ALT_MASK) {
1801 star->randomized = 0;
1802 star->updateRepr();
1803 } else if (state & GDK_SHIFT_MASK) {
1804 star->rounded = 0;
1805 star->updateRepr();
1806 } else if (state & GDK_CONTROL_MASK) {
1807 star->arg[1] = star->arg[0] + M_PI / star->sides;
1808 star->updateRepr();
1809 }
1810}
1811
1812void
1813StarKnotHolderEntity1::knot_click(unsigned int state)
1814{
1815 sp_star_knot_click(item, state);
1816}
1817
1818void
1819StarKnotHolderEntity2::knot_click(unsigned int state)
1820{
1821 sp_star_knot_click(item, state);
1822}
1823
1824StarKnotHolder::StarKnotHolder(SPDesktop *desktop, SPItem *item)
1826{
1827 auto star = cast<SPStar>(item);
1828 g_assert(item != nullptr);
1829
1830 StarKnotHolderEntity1 *entity1 = new StarKnotHolderEntity1();
1831 entity1->create(desktop, item, this, Inkscape::CANVAS_ITEM_CTRL_TYPE_SHAPER, "Star:entity1",
1832 _("Adjust the <b>tip radius</b> of the star or polygon; "
1833 "with <b>Shift</b> to round; with <b>Alt</b> to randomize"));
1834
1835 entity.push_back(entity1);
1836
1837 if (star->flatsided == false) {
1838 StarKnotHolderEntity2 *entity2 = new StarKnotHolderEntity2();
1839 entity2->create(desktop, item, this, Inkscape::CANVAS_ITEM_CTRL_TYPE_SHAPER, "Star:entity2",
1840 _("Adjust the <b>base radius</b> of the star; with <b>Ctrl</b> to keep star rays "
1841 "radial (no skew); with <b>Shift</b> to round; with <b>Alt</b> to randomize"));
1842 entity.push_back(entity2);
1843 }
1844
1845 StarKnotHolderEntityCenter *entity_center = new StarKnotHolderEntityCenter();
1846 entity_center->create(desktop, item, this, Inkscape::CANVAS_ITEM_CTRL_TYPE_MARKER, "Star:center",
1847 _("Drag to move the star"));
1848 entity.push_back(entity_center);
1849
1850 add_pattern_knotholder();
1851 add_hatch_knotholder();
1852}
1853
1854/* SPSpiral */
1855
1856class SpiralKnotHolderEntityInner : public KnotHolderEntity {
1857public:
1858 Geom::Point knot_get() const override;
1859 void knot_set(Geom::Point const &p, Geom::Point const &origin, unsigned int state) override;
1860 void knot_ungrabbed(Geom::Point const &p, Geom::Point const &origin, guint state) override {};
1861 void knot_click(unsigned int state) override;
1862};
1863
1864class SpiralKnotHolderEntityOuter : public KnotHolderEntity {
1865public:
1866 Geom::Point knot_get() const override;
1867 void knot_ungrabbed(Geom::Point const &p, Geom::Point const &origin, guint state) override {};
1868 void knot_set(Geom::Point const &p, Geom::Point const &origin, unsigned int state) override;
1869};
1870
1871class SpiralKnotHolderEntityCenter : public KnotHolderEntity {
1872public:
1873 Geom::Point knot_get() const override;
1874 void knot_ungrabbed(Geom::Point const &p, Geom::Point const &origin, guint state) override {};
1875 void knot_set(Geom::Point const &p, Geom::Point const &origin, unsigned int state) override;
1876};
1877
1878
1879/*
1880 * set attributes via inner (t=t0) knot point:
1881 * [default] increase/decrease inner point
1882 * [shift] increase/decrease inner and outer arg synchronizely
1883 * [control] constrain inner arg to round per PI/4
1884 */
1885void
1886SpiralKnotHolderEntityInner::knot_set(Geom::Point const &p, Geom::Point const &origin, unsigned int state)
1887{
1889 int snaps = prefs->getInt("/options/rotationsnapsperpi/value", 12);
1890
1891 auto spiral = cast<SPSpiral>(item);
1892 g_assert(spiral != nullptr);
1893
1894 gdouble dx = p[Geom::X] - spiral->cx;
1895 gdouble dy = p[Geom::Y] - spiral->cy;
1896
1897 gdouble moved_y = p[Geom::Y] - origin[Geom::Y];
1898
1899 if (state & GDK_ALT_MASK) {
1900 // adjust divergence by vertical drag, relative to rad
1901 if (spiral->rad > 0) {
1902 double exp_delta = 0.1*moved_y/(spiral->rad); // arbitrary multiplier to slow it down
1903 spiral->exp += exp_delta;
1904 if (spiral->exp < 1e-3)
1905 spiral->exp = 1e-3;
1906 }
1907 } else {
1908 // roll/unroll from inside
1909 gdouble arg_t0;
1910 spiral->getPolar(spiral->t0, nullptr, &arg_t0);
1911
1912 gdouble arg_tmp = atan2(dy, dx) - arg_t0;
1913 gdouble arg_t0_new = arg_tmp - floor((arg_tmp+M_PI)/(2.0*M_PI))*2.0*M_PI + arg_t0;
1914 spiral->t0 = (arg_t0_new - spiral->arg) / (2.0*M_PI*spiral->revo);
1915
1916 /* round inner arg per PI/snaps, if CTRL is pressed */
1917 if ( ( state & GDK_CONTROL_MASK )
1918 && ( fabs(spiral->revo) > SP_EPSILON_2 )
1919 && ( snaps != 0 ) ) {
1920 gdouble arg = 2.0*M_PI*spiral->revo*spiral->t0 + spiral->arg;
1921 double snaps_radian = M_PI/snaps;
1922 spiral->t0 = (std::round(arg/snaps_radian)*snaps_radian - spiral->arg)/(2.0*M_PI*spiral->revo);
1923 }
1924
1925 spiral->t0 = CLAMP(spiral->t0, 0.0, 0.999);
1926 }
1927
1928 spiral->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
1929}
1930
1931/*
1932 * set attributes via outer (t=1) knot point:
1933 * [default] increase/decrease revolution factor
1934 * [control] constrain inner arg to round per PI/4
1935 */
1936void
1937SpiralKnotHolderEntityOuter::knot_set(Geom::Point const &p, Geom::Point const &/*origin*/, unsigned int state)
1938{
1940 int snaps = prefs->getInt("/options/rotationsnapsperpi/value", 12);
1941
1942 auto spiral = cast<SPSpiral>(item);
1943 g_assert(spiral != nullptr);
1944
1945 gdouble dx = p[Geom::X] - spiral->cx;
1946 gdouble dy = p[Geom::Y] - spiral->cy;
1947
1948 if (state & GDK_SHIFT_MASK) { // rotate without roll/unroll
1949 spiral->arg = atan2(dy, dx) - 2.0*M_PI*spiral->revo;
1950 if (!(state & GDK_ALT_MASK)) {
1951 // if alt not pressed, change also rad; otherwise it is locked
1952 spiral->rad = MAX(hypot(dx, dy), 0.001);
1953 }
1954 if ( ( state & GDK_CONTROL_MASK ) && snaps ) {
1955 double snaps_radian = M_PI/snaps;
1956 spiral->arg = std::round(spiral->arg/snaps_radian) * snaps_radian;
1957 }
1958 } else { // roll/unroll
1959 // arg of the spiral outer end
1960 double arg_1;
1961 spiral->getPolar(1, nullptr, &arg_1);
1962
1963 // its fractional part after the whole turns are subtracted
1964 static double _2PI = 2.0 * M_PI;
1965 double arg_r = arg_1 - std::round(arg_1/_2PI) * _2PI;
1966
1967 // arg of the mouse point relative to spiral center
1968 double mouse_angle = atan2(dy, dx);
1969 if (mouse_angle < 0)
1970 mouse_angle += _2PI;
1971
1972 // snap if ctrl
1973 if ( ( state & GDK_CONTROL_MASK ) && snaps ) {
1974 double snaps_radian = M_PI/snaps;
1975 mouse_angle = std::round(mouse_angle/snaps_radian) * snaps_radian;
1976 }
1977
1978 // by how much we want to rotate the outer point
1979 double diff = mouse_angle - arg_r;
1980 if (diff > M_PI)
1981 diff -= _2PI;
1982 else if (diff < -M_PI)
1983 diff += _2PI;
1984
1985 // calculate the new rad;
1986 // the value of t corresponding to the angle arg_1 + diff:
1987 double t_temp = ((arg_1 + diff) - spiral->arg)/(_2PI*spiral->revo);
1988 // the rad at that t:
1989 double rad_new = 0;
1990 if (t_temp > spiral->t0)
1991 spiral->getPolar(t_temp, &rad_new, nullptr);
1992
1993 // change the revo (converting diff from radians to the number of turns)
1994 spiral->revo += diff/(2*M_PI);
1995 if (spiral->revo < 1e-3)
1996 spiral->revo = 1e-3;
1997
1998 // if alt not pressed and the values are sane, change the rad
1999 if (!(state & GDK_ALT_MASK) && rad_new > 1e-3 && rad_new/spiral->rad < 2) {
2000 // adjust t0 too so that the inner point stays unmoved
2001 double r0;
2002 spiral->getPolar(spiral->t0, &r0, nullptr);
2003 spiral->rad = rad_new;
2004 spiral->t0 = pow(r0 / spiral->rad, 1.0/spiral->exp);
2005 }
2006 if (!std::isfinite(spiral->t0)) spiral->t0 = 0.0;
2007 spiral->t0 = CLAMP(spiral->t0, 0.0, 0.999);
2008 }
2009
2010 spiral->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
2011}
2012
2013void
2014SpiralKnotHolderEntityCenter::knot_set(Geom::Point const &p, Geom::Point const &/*origin*/, unsigned int state)
2015{
2016 auto spiral = cast<SPSpiral>(item);
2017 g_assert(spiral != nullptr);
2018
2019 Geom::Point const s = snap_knot_position(p, state);
2020
2021 spiral->cx = s[Geom::X];
2022 spiral->cy = s[Geom::Y];
2023
2024 spiral->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
2025}
2026
2028SpiralKnotHolderEntityInner::knot_get() const
2029{
2030 SPSpiral const *spiral = cast<SPSpiral>(item);
2031 g_assert(spiral != nullptr);
2032
2033 return spiral->getXY(spiral->t0);
2034}
2035
2037SpiralKnotHolderEntityOuter::knot_get() const
2038{
2039 SPSpiral const *spiral = cast<SPSpiral>(item);
2040 g_assert(spiral != nullptr);
2041
2042 return spiral->getXY(1.0);
2043}
2044
2046SpiralKnotHolderEntityCenter::knot_get() const
2047{
2048 SPSpiral const *spiral = cast<SPSpiral>(item);
2049 g_assert(spiral != nullptr);
2050
2051 return Geom::Point(spiral->cx, spiral->cy);
2052}
2053
2054void
2056{
2057 auto spiral = cast<SPSpiral>(item);
2058 g_assert(spiral != nullptr);
2059
2060 if (state & GDK_ALT_MASK) {
2061 spiral->exp = 1;
2062 spiral->updateRepr();
2063 } else if (state & GDK_SHIFT_MASK) {
2064 spiral->t0 = 0;
2065 spiral->updateRepr();
2066 }
2067}
2068
2069SpiralKnotHolder::SpiralKnotHolder(SPDesktop *desktop, SPItem *item)
2071{
2072 SpiralKnotHolderEntityCenter *entity_center = new SpiralKnotHolderEntityCenter();
2073 SpiralKnotHolderEntityInner *entity_inner = new SpiralKnotHolderEntityInner();
2074 SpiralKnotHolderEntityOuter *entity_outer = new SpiralKnotHolderEntityOuter();
2075
2076 // NOTE: entity_central and entity_inner can overlap.
2077 //
2078 // In that case it would be a problem if the center control point was ON
2079 // TOP because it would steal the mouse focus and the user would loose the
2080 // ability to access the inner control point using only the mouse.
2081 //
2082 // However if the inner control point is ON TOP, taking focus, the
2083 // situation is a lot better: the user can still move the inner control
2084 // point with the mouse to regain access to the center control point.
2085 //
2086 // So, create entity_inner AFTER entity_center; this ensures that
2087 // entity_inner gets rendered ON TOP.
2088 entity_center->create(desktop, item, this, Inkscape::CANVAS_ITEM_CTRL_TYPE_MARKER, "Spiral:center",
2089 _("Drag to move the spiral"));
2090
2091 entity_inner->create(desktop, item, this, Inkscape::CANVAS_ITEM_CTRL_TYPE_SHAPER, "Spiral:inner",
2092 _("Roll/unroll the spiral from <b>inside</b>; with <b>Ctrl</b> to snap angle; "
2093 "with <b>Alt</b> to converge/diverge"));
2094
2095 entity_outer->create(desktop, item, this, Inkscape::CANVAS_ITEM_CTRL_TYPE_SHAPER, "Spiral:outer",
2096 _("Roll/unroll the spiral from <b>outside</b>; with <b>Ctrl</b> to snap angle; "
2097 "with <b>Shift</b> to scale/rotate; with <b>Alt</b> to lock radius"));
2098
2099 entity.push_back(entity_center);
2100 entity.push_back(entity_inner);
2101 entity.push_back(entity_outer);
2102
2103 add_pattern_knotholder();
2104 add_hatch_knotholder();
2105}
2106
2107/* SPOffset */
2108
2109class OffsetKnotHolderEntity : public KnotHolderEntity {
2110public:
2111 Geom::Point knot_get() const override;
2112 void knot_ungrabbed(Geom::Point const &p, Geom::Point const &origin, guint state) override {};
2113 void knot_set(Geom::Point const &p, Geom::Point const &origin, unsigned int state) override;
2114};
2115
2116void
2117OffsetKnotHolderEntity::knot_set(Geom::Point const &p, Geom::Point const &/*origin*/, unsigned int state)
2118{
2119 auto offset = cast<SPOffset>(item);
2120 g_assert(offset != nullptr);
2121
2122 Geom::Point const p_snapped = snap_knot_position(p, state);
2123
2124 offset->rad = sp_offset_distance_to_original(offset, p_snapped);
2125 offset->knot = p_snapped;
2126 offset->knotSet = true;
2127
2128 offset->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
2129}
2130
2131
2133OffsetKnotHolderEntity::knot_get() const
2134{
2135 SPOffset const *offset = cast<SPOffset>(item);
2136 g_assert(offset != nullptr);
2137
2138 Geom::Point np;
2140 return np;
2141}
2142
2143OffsetKnotHolder::OffsetKnotHolder(SPDesktop *desktop, SPItem *item)
2145{
2146 OffsetKnotHolderEntity *entity_offset = new OffsetKnotHolderEntity();
2147 entity_offset->create(desktop, item, this, Inkscape::CANVAS_ITEM_CTRL_TYPE_SHAPER, "Offset:entity",
2148 _("Adjust the <b>offset distance</b>"));
2149 entity.push_back(entity_offset);
2150
2151 add_pattern_knotholder();
2152 add_hatch_knotholder();
2153}
2154
2155
2156/* SPText */
2157class TextKnotHolderEntityInlineSize : public KnotHolderEntity {
2158public:
2159 Geom::Point knot_get() const override;
2160 void knot_set(Geom::Point const &p, Geom::Point const &origin, unsigned int state) override;
2161 void knot_ungrabbed(Geom::Point const &p, Geom::Point const &origin, guint state) override {};
2162 void knot_click(unsigned int state) override;
2163};
2164
2166TextKnotHolderEntityInlineSize::knot_get() const
2167{
2168 auto text = cast<SPText>(item);
2169 g_assert(text != nullptr);
2170
2171 SPStyle* style = text->style;
2172 double inline_size = style->inline_size.computed;
2173 unsigned mode = style->writing_mode.computed;
2174 unsigned anchor = style->text_anchor.computed;
2175 unsigned direction = style->direction.computed;
2176
2177 Geom::Point p(text->attributes.firstXY());
2178
2179 if (text->has_inline_size()) {
2180 // SVG 2 'inline-size'
2181
2182 // Keep handle at end of text line.
2185 // horizontal
2186 if ( (direction == SP_CSS_DIRECTION_LTR && anchor == SP_CSS_TEXT_ANCHOR_START ) ||
2187 (direction == SP_CSS_DIRECTION_RTL && anchor == SP_CSS_TEXT_ANCHOR_END) ) {
2188 p *= Geom::Translate (inline_size, 0);
2189 } else if ( direction == SP_CSS_DIRECTION_LTR && anchor == SP_CSS_TEXT_ANCHOR_MIDDLE) {
2190 p *= Geom::Translate (inline_size/2.0, 0 );
2191 } else if ( direction == SP_CSS_DIRECTION_RTL && anchor == SP_CSS_TEXT_ANCHOR_MIDDLE) {
2192 p *= Geom::Translate (-inline_size/2.0, 0 );
2193 } else if ( (direction == SP_CSS_DIRECTION_LTR && anchor == SP_CSS_TEXT_ANCHOR_END ) ||
2194 (direction == SP_CSS_DIRECTION_RTL && anchor == SP_CSS_TEXT_ANCHOR_START) ) {
2195 p *= Geom::Translate (-inline_size, 0);
2196 }
2197 } else {
2198 // vertical
2199 if (anchor == SP_CSS_TEXT_ANCHOR_START) {
2200 p *= Geom::Translate (0, inline_size);
2201 } else if (anchor == SP_CSS_TEXT_ANCHOR_MIDDLE) {
2202 p *= Geom::Translate (0, inline_size/2.0);
2203 } else if (anchor == SP_CSS_TEXT_ANCHOR_END) {
2204 p *= Geom::Translate (0, -inline_size);
2205 }
2206 }
2207 } else {
2208 // Normal single line text.
2209 Geom::OptRect bbox = text->geometricBounds(); // Check if this is best.
2210 if (bbox) {
2213 // horizontal
2214 if ( (direction == SP_CSS_DIRECTION_LTR && anchor == SP_CSS_TEXT_ANCHOR_START ) ||
2215 (direction == SP_CSS_DIRECTION_RTL && anchor == SP_CSS_TEXT_ANCHOR_END) ) {
2216 p *= Geom::Translate ((*bbox).width(), 0);
2217 } else if ( direction == SP_CSS_DIRECTION_LTR && anchor == SP_CSS_TEXT_ANCHOR_MIDDLE) {
2218 p *= Geom::Translate ((*bbox).width()/2, 0);
2219 } else if ( direction == SP_CSS_DIRECTION_RTL && anchor == SP_CSS_TEXT_ANCHOR_MIDDLE) {
2220 p *= Geom::Translate (-(*bbox).width()/2, 0);
2221 } else if ( (direction == SP_CSS_DIRECTION_LTR && anchor == SP_CSS_TEXT_ANCHOR_END ) ||
2222 (direction == SP_CSS_DIRECTION_RTL && anchor == SP_CSS_TEXT_ANCHOR_START) ) {
2223 p *= Geom::Translate (-(*bbox).width(), 0);
2224 }
2225 } else {
2226 // vertical
2227 if (anchor == SP_CSS_TEXT_ANCHOR_START) {
2228 p *= Geom::Translate (0, (*bbox).height());
2229 } else if (anchor == SP_CSS_TEXT_ANCHOR_MIDDLE) {
2230 p *= Geom::Translate (0, (*bbox).height()/2);
2231 } else if (anchor == SP_CSS_TEXT_ANCHOR_END) {
2232 p *= Geom::Translate (0, -(*bbox).height());
2233 }
2235 p += Geom::Point((*bbox).width(), 0); // Keep on right side
2236 }
2237 }
2238 }
2239 }
2240
2241 return p;
2242}
2243
2244// Conversion from Inkscape SVG 1.1 to SVG 2 'inline-size'.
2245void
2246TextKnotHolderEntityInlineSize::knot_set(Geom::Point const &p, Geom::Point const &/*origin*/, unsigned int state)
2247{
2248 auto text = cast<SPText>(item);
2249 g_assert(text != nullptr);
2250
2251 SPStyle* style = text->style;
2252 unsigned mode = style->writing_mode.computed;
2253 unsigned anchor = style->text_anchor.computed;
2254 unsigned direction = style->direction.computed;
2255
2256 Geom::Point const s = snap_knot_position(p, state);
2257 Geom::Point delta = s - text->attributes.firstXY();
2258 double size = 0.0;
2261 // horizontal
2262
2263 size = delta[Geom::X];
2264 if ( (direction == SP_CSS_DIRECTION_LTR && anchor == SP_CSS_TEXT_ANCHOR_START ) ||
2265 (direction == SP_CSS_DIRECTION_RTL && anchor == SP_CSS_TEXT_ANCHOR_END) ) {
2266 // Do nothing
2267 } else if ( (direction == SP_CSS_DIRECTION_LTR && anchor == SP_CSS_TEXT_ANCHOR_END ) ||
2268 (direction == SP_CSS_DIRECTION_RTL && anchor == SP_CSS_TEXT_ANCHOR_START) ) {
2269 size = -size;
2270 } else if ( anchor == SP_CSS_TEXT_ANCHOR_MIDDLE) {
2271 size = 2.0 * abs(size);
2272 } else {
2273 std::cerr << "TextKnotHolderEntityInlinSize: Should not be reached!" << std::endl;
2274 }
2275
2276 } else {
2277 // vertical
2278
2279 size = delta[Geom::Y];
2280 if (anchor == SP_CSS_TEXT_ANCHOR_START) {
2281 // Do nothing
2282 } else if (anchor == SP_CSS_TEXT_ANCHOR_END) {
2283 size = -size;
2284 } else if (anchor == SP_CSS_TEXT_ANCHOR_MIDDLE) {
2285 size = 2.0 * abs(size);
2286 }
2287 }
2288
2289 // Size should never be negative
2290 if (size < 0.0) {
2291 size = 0.0;
2292 }
2293
2294 // Set 'inline-size'.
2295 text->style->inline_size.setDouble(size);
2296 text->style->inline_size.set = true;
2297
2298 // Ensure we respect new lines.
2299 text->style->white_space.read("pre");
2300 text->style->white_space.set = true;
2301
2302 // Convert sodipodi:role="line" to '\n'.
2303 text->sodipodi_to_newline();
2304
2305 text->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
2306 text->updateRepr();
2307}
2308
2309// Conversion from SVG 2 'inline-size' to Inkscape's SVG 1.1.
2310void
2312{
2313 auto text = cast<SPText>(item);
2314 g_assert(text != nullptr);
2315
2316 if (state & GDK_CONTROL_MASK) {
2317
2318 text->style->inline_size.clear();
2319 text->remove_svg11_fallback(); // Else 'x' and 'y' will be interpreted as absolute positions.
2320 text->newline_to_sodipodi(); // Convert '\n' to tspans with sodipodi:role="line".
2321
2322 text->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
2323 text->updateRepr();
2324 }
2325}
2326
2330class TextKnotHolderEntityShapePadding : public KnotHolderEntity {
2331public:
2332 Geom::Point knot_get() const override;
2333 void knot_ungrabbed(Geom::Point const &p, Geom::Point const &origin, guint state) override {};
2334 void knot_set(Geom::Point const &p, Geom::Point const &origin, unsigned int state) override;
2335};
2336
2338TextKnotHolderEntityShapePadding::knot_get() const
2339{
2340 auto text = cast<SPText>(item);
2341 g_assert(text != nullptr);
2343
2344 if (!text->has_shape_inside()) {
2345 return corner;
2346 }
2347
2348 auto shape = text->get_first_shape_dependency();
2349 if (!shape) {
2350 return corner;
2351 }
2352
2353 Geom::OptRect bounds = shape->geometricBounds();
2354 if (bounds) {
2355 corner = (*bounds).corner(1);
2356 if (text->style->shape_padding.set) {
2357 auto padding = text->style->shape_padding.computed;
2358 corner *= Geom::Affine(Geom::Translate(-padding, padding));
2359 }
2360 corner *= shape->transform;
2361 }
2362 return corner;
2363}
2364
2365void
2366TextKnotHolderEntityShapePadding::knot_set(Geom::Point const &p, Geom::Point const &/*origin*/, unsigned int state)
2367{
2368 // Text in a shape: rectangle
2369 auto text = cast<SPText>(item);
2370 g_assert(text != nullptr);
2371 if (!text->has_shape_inside()) {
2372 return;
2373 }
2374
2375 if (auto shape = text->get_first_shape_dependency()) {
2376 if (Geom::OptRect optbounds = shape->geometricBounds()) {
2377 auto bounds = *optbounds;
2378 Geom::Point const point_a = snap_knot_position(p, state);
2379 Geom::Point point_b = point_a * shape->transform.inverse();
2380
2381 double padding = 0.0;
2382 if (point_b[Geom::X] - 1 > bounds.midpoint()[Geom::X]) {
2383 padding = bounds.corner(1)[Geom::X] - point_b[Geom::X];
2384 }
2385
2386 // Padding can only be a positive value according to the CSS/text-padding spec
2387 if (padding >= 0.0) {
2389 os << padding;
2390 text->style->shape_padding.read(os.str().c_str());
2391
2392 text->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
2393 text->updateRepr();
2394 }
2395 }
2396 }
2397}
2398
2399
2403class TextKnotHolderEntityShapeMargin : public KnotHolderEntity {
2404public:
2405 Geom::Point knot_get() const override;
2406 void knot_ungrabbed(Geom::Point const &p, Geom::Point const &origin, guint state) override {};
2407 void knot_set(Geom::Point const &p, Geom::Point const &origin, unsigned int state) override;
2408 void set_shape(SPShape *shape) { linked_shape = shape; }
2409 SPShape *linked_shape;
2410};
2411
2413TextKnotHolderEntityShapeMargin::knot_get() const
2414{
2415 Geom::Point corner;
2416 if (linked_shape == nullptr) return corner;
2417
2418 Geom::OptRect bounds = linked_shape->geometricBounds();
2419 if (bounds) {
2420 corner = (*bounds).corner(1);
2421 if (linked_shape->style->shape_margin.set) {
2422 auto margin = linked_shape->style->shape_margin.computed;
2424 }
2425 corner *= linked_shape->transform;
2426 }
2427 return corner;
2428}
2429
2430void
2431TextKnotHolderEntityShapeMargin::knot_set(Geom::Point const &p, Geom::Point const &/*origin*/, unsigned int state)
2432{
2433 g_assert(linked_shape != nullptr);
2434
2435 Geom::OptRect bounds = linked_shape->geometricBounds();
2436 if (bounds) {
2437 Geom::Point const point_a = snap_knot_position(p, state);
2438 Geom::Point point_b = point_a * linked_shape->transform.inverse();
2439 auto margin = -((*bounds).corner(1)[Geom::X] - point_b[Geom::X]);
2440
2441 // Margins can only be `non-negative` according to the CSS/shape-margin spec
2442 if (margin >= 0.0) {
2444 os << margin;
2445 linked_shape->style->shape_margin.read(os.str().c_str());
2446
2447 linked_shape->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
2448 linked_shape->updateRepr();
2449 }
2450 }
2451}
2452
2453
2454
2455
2456class TextKnotHolderEntityShapeInside : public KnotHolderEntity {
2457public:
2458 Geom::Point knot_get() const override;
2459 void knot_ungrabbed(Geom::Point const &p, Geom::Point const &origin, guint state) override {};
2460 void knot_set(Geom::Point const &p, Geom::Point const &origin, unsigned int state) override;
2461};
2462
2464TextKnotHolderEntityShapeInside::knot_get() const
2465{
2466 // SVG 2 'shape-inside'. We only get here if there is a rectangle shape.
2467 auto text = cast<SPText>(item);
2468 g_assert(text != nullptr);
2469
2471 if (text->has_shape_inside()) {
2472 Geom::OptRect frame = text->get_frame();
2473 if (frame) {
2474 p = (*frame).corner(2);
2475 } else {
2476 std::cerr << "TextKnotHolderEntityShapeInside::knot_get(): no frame!" << std::endl;
2477 }
2478 }
2479 return p;
2480}
2481
2482void
2483TextKnotHolderEntityShapeInside::knot_set(Geom::Point const &p, Geom::Point const &/*origin*/, unsigned int state)
2484{
2485 // Text in a shape: rectangle
2486 auto text = cast<SPText>(item);
2487 g_assert(text != nullptr);
2488
2489 Geom::Point const s = snap_knot_position(p, state);
2490
2491 Inkscape::XML::Node* rectangle = text->get_first_rectangle();
2492 if (!rectangle) {
2493 return;
2494 }
2495 double x = rectangle->getAttributeDouble("x", 0.0);;
2496 double y = rectangle->getAttributeDouble("y", 0.0);
2497 double width = s[Geom::X] - x;
2498 double height = s[Geom::Y] - y;
2499 rectangle->setAttributeSvgDouble("width", width);
2500 rectangle->setAttributeSvgDouble("height", height);
2501 text->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
2502 text->updateRepr();
2503}
2504
2505TextKnotHolder::TextKnotHolder(SPDesktop *desktop, SPItem *item)
2507{
2508 auto text = cast<SPText>(item);
2509 g_assert(text != nullptr);
2510
2511 if (text->has_shape_inside()) {
2512 // 'shape-inside'
2513
2514 if (text->get_first_rectangle()) {
2515 auto entity_shapeinside = new TextKnotHolderEntityShapeInside();
2516 entity_shapeinside->create(desktop, item, this, Inkscape::CANVAS_ITEM_CTRL_TYPE_SHAPER, "Text:shapeinside",
2517 _("Adjust the <b>rectangular</b> region of the text."));
2518 entity.push_back(entity_shapeinside);
2519 }
2520
2521 if (text->get_first_shape_dependency()) {
2522 auto entity_shapepadding = new TextKnotHolderEntityShapePadding();
2523 entity_shapepadding->create(desktop, item, this, Inkscape::CANVAS_ITEM_CTRL_TYPE_SIZER, "Text:shapepadding",
2524 _("Adjust the text <b>shape padding</b>."));
2525 entity.push_back(entity_shapepadding);
2526 }
2527
2528
2529 // Add knots for shape subtraction margins
2530 if (text->style->shape_subtract.set) {
2531 for (auto *href : text->style->shape_subtract.hrefs) {
2532 if (auto shape = href->getObject()) {
2533 auto entity_shapemargin = new TextKnotHolderEntityShapeMargin();
2534 entity_shapemargin->create(desktop, item, this, Inkscape::CANVAS_ITEM_CTRL_TYPE_SIZER, "Text:shapemargin",
2535 _("Adjust the shape's <b>text margin</b>."));
2536 entity_shapemargin->set_shape(shape);
2537 entity_shapemargin->update_knot();
2538 entity.push_back(entity_shapemargin);
2539 }
2540 }
2541 }
2542
2543 } else {
2544 // 'inline-size' or normal text
2545 TextKnotHolderEntityInlineSize *entity_inlinesize = new TextKnotHolderEntityInlineSize();
2546
2547 entity_inlinesize->create(desktop, item, this, Inkscape::CANVAS_ITEM_CTRL_TYPE_SHAPER, "Text:inlinesize",
2548 _("Adjust the <b>inline size</b> (line length) of the text."));
2549 entity.push_back(entity_inlinesize);
2550 }
2551
2552 add_pattern_knotholder();
2553 add_hatch_knotholder();
2554}
2555
2556
2557// TODO: this is derived from RectKnotHolderEntityWH because it used the same static function
2558// set_internal as the latter before KnotHolderEntity was C++ified. Check whether this also makes
2559// sense logically.
2560class FlowtextKnotHolderEntity : public RectKnotHolderEntityWH {
2561public:
2562 Geom::Point knot_get() const override;
2563 void knot_set(Geom::Point const &p, Geom::Point const &origin, unsigned int state) override;
2564};
2565
2567FlowtextKnotHolderEntity::knot_get() const
2568{
2569 SPRect const *rect = cast<SPRect>(item);
2570 g_assert(rect != nullptr);
2571
2572 return Geom::Point(rect->x.computed + rect->width.computed, rect->y.computed + rect->height.computed);
2573}
2574
2575void
2576FlowtextKnotHolderEntity::knot_set(Geom::Point const &p, Geom::Point const &origin, unsigned int state)
2577{
2578 set_internal(p, origin, state);
2579}
2580
2581FlowtextKnotHolder::FlowtextKnotHolder(SPDesktop *desktop, SPItem *item)
2583{
2584 g_assert(item != nullptr);
2585
2586 FlowtextKnotHolderEntity *entity_flowtext = new FlowtextKnotHolderEntity();
2587 entity_flowtext->create(desktop, item, this, Inkscape::CANVAS_ITEM_CTRL_TYPE_SHAPER, "FlowText:entity",
2588 _("Drag to resize the <b>flowed text frame</b>"));
2589 entity.push_back(entity_flowtext);
2590}
2591
2592/*
2593 Local Variables:
2594 mode:c++
2595 c-file-style:"stroustrup"
2596 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
2597 indent-tabs-mode:nil
2598 fill-column:99
2599 End:
2600*/
2601// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
Point origin
Definition aa.cpp:227
int margin
Definition canvas.cpp:166
Geom::IntRect bounds
Definition canvas.cpp:182
3x3 matrix representing an affine transformation.
Definition affine.h:70
void unionWith(CRect const &b)
Enlarge the rectangle to contain the argument.
CPoint midpoint() const
Get the point in the geometric center of the rectangle.
CPoint min() const
Get the corner of the rectangle with smallest coordinate values.
CPoint corner(unsigned i) const
Return the n-th corner of the rectangle.
Axis-aligned rectangle that can be empty.
Definition rect.h:203
Two-dimensional point that doubles as a vector.
Definition point.h:66
constexpr Coord y() const noexcept
Definition point.h:106
constexpr Coord x() const noexcept
Definition point.h:104
Axis aligned, non-empty rectangle.
Definition rect.h:92
static Rotate from_degrees(Coord deg)
Construct a rotation from its angle in degrees.
Definition transforms.h:218
Scaling from the origin.
Definition transforms.h:150
Translation by a vector.
Definition transforms.h:115
A thin wrapper around std::ostringstream, but writing floating point numbers in the format required b...
Preference storage class.
Definition preferences.h:66
static Preferences * get()
Access the singleton Preferences object.
int getInt(Glib::ustring const &pref_path, int def=0)
Retrieve an integer.
Interface for refcounted XML nodes.
Definition node.h:80
double getAttributeDouble(Util::const_char_ptr key, double default_value=0.0) const
Definition node.cpp:76
bool setAttributeSvgDouble(Util::const_char_ptr key, double val)
For attributes where an exponent is allowed.
Definition node.cpp:111
KnotHolderEntity definition.
virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, unsigned state)=0
virtual Geom::Point knot_get() const =0
virtual void knot_ungrabbed(Geom::Point const &p, Geom::Point const &origin, unsigned state)=0
virtual void knot_click(unsigned)
SPDesktop * desktop
Definition knot-holder.h:93
SPItem * item
Definition knot-holder.h:94
To do: update description of desktop.
Definition desktop.h:149
SPDocument * getDocument() const
Definition desktop.h:189
Typed SVG document implementation.
Definition document.h:101
const Geom::Affine & dt2doc() const
Desktop to document coordinate transformation.
Definition document.h:268
Geom::Point getPointAtAngle(double arg) const
Base class for visual SVG elements.
Definition sp-item.h:109
Geom::Affine i2dt_affine() const
Returns the transformation from item to desktop coords.
Definition sp-item.cpp:1837
Geom::OptRect desktopVisualBounds() const
Get item's visual bbox in desktop coordinate system.
Definition sp-item.cpp:1065
SPStyle * style
Represents the style properties, whether from presentation attributes, the style attribute,...
Definition sp-object.h:248
Inkscape::XML::Node * updateRepr(unsigned int flags=SP_OBJECT_WRITE_EXT)
Updates the object's repr based on the object's state.
void requestDisplayUpdate(unsigned int flags)
Queues an deferred update of this object's display.
SPOffset class.
Definition sp-offset.h:50
SVGLength rx
Definition sp-rect.h:80
SVGLength height
Definition sp-rect.h:79
SVGLength width
Definition sp-rect.h:78
SVGLength x
Definition sp-rect.h:76
SVGLength ry
Definition sp-rect.h:81
SVGLength y
Definition sp-rect.h:77
Base class for shapes, including <path> element.
Definition sp-shape.h:38
A spiral Shape.
Definition sp-spiral.h:39
float t0
Definition sp-spiral.h:50
float cx
Definition sp-spiral.h:45
float exp
Spiral expansion factor.
Definition sp-spiral.h:46
float cy
Definition sp-spiral.h:45
Geom::Point getXY(double t) const
Return one of the points on the spiral.
Geom::Point center
Definition sp-star.h:33
An SVG style object.
Definition style.h:45
SPPaintServer * getFillPaintServer()
Definition style.h:339
T< SPAttr::FILL, SPIPaint > fill
fill
Definition style.h:240
T< SPAttr::STROKE, SPIPaint > stroke
stroke
Definition style.h:247
SPPaintServer * getStrokePaintServer()
Definition style.h:343
T< SPAttr::DIRECTION, SPIEnum< SPCSSDirection > > direction
text direction (svg1.1)
Definition style.h:161
T< SPAttr::INLINE_SIZE, SPILength > inline_size
Definition style.h:183
T< SPAttr::TEXT_ANCHOR, SPIEnum< SPTextAnchor > > text_anchor
Anchor of the text (svg1.1 10.9.1)
Definition style.h:173
T< SPAttr::WRITING_MODE, SPIEnum< SPCSSWritingMode > > writing_mode
Writing mode (svg1.1 10.7.2, CSS Writing Modes 3)
Definition style.h:163
float computed
Definition svg-length.h:50
TODO: insert short description here.
Editable view implementation.
@ SP_ASPECT_NONE
Definition enums.h:42
constexpr Coord infinity()
Get a value representing infinity.
Definition coord.h:88
@ Y
Definition coord.h:48
@ X
Definition coord.h:48
T pow(T const &t, int n)
Integer exponentiation for transforms.
Definition transforms.h:98
auto floor(Geom::Rect const &rect)
Definition geom.h:131
SPItem * item
double offset
Perspective line for 3D perspectives.
SBasisN< n > cos(LinearN< n > bo, int k)
double atan2(Point const &p)
SBasis L2(D2< SBasis > const &a, unsigned k)
Definition d2-sbasis.cpp:42
Piecewise< SBasis > min(SBasis const &f, SBasis const &g)
Return the more negative of the two functions pointwise.
SBasisN< n > sin(LinearN< n > bo, int k)
Point abs(Point const &b)
std::unique_ptr< KnotHolder > create_LPE_knot_holder(SPItem *item, SPDesktop *desktop)
std::unique_ptr< KnotHolder > create_knot_holder(SPItem *item, SPDesktop *desktop, double edit_rotation, int edit_marker_mode)
Helper class to stream background task notifications as a series of messages.
@ CANVAS_ITEM_CTRL_TYPE_SIZER
@ CANVAS_ITEM_CTRL_TYPE_MARKER
@ CANVAS_ITEM_CTRL_TYPE_SHAPER
@ CANVAS_ITEM_CTRL_TYPE_ROTATE
int mode
int size
Singleton class to access the preferences file in a convenient way.
Ocnode ** ref
Definition quantize.cpp:32
GList * items
double getMarkerYScale(SPItem *item)
Geom::Rect getMarkerBounds(SPItem *item, SPDesktop *desktop)
double getMarkerXScale(SPItem *item)
static void sp_star_knot_click(SPItem *item, unsigned int state)
Geom::Affine getMarkerRotation(SPItem *item, double edit_rotation, int edit_marker_mode, bool reverse=false)
static void sp_rect_clamp_radii(SPRect *rect)
static gint sp_genericellipse_side(SPGenericEllipse *ellipse, Geom::Point const &p)
@ SP_GENERIC_ELLIPSE_ARC_TYPE_SLICE
Definition sp-ellipse.h:31
@ SP_GENERIC_ELLIPSE_ARC_TYPE_ARC
Definition sp-ellipse.h:32
TODO: insert short description here.
Some things pertinent to all visible shapes: SPItem, SPItemView, SPItemCtx.
@ SP_MARKER_LOC_START
@ MARKER_ORIENT_ANGLE
Definition sp-marker.h:36
@ MARKER_ORIENT_AUTO_START_REVERSE
Definition sp-marker.h:38
double sp_offset_distance_to_original(SPOffset *offset, Geom::Point px)
Distance to the original path; that function is called from shape-editor-knotholders to set the radiu...
void sp_offset_top_point(SPOffset const *offset, Geom::Point *px)
Computes a point on the offset; used to set a "seed" position for the control knot.
SVG <pattern> implementation.
Geom::Point sp_star_get_xy(SPStar const *star, SPStarPoint point, gint index, bool randomized)
sp_star_get_xy: Get X-Y value as item coordinate system @star: star item @point: point type to obtain...
Definition sp-star.cpp:604
@ SP_STAR_POINT_KNOT2
Definition sp-star.h:22
@ SP_STAR_POINT_KNOT1
Definition sp-star.h:21
TODO: insert short description here.
@ SP_CSS_TEXT_ANCHOR_MIDDLE
@ SP_CSS_TEXT_ANCHOR_START
@ SP_CSS_TEXT_ANCHOR_END
@ SP_CSS_WRITING_MODE_LR_TB
@ SP_CSS_WRITING_MODE_TB_LR
@ SP_CSS_WRITING_MODE_RL_TB
@ SP_CSS_DIRECTION_RTL
@ SP_CSS_DIRECTION_LTR
SPStyle - a style object for SPItem objects.
int delta
SPDesktop * desktop
double height
double width