Context for the "About" label
[inkscape.git] / src / ui / knot / knot-holder.cpp
blob6d8043c96b4557c3b9f85f36d180b160d38aca9a
1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3 * Container for SPKnot visual handles.
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>
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>
21 #include "desktop.h"
22 #include "display/control/snap-indicator.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"
42 #include "ui/tools/spiral-tool.h"
44 using Inkscape::DocumentUndo;
46 KnotHolder::KnotHolder(SPDesktop *desktop, SPItem *item)
47 : desktop(desktop)
48 , item(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!");
56 sp_object_ref(item);
59 KnotHolder::~KnotHolder() {
60 sp_object_unref(item);
61 clear();
64 // this allow clear knothoolder before a destruction
65 void
66 KnotHolder::clear()
68 for (auto & i : entity) {
69 delete i;
71 entity.clear(); // is this necessary?
74 void
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()) {
85 delete (*e);
86 e = entity.erase(e);
87 } else {
88 (*e)->update_knot();
89 ++e;
94 /**
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()) {
102 return true;
106 return false;
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()) {
117 return true;
120 return false;
123 void
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);
144 } else {
145 e->knot->selectKnot(false);
151 void
152 KnotHolder::knot_clicked_handler(SPKnot *knot, guint state)
154 SPItem *saved_item = this->item;
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);
163 auto savedShape = cast<SPShape>(saved_item);
164 if (savedShape) {
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");
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");
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);
201 } else {
202 std::terminate();
206 void
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()) {
232 return;
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);
239 void
240 KnotHolder::knot_moved_handler(SPKnot *knot, Geom::Point const &p, guint state)
242 if (!dragging) {
243 // The knot has just been grabbed
244 knot_grabbed_handler(knot, state);
245 dragging = true;
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);
255 break;
259 auto shape = cast<SPShape>(item);
260 if (shape) {
261 shape->set_shape();
264 this->update_knots();
267 void
268 KnotHolder::knot_ungrabbed_handler(SPKnot *knot, guint state)
270 dragging = false;
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);
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;
286 break;
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
296 // (such as object).
297 object->updateRepr();
299 SPFilter *filter = (object->style) ? object->style->getFilter() : nullptr;
300 if (filter) {
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);
327 entity.push_back(e);
330 void KnotHolder::remove(KnotHolderEntity *e)
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;
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)
460 bool ret = false;
461 for (auto i : entity) {
462 ret = i->set_item_clickpos(loc) || ret;
464 return ret;
468 * When object being edited has some attributes changed (fill, stroke)
469 * update what objects we watch
471 void KnotHolder::install_modification_watch() {
472 g_assert(item);
474 if (auto pattern = cast<SPPattern>(item->style->getFillPaintServer())) {
475 _watch_fill = pattern->connectModified([this] (SPObject *, unsigned) {
476 update_knots();
479 else {
480 _watch_fill.disconnect();
483 if (auto pattern = cast<SPPattern>(item->style->getStrokePaintServer())) {
484 _watch_stroke = pattern->connectModified([this] (SPObject *, unsigned) {
485 update_knots();
488 else {
489 _watch_stroke.disconnect();
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:
502 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :