1 // SPDX-License-Identifier: GPL-2.0-or-later
3 * Container for SPKnot visual handles.
6 * Mitsuru Oka <oka326@parkcity.ne.jp>
7 * bulia byak <buliabyak@users.sf.net>
8 * Maximilian Albert <maximilian.albert@gmail.com>
10 * Jon A. Cruz <jon@joncruz.org>
12 * Copyright (C) 2001-2008 authors
14 * Released under GNU GPL v2+, read the file 'COPYING' for more information.
17 #include "knot-holder.h"
19 #include <glibmm/i18n.h>
22 #include "display/control/snap-indicator.h"
23 #include "document-undo.h"
24 #include "knot-holder-entity.h"
26 #include "object/box3d.h"
27 #include "object/sp-ellipse.h"
28 #include "object/sp-hatch.h"
29 #include "object/sp-marker.h"
30 #include "object/sp-offset.h"
31 #include "object/sp-pattern.h"
32 #include "object/sp-rect.h"
33 #include "object/sp-shape.h"
34 #include "object/sp-spiral.h"
35 #include "object/sp-star.h"
37 #include "ui/icon-names.h"
38 #include "ui/shape-editor.h"
39 #include "ui/tools/arc-tool.h"
40 #include "ui/tools/node-tool.h"
41 #include "ui/tools/rect-tool.h"
42 #include "ui/tools/spiral-tool.h"
44 using Inkscape::DocumentUndo
;
46 KnotHolder::KnotHolder(SPDesktop
*desktop
, SPItem
*item
)
49 // XML Tree being used directly for item->getRepr() while it shouldn't be...
50 , repr(item
? item
->getRepr() : nullptr)
52 if (!desktop
|| !item
) {
53 g_warning ("Error! Throw an exception, please!");
59 KnotHolder::~KnotHolder() {
60 sp_object_unref(item
);
64 // this allow clear knothoolder before a destruction
68 for (auto & i
: entity
) {
71 entity
.clear(); // is this necessary?
75 KnotHolder::setEditTransform(Geom::Affine edit_transform
)
77 _edit_transform
= edit_transform
;
80 void KnotHolder::update_knots()
82 for (auto e
= entity
.begin(); e
!= entity
.end(); ) {
83 // check if pattern was removed without deleting the knot
84 if ((*e
)->knot_missing()) {
95 * Returns true if at least one of the KnotHolderEntities has the mouse hovering above it.
97 bool KnotHolder::knot_mouseover() const {
98 for (auto i
: entity
) {
99 const SPKnot
*knot
= i
->knot
;
101 if (knot
&& knot
->is_mouseover()) {
110 * Returns true if at least one of the KnotHolderEntities is selected
112 bool KnotHolder::knot_selected() const {
113 for (auto i
: entity
) {
114 const SPKnot
*knot
= i
->knot
;
116 if (knot
&& knot
->is_selected()) {
124 KnotHolder::knot_mousedown_handler(SPKnot
*knot
, guint state
)
126 if (!(state
& GDK_SHIFT_MASK
)) {
127 // TODO: handle this event in the Node Tool
128 if (auto *nt
= dynamic_cast<Inkscape::UI::Tools::NodeTool
*>(desktop
->getTool())) {
129 for (auto &_shape_editor
: nt
->_shape_editors
) {
130 Inkscape::UI::ShapeEditor
*shape_editor
= _shape_editor
.second
.get();
131 if (shape_editor
&& shape_editor
->knotholder
) {
132 shape_editor
->knotholder
->unselect_knots();
137 for(auto e
: this->entity
) {
138 if (!(state
& GDK_SHIFT_MASK
)) {
139 e
->knot
->selectKnot(false);
141 if (e
->knot
== knot
) {
142 if (!(e
->knot
->is_selected()) || !(state
& GDK_SHIFT_MASK
)){
143 e
->knot
->selectKnot(true);
145 e
->knot
->selectKnot(false);
152 KnotHolder::knot_clicked_handler(SPKnot
*knot
, guint state
)
154 SPItem
*saved_item
= this->item
;
156 for(auto e
: this->entity
) {
158 // no need to test whether knot_click exists since it's virtual now
159 e
->knot_click(state
);
163 auto savedShape
= cast
<SPShape
>(saved_item
);
165 savedShape
->set_shape();
169 this->update_knots();
171 Glib::ustring icon_name
;
173 // TODO extract duplicated blocks;
174 if (is
<SPRect
>(saved_item
)) {
175 icon_name
= INKSCAPE_ICON("draw-rectangle");
176 } else if (is
<SPBox3D
>(saved_item
)) {
177 icon_name
= INKSCAPE_ICON("draw-cuboid");
178 } else if (is
<SPGenericEllipse
>(saved_item
)) {
179 icon_name
= INKSCAPE_ICON("draw-ellipse");
180 } else if (is
<SPStar
>(saved_item
)) {
181 icon_name
= INKSCAPE_ICON("draw-polygon-star");
182 } else if (is
<SPSpiral
>(saved_item
)) {
183 icon_name
= INKSCAPE_ICON("draw-spiral");
184 } else if (is
<SPMarker
>(saved_item
)) {
185 icon_name
= INKSCAPE_ICON("tool-pointer");
187 auto offset
= cast
<SPOffset
>(saved_item
);
189 if (offset
->sourceHref
) {
190 icon_name
= INKSCAPE_ICON("path-offset-linked");
192 icon_name
= INKSCAPE_ICON("path-offset-dynamic");
197 // for drag, this is done by ungrabbed_handler, but for click we must do it here
199 if (saved_item
&& saved_item
->document
) { // increasingly aggressive sanity checks
200 DocumentUndo::done(saved_item
->document
, _("Change handle"), icon_name
);
207 KnotHolder::transform_selected(Geom::Affine transform
){
208 for (auto & i
: entity
) {
209 SPKnot
*knot
= i
->knot
;
210 if (knot
->is_selected()) {
211 knot_moved_handler(knot
, knot
->pos
* transform
, 0);
212 knot
->selectKnot(true);
217 void KnotHolder::unselect_knots()
219 for (auto e
: entity
) {
220 if (e
->knot
->is_selected()) {
221 e
->knot
->selectKnot(false);
226 /** Notifies an entity that its knot has just been grabbed. */
227 void KnotHolder::knot_grabbed_handler(SPKnot
*knot
, unsigned state
)
229 auto grab_entity
= std::find_if(entity
.begin(), entity
.end(),
230 [=](KnotHolderEntity
*khe
) -> bool { return khe
->knot
== knot
; });
231 if (grab_entity
== entity
.end()) {
234 auto const item_origin
= (*grab_entity
)->knot
->drag_origin
* item
->dt2i_affine()
235 * _edit_transform
.inverse();
236 (*grab_entity
)->knot_grabbed(item_origin
, state
);
240 KnotHolder::knot_moved_handler(SPKnot
*knot
, Geom::Point
const &p
, guint state
)
243 // The knot has just been grabbed
244 knot_grabbed_handler(knot
, state
);
248 // this was a local change and the knotholder does not need to be recreated:
249 this->local_change
= TRUE
;
251 for(auto e
: this->entity
) {
252 if (e
->knot
== knot
) {
253 Geom::Point
const q
= p
* item
->i2dt_affine().inverse() * _edit_transform
.inverse();
254 e
->knot_set(q
, e
->knot
->drag_origin
* item
->i2dt_affine().inverse() * _edit_transform
.inverse(), state
);
259 auto shape
= cast
<SPShape
>(item
);
264 this->update_knots();
268 KnotHolder::knot_ungrabbed_handler(SPKnot
*knot
, guint state
)
271 desktop
->getSnapIndicator()->remove_snaptarget();
273 // if a point is dragged while not selected, it should select itself,
274 // even if it was just unselected in the mousedown event handler.
275 if (!knot
->is_selected()) {
276 knot
->selectKnot(true);
278 for (auto e
: entity
) {
279 if (e
->knot
== knot
) {
280 e
->knot_ungrabbed(e
->knot
->position(),
281 e
->knot
->drag_origin
* item
->i2dt_affine().inverse() * _edit_transform
.inverse(),
283 if (e
->knot
->is_lpe
) {
291 SPObject
*object
= item
;
293 // Caution: this call involves a screen update, which may process events, and as a
294 // result the knotholder may be destructed. So, after the updateRepr, we cannot use any
295 // fields of this knotholder (such as this->item), but only values we have saved beforehand
297 object
->updateRepr();
299 SPFilter
*filter
= (object
->style
) ? object
->style
->getFilter() : nullptr;
301 filter
->updateRepr();
303 Glib::ustring icon_name
;
305 // TODO extract duplicated blocks;
306 if (is
<SPRect
>(object
)) {
307 icon_name
= INKSCAPE_ICON("draw-rectangle");
308 } else if (is
<SPBox3D
>(object
)) {
309 icon_name
= INKSCAPE_ICON("draw-cuboid");
310 } else if (is
<SPGenericEllipse
>(object
)) {
311 icon_name
= INKSCAPE_ICON("draw-ellipse");
312 } else if (is
<SPStar
>(object
)) {
313 icon_name
= INKSCAPE_ICON("draw-polygon-star");
314 } else if (is
<SPSpiral
>(object
)) {
315 icon_name
= INKSCAPE_ICON("draw-spiral");
316 } else if (is
<SPMarker
>(object
)) {
317 icon_name
= INKSCAPE_ICON("tool-pointer");
318 } else if (auto offset
= cast
<SPOffset
>(object
)) {
319 icon_name
= offset
->sourceHref
? INKSCAPE_ICON("path-offset-linked") : INKSCAPE_ICON("path-offset-dynamic");
321 DocumentUndo::done(object
->document
, _("Move handle"), icon_name
);
324 void KnotHolder::add(KnotHolderEntity
*e
)
326 // g_message("Adding a knot at %p", e);
330 void KnotHolder::remove(KnotHolderEntity
*e
)
332 std::size_t counter
= -1;
333 for (auto & i
: entity
) {
336 entity
.remove_if([i
=&i
](auto& x
){return &x
==i
;});
341 entity
.clear(); // is this necessary?
344 void KnotHolder::add_pattern_knotholder()
346 if (is
<SPPattern
>(item
->style
->getFillPaintServer())) {
347 auto entity_xy
= new PatternKnotHolderEntityXY(true);
348 auto entity_angle
= new PatternKnotHolderEntityAngle(true);
349 auto entity_scale
= new PatternKnotHolderEntityScale(true);
350 entity_xy
->create(desktop
, item
, this, Inkscape::CANVAS_ITEM_CTRL_TYPE_SIZER
, "Pattern:Fill:xy",
351 // TRANSLATORS: This refers to the pattern that's inside the object
352 _("<b>Move</b> the pattern fill inside the object"));
354 entity_scale
->create(desktop
, item
, this, Inkscape::CANVAS_ITEM_CTRL_TYPE_SIZER
, "Pattern:Fill:scale",
355 _("<b>Scale</b> the pattern fill; uniformly if with <b>Ctrl</b>"));
357 entity_angle
->create(desktop
, item
, this, Inkscape::CANVAS_ITEM_CTRL_TYPE_ROTATE
, "Pattern:Fill:angle",
358 _("<b>Rotate</b> the pattern fill; with <b>Ctrl</b> to snap angle"));
360 entity
.push_back(entity_xy
);
361 entity
.push_back(entity_angle
);
362 entity
.push_back(entity_scale
);
365 if (is
<SPPattern
>(item
->style
->getStrokePaintServer())) {
366 auto entity_xy
= new PatternKnotHolderEntityXY(false);
367 auto entity_angle
= new PatternKnotHolderEntityAngle(false);
368 auto entity_scale
= new PatternKnotHolderEntityScale(false);
369 entity_xy
->create(desktop
, item
, this, Inkscape::CANVAS_ITEM_CTRL_TYPE_MARKER
, "Pattern:Stroke:xy",
370 // TRANSLATORS: This refers to the pattern that's inside the object
371 _("<b>Move</b> the stroke's pattern inside the object"));
373 entity_scale
->create(desktop
, item
, this, Inkscape::CANVAS_ITEM_CTRL_TYPE_SIZER
, "Pattern:Stroke:scale",
374 _("<b>Scale</b> the stroke's pattern; uniformly if with <b>Ctrl</b>"));
376 entity_angle
->create(desktop
, item
, this, Inkscape::CANVAS_ITEM_CTRL_TYPE_ROTATE
, "Pattern:Stroke:angle",
377 _("<b>Rotate</b> the stroke's pattern; with <b>Ctrl</b> to snap angle"));
379 entity
.push_back(entity_xy
);
380 entity
.push_back(entity_angle
);
381 entity
.push_back(entity_scale
);
384 // watch patterns and update knots when they change
385 install_modification_watch();
388 void KnotHolder::add_hatch_knotholder()
390 if ((item
->style
->fill
.isPaintserver()) && cast
<SPHatch
>(item
->style
->getFillPaintServer())) {
391 HatchKnotHolderEntityXY
*entity_xy
= new HatchKnotHolderEntityXY(true);
392 HatchKnotHolderEntityAngle
*entity_angle
= new HatchKnotHolderEntityAngle(true);
393 HatchKnotHolderEntityScale
*entity_scale
= new HatchKnotHolderEntityScale(true);
394 entity_xy
->create(desktop
, item
, this, Inkscape::CANVAS_ITEM_CTRL_TYPE_MARKER
, "Hatch:Fill:xy",
395 // TRANSLATORS: This refers to the hatch that's inside the object
396 _("<b>Move</b> the hatch fill inside the object"));
398 entity_scale
->create(desktop
, item
, this, Inkscape::CANVAS_ITEM_CTRL_TYPE_SIZER
, "Hatch:Fill:scale",
399 _("<b>Scale</b> the hatch fill; uniformly if with <b>Ctrl</b>"));
401 entity_angle
->create(desktop
, item
, this, Inkscape::CANVAS_ITEM_CTRL_TYPE_ROTATE
, "Hatch:Fill:angle",
402 _("<b>Rotate</b> the hatch fill; with <b>Ctrl</b> to snap angle"));
404 entity
.push_back(entity_xy
);
405 entity
.push_back(entity_angle
);
406 entity
.push_back(entity_scale
);
409 if ((item
->style
->stroke
.isPaintserver()) && cast
<SPHatch
>(item
->style
->getStrokePaintServer())) {
410 HatchKnotHolderEntityXY
*entity_xy
= new HatchKnotHolderEntityXY(false);
411 HatchKnotHolderEntityAngle
*entity_angle
= new HatchKnotHolderEntityAngle(false);
412 HatchKnotHolderEntityScale
*entity_scale
= new HatchKnotHolderEntityScale(false);
413 entity_xy
->create(desktop
, item
, this, Inkscape::CANVAS_ITEM_CTRL_TYPE_MARKER
, "Hatch:Stroke:xy",
414 // TRANSLATORS: This refers to the pattern that's inside the object
415 _("<b>Move</b> the hatch stroke inside the object"));
417 entity_scale
->create(desktop
, item
, this, Inkscape::CANVAS_ITEM_CTRL_TYPE_SIZER
, "Hatch:Stroke:scale",
418 _("<b>Scale</b> the hatch stroke; uniformly if with <b>Ctrl</b>"));
420 entity_angle
->create(desktop
, item
, this, Inkscape::CANVAS_ITEM_CTRL_TYPE_ROTATE
, "Hatch:Stroke:angle",
421 _("<b>Rotate</b> the hatch stroke; with <b>Ctrl</b> to snap angle"));
423 entity
.push_back(entity_xy
);
424 entity
.push_back(entity_angle
);
425 entity
.push_back(entity_scale
);
429 void KnotHolder::add_filter_knotholder() {
430 if (auto filter
= item
->style
->getFilter()) {
431 if (!filter
->auto_region
) {
432 auto entity_tl
= new FilterKnotHolderEntity(true);
433 auto entity_br
= new FilterKnotHolderEntity(false);
434 entity_tl
->create(desktop
, item
, this, Inkscape::CANVAS_ITEM_CTRL_TYPE_MARKER
, "Filter:TopLeft",
435 _("<b>Resize</b> the filter effect region"));
436 entity_br
->create(desktop
, item
, this, Inkscape::CANVAS_ITEM_CTRL_TYPE_MARKER
, "Filter:BottomRight",
437 _("<b>Resize</b> the filter effect region"));
438 entity
.push_back(entity_tl
);
439 entity
.push_back(entity_br
);
443 // always install blur nodes, they default to disabled.
444 auto entity_x
= new BlurKnotHolderEntity(Geom::X
);
445 auto entity_y
= new BlurKnotHolderEntity(Geom::Y
);
446 entity_x
->create(desktop
, item
, this, Inkscape::CANVAS_ITEM_CTRL_TYPE_ROTATE
, "Filter:BlurX",
447 _("<b>Drag</b> to <b>adjust</b> blur in x direction; <b>Ctrl</b>+<b>Drag</b> makes x equal to y; <b>Shift</b>+<b>Ctrl</b>+<b>Drag</b> scales blur proportionately "));
448 entity_y
->create(desktop
, item
, this, Inkscape::CANVAS_ITEM_CTRL_TYPE_ROTATE
, "Filter:BlurY",
449 _("<b>Drag</b> to <b>adjust</b> blur in y direction; <b>Ctrl</b>+<b>Drag</b> makes y equal to x; <b>Shift</b>+<b>Ctrl</b>+<b>Drag</b> scales blur proportionately "));
450 entity
.push_back(entity_x
);
451 entity
.push_back(entity_y
);
455 * When editing an object, this extra information tells out knots
456 * where the user has clicked on the item.
458 bool KnotHolder::set_item_clickpos(Geom::Point loc
)
461 for (auto i
: entity
) {
462 ret
= i
->set_item_clickpos(loc
) || ret
;
468 * When object being edited has some attributes changed (fill, stroke)
469 * update what objects we watch
471 void KnotHolder::install_modification_watch() {
474 if (auto pattern
= cast
<SPPattern
>(item
->style
->getFillPaintServer())) {
475 _watch_fill
= pattern
->connectModified([this] (SPObject
*, unsigned) {
480 _watch_fill
.disconnect();
483 if (auto pattern
= cast
<SPPattern
>(item
->style
->getStrokePaintServer())) {
484 _watch_stroke
= pattern
->connectModified([this] (SPObject
*, unsigned) {
489 _watch_stroke
.disconnect();
496 c-file-style:"stroustrup"
497 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
502 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :