Inkscape
Vector Graphics Editor
Loading...
Searching...
No Matches
knot-holder.cpp
Go to the documentation of this file.
1// SPDX-License-Identifier: GPL-2.0-or-later
2/*
3 * Container for SPKnot visual handles.
4 *
5 * Authors:
6 * Mitsuru Oka <oka326@parkcity.ne.jp>
7 * bulia byak <buliabyak@users.sf.net>
8 * Maximilian Albert <maximilian.albert@gmail.com>
9 * Abhishek Sharma
10 * Jon A. Cruz <jon@joncruz.org>
11 *
12 * Copyright (C) 2001-2008 authors
13 *
14 * Released under GNU GPL v2+, read the file 'COPYING' for more information.
15 */
16
17#include "knot-holder.h"
18
19#include <glibmm/i18n.h>
20
21#include "desktop.h"
23#include "document-undo.h"
24#include "knot-holder-entity.h"
25#include "knot.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"
36#include "style.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"
43
45
48 , item(item)
49 // XML Tree being used directly for item->getRepr() while it shouldn't be...
50 , repr(item ? item->getRepr() : nullptr)
51{
52 if (!desktop || !item) {
53 g_warning ("Error! Throw an exception, please!");
54 }
55
57}
58
63
64// this allow clear knothoolder before a destruction
65void
67{
68 for (auto & i : entity) {
69 delete i;
70 }
71 entity.clear(); // is this necessary?
72}
73
74void
76{
77 _edit_transform = edit_transform;
78}
79
81{
82 for (auto e = entity.begin(); e != entity.end(); ) {
83 // check if pattern was removed without deleting the knot
84 if ((*e)->knot_missing()) {
85 delete (*e);
86 e = entity.erase(e);
87 } else {
88 (*e)->update_knot();
89 ++e;
90 }
91 }
92}
93
98 for (auto i : entity) {
99 const SPKnot *knot = i->knot;
100
101 if (knot && knot->is_mouseover()) {
102 return true;
103 }
104 }
105
106 return false;
107}
108
113 for (auto i : entity) {
114 const SPKnot *knot = i->knot;
115
116 if (knot && knot->is_selected()) {
117 return true;
118 }
119 }
120 return false;
121}
122
123void
125{
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();
133 }
134 }
135 }
136 }
137 for(auto e : this->entity) {
138 if (!(state & GDK_SHIFT_MASK)) {
139 e->knot->selectKnot(false);
140 }
141 if (e->knot == knot) {
142 if (!(e->knot->is_selected()) || !(state & GDK_SHIFT_MASK)){
143 e->knot->selectKnot(true);
144 } else {
145 e->knot->selectKnot(false);
146 }
147 }
148 }
149}
150
151void
153{
154 SPItem *saved_item = this->item;
155
156 for(auto e : this->entity) {
157 if (e->knot == knot)
158 // no need to test whether knot_click exists since it's virtual now
159 e->knot_click(state);
160 }
161
162 {
163 auto savedShape = cast<SPShape>(saved_item);
164 if (savedShape) {
165 savedShape->set_shape();
166 }
167 }
168
169 this->update_knots();
170
171 Glib::ustring icon_name;
172
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");
186 } else {
187 auto offset = cast<SPOffset>(saved_item);
188 if (offset) {
189 if (offset->sourceHref) {
190 icon_name = INKSCAPE_ICON("path-offset-linked");
191 } else {
192 icon_name = INKSCAPE_ICON("path-offset-dynamic");
193 }
194 }
195 }
196
197 // for drag, this is done by ungrabbed_handler, but for click we must do it here
198
199 if (saved_item && saved_item->document) { // increasingly aggressive sanity checks
200 DocumentUndo::done(saved_item->document, _("Change handle"), icon_name);
201 } else {
202 std::terminate();
203 }
204}
205
206void
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);
213 }
214 }
215}
216
218{
219 for (auto e : entity) {
220 if (e->knot->is_selected()) {
221 e->knot->selectKnot(false);
222 }
223 }
224}
225
227void KnotHolder::knot_grabbed_handler(SPKnot *knot, unsigned state)
228{
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()) {
232 return;
233 }
234 auto const item_origin = (*grab_entity)->knot->drag_origin * item->dt2i_affine()
236 (*grab_entity)->knot_grabbed(item_origin, state);
237}
238
239void
241{
242 if (!dragging) {
243 // The knot has just been grabbed
244 knot_grabbed_handler(knot, state);
245 dragging = true;
246 }
247
248 // this was a local change and the knotholder does not need to be recreated:
249 this->local_change = TRUE;
250
251 for(auto e : this->entity) {
252 if (e->knot == knot) {
254 e->knot_set(q, e->knot->drag_origin * item->i2dt_affine().inverse() * _edit_transform.inverse(), state);
255 break;
256 }
257 }
258
259 auto shape = cast<SPShape>(item);
260 if (shape) {
261 shape->set_shape();
262 }
263
264 this->update_knots();
265}
266
267void
269{
270 dragging = false;
272
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);
277 } else {
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(),
282 state);
283 if (e->knot->is_lpe) {
284 return;
285 }
286 break;
287 }
288 }
289 }
290
291 SPObject *object = item;
292
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
296 // (such as object).
297 object->updateRepr();
298
299 SPFilter *filter = (object->style) ? object->style->getFilter() : nullptr;
300 if (filter) {
301 filter->updateRepr();
302 }
303 Glib::ustring icon_name;
304
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");
320 }
321 DocumentUndo::done(object->document, _("Move handle"), icon_name);
322}
323
325{
326 // g_message("Adding a knot at %p", e);
327 entity.push_back(e);
328}
329
331{
332 std::size_t counter = -1;
333 for (auto & i : entity) {
334 ++ counter;
335 if (e == i) {
336 entity.remove_if([i=&i](auto& x){return &x==i;});
337 delete i;
338 break;
339 }
340 }
341 entity.clear(); // is this necessary?
342}
343
345{
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"));
353
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>"));
356
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"));
359
360 entity.push_back(entity_xy);
361 entity.push_back(entity_angle);
362 entity.push_back(entity_scale);
363 }
364
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"));
372
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>"));
375
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"));
378
379 entity.push_back(entity_xy);
380 entity.push_back(entity_angle);
381 entity.push_back(entity_scale);
382 }
383
384 // watch patterns and update knots when they change
386}
387
389{
390 if ((item->style->fill.isPaintserver()) && cast<SPHatch>(item->style->getFillPaintServer())) {
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"));
397
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>"));
400
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"));
403
404 entity.push_back(entity_xy);
405 entity.push_back(entity_angle);
406 entity.push_back(entity_scale);
407 }
408
409 if ((item->style->stroke.isPaintserver()) && cast<SPHatch>(item->style->getStrokePaintServer())) {
410 HatchKnotHolderEntityXY *entity_xy = new HatchKnotHolderEntityXY(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"));
416
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>"));
419
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"));
422
423 entity.push_back(entity_xy);
424 entity.push_back(entity_angle);
425 entity.push_back(entity_scale);
426 }
427}
428
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);
440 }
441 }
442
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);
452}
453
459{
460 bool ret = false;
461 for (auto i : entity) {
462 ret = i->set_item_clickpos(loc) || ret;
463 }
464 return ret;
465}
466
472 g_assert(item);
473
474 if (auto pattern = cast<SPPattern>(item->style->getFillPaintServer())) {
475 _watch_fill = pattern->connectModified([this] (SPObject *, unsigned) {
476 update_knots();
477 });
478 }
479 else {
480 _watch_fill.disconnect();
481 }
482
483 if (auto pattern = cast<SPPattern>(item->style->getStrokePaintServer())) {
484 _watch_stroke = pattern->connectModified([this] (SPObject *, unsigned) {
485 update_knots();
486 });
487 }
488 else {
489 _watch_stroke.disconnect();
490 }
491}
492
493/*
494 Local Variables:
495 mode:c++
496 c-file-style:"stroustrup"
497 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
498 indent-tabs-mode:nil
499 fill-column:99
500 End:
501*/
502// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
3x3 matrix representing an affine transformation.
Definition affine.h:70
Affine inverse() const
Compute the inverse matrix.
Definition affine.cpp:388
Two-dimensional point that doubles as a vector.
Definition point.h:66
void remove_snaptarget(bool only_if_presnap=false)
std::unique_ptr< KnotHolder > knotholder
KnotHolderEntity definition.
void create(SPDesktop *desktop, SPItem *item, KnotHolder *parent, Inkscape::CanvasItemCtrlType type=Inkscape::CANVAS_ITEM_CTRL_TYPE_DEFAULT, Glib::ustring const &name="unknown", char const *tip="", uint32_t color=0xffffff00)
SPDesktop * desktop
Definition knot-holder.h:93
void add_filter_knotholder()
bool knot_selected() const
Returns true if at least one of the KnotHolderEntities is selected.
void unselect_knots()
void add_hatch_knotholder()
void knot_mousedown_handler(SPKnot *knot, unsigned int state)
virtual ~KnotHolder()
bool set_item_clickpos(Geom::Point loc)
When editing an object, this extra information tells out knots where the user has clicked on the item...
void transform_selected(Geom::Affine transform)
SPItem * item
Definition knot-holder.h:94
KnotHolder()=delete
Geom::Affine _edit_transform
void knot_moved_handler(SPKnot *knot, Geom::Point const &p, unsigned int state)
void add_pattern_knotholder()
bool knot_mouseover() const
Returns true if at least one of the KnotHolderEntities has the mouse hovering above it.
void update_knots()
void remove(KnotHolderEntity *e)
bool local_change
if true, no need to recreate knotholder if repr was changed.
Definition knot-holder.h:97
bool dragging
Definition knot-holder.h:99
void knot_grabbed_handler(SPKnot *knot, unsigned state)
Notifies an entity that its knot has just been grabbed.
sigc::scoped_connection _watch_fill
void setEditTransform(Geom::Affine edit_transform)
void install_modification_watch()
When object being edited has some attributes changed (fill, stroke) update what objects we watch.
void knot_ungrabbed_handler(SPKnot *knot, unsigned int state)
void knot_clicked_handler(SPKnot *knot, unsigned int state)
sigc::scoped_connection _watch_stroke
std::list< KnotHolderEntity * > entity
Definition knot-holder.h:85
void add(KnotHolderEntity *e)
To do: update description of desktop.
Definition desktop.h:149
Inkscape::Display::SnapIndicator * getSnapIndicator() const
Definition desktop.h:193
Inkscape::UI::Tools::ToolBase * getTool() const
Definition desktop.h:187
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:1821
Geom::Affine dt2i_affine() const
Returns the transformation from desktop to item coords.
Definition sp-item.cpp:1841
Desktop-bound visual control object.
Definition knot.h:51
Geom::Point pos
Our desktop coordinates.
Definition knot.h:69
bool is_selected() const
Definition knot.h:168
bool is_mouseover() const
Definition knot.h:169
void selectKnot(bool select)
Select knot.
Definition knot.cpp:103
SPObject is an abstract base class of all of the document nodes at the SVG document level.
Definition sp-object.h:160
SPDocument * document
Definition sp-object.h:188
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.
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
SPFilter * getFilter()
Definition style.h:335
SPPaintServer * getStrokePaintServer()
Definition style.h:343
Editable view implementation.
TODO: insert short description here.
@ Y
Definition coord.h:48
@ X
Definition coord.h:48
Macro for icon names used in Inkscape.
SPItem * item
Declarations for SPKnot: Desktop-bound visual control object.
double offset
@ CANVAS_ITEM_CTRL_TYPE_SIZER
@ CANVAS_ITEM_CTRL_TYPE_MARKER
@ CANVAS_ITEM_CTRL_TYPE_ROTATE
New node tool with support for multiple path editing.
static gint counter
Definition box3d.cpp:39
Inkscape::ShapeEditor This is a container class which contains a knotholder for shapes.
Provides a class that shows a temporary indicator on the canvas of where the snap was,...
SVG <hatch> implementation.
SPObject * sp_object_unref(SPObject *object, SPObject *owner)
Decrease reference count of object, with possible debugging and finalization.
SPObject * sp_object_ref(SPObject *object, SPObject *owner)
Increase reference count of object, with possible debugging.
SVG <pattern> implementation.
Spiral drawing context.
SPStyle - a style object for SPItem objects.
SPDesktop * desktop