Inkscape
Vector Graphics Editor
Loading...
Searching...
No Matches
path.cpp
Go to the documentation of this file.
1// SPDX-License-Identifier: GPL-2.0-or-later
2/*
3 * Copyright (C) Johan Engelen 2007 <j.b.c.engelen@utwente.nl>
4 * Abhishek Sharma
5 *
6 * Released under GNU GPL v2+, read the file 'COPYING' for more information.
7 */
8
9#include "path.h"
10
11#include <2geom/d2.h>
12#include <2geom/pathvector.h>
15#include <glibmm/i18n.h>
16#include <glibmm/utility.h>
17#include <gtkmm/button.h>
18#include <gtkmm/label.h>
19#include <gtkmm/image.h>
20
21#include "bad-uri-exception.h"
22#include "desktop.h"
23#include "document.h"
24#include "document-undo.h"
25#include "inkscape.h"
26#include "selection.h"
27
29#include "display/curve.h"
30#include "live_effects/effect.h"
32#include "object/sp-item.h"
33#include "object/sp-shape.h"
34#include "object/sp-text.h"
35#include "object/uri.h"
36#include "svg/svg.h"
37#include "ui/clipboard.h" // clipboard support
38#include "ui/icon-loader.h"
39#include "ui/pack.h"
42#include "ui/tools/node-tool.h"
43
44namespace Inkscape {
45namespace LivePathEffect {
46
47PathParam::PathParam( const Glib::ustring& label, const Glib::ustring& tip,
48 const Glib::ustring& key, Inkscape::UI::Widget::Registry* wr,
49 Effect* effect, const gchar * default_value)
50 : Parameter(label, tip, key, wr, effect),
51 changed(true),
52 _pathvector(),
53 _pwd2(),
54 must_recalculate_pwd2(false),
55 href(nullptr),
56 ref( (SPObject*)effect->getLPEObj() )
57{
58 defvalue = g_strdup(default_value);
60 oncanvas_editable = true;
61 _from_original_d = false;
62 _edit_button = true;
63 _copy_button = true;
64 _paste_button = true;
65 _link_button = true;
66 ref_changed_connection = ref.changedSignal().connect(sigc::mem_fun(*this, &PathParam::ref_changed));
67}
68
70 unlink();
72 g_free(defvalue);
73}
74
76 setUpdating(false);
79 SPItem *item = nullptr;
80 if ((item = getItem())) {
81 item->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
82 }
83}
84
88 if (auto item = getItem()) {
89 std::vector<SPLPEItem *> lpeitems = param_effect->getCurrrentLPEItems();
90 if (lpeitems.size() == 1) {
91 param_effect->sp_lpe_item = lpeitems[0];
92 }
94 }
95 return affine;
96}
97
98Geom::PathVector const &
100{
101 return _pathvector;
102}
103
106{
107 ensure_pwd2();
108 return _pwd2;
109}
110
111void
116
117void
122
123std::vector<SPObject *> PathParam::param_get_satellites()
124{
125
126 std::vector<SPObject *> objs;
127 if (ref.isAttached()) {
128 // we reload connexions in case are lost for example item recreation on ungroup
130 write_to_SVG();
131 }
132
133 SPObject * linked_obj = ref.getObject();
134 if (linked_obj) {
135 objs.push_back(linked_obj);
136 }
137 }
138 return objs;
139}
140
141bool
142PathParam::param_readSVGValue(const gchar * strvalue)
143{
144 if (strvalue) {
146 unlink();
148
149
150 if (strvalue[0] == '#') {
151 bool write = false;
152 SPObject * old_ref = param_effect->getSPDoc()->getObjectByHref(strvalue);
153 Glib::ustring id_tmp;
154 if (old_ref) {
155 SPObject * tmpsuccessor = old_ref->_tmpsuccessor;
156 // study add setListener() in LPE that generate items from 0
157 if (tmpsuccessor && tmpsuccessor->getId()) {
158 id_tmp = tmpsuccessor->getId();
159 id_tmp.insert(id_tmp.begin(), '#');
160 write = true;
161 }
162 }
163 if (href)
164 g_free(href);
165 href = g_strdup(id_tmp.empty() ? strvalue : id_tmp.c_str());
166
167 // Now do the attaching, which emits the changed signal.
168 try {
170 //lp:1299948
171 SPItem* i = ref.getObject();
172 if (i) {
173 linked_modified_callback(i, SP_OBJECT_MODIFIED_FLAG);
174 } // else: document still processing new events. Repr of the linked object not created yet.
175 } catch (Inkscape::BadURIException &e) {
176 g_warning("%s", e.what());
177 ref.detach();
179 }
180 if (write) {
181 auto full = param_getSVGValue();
182 param_write_to_repr(full.c_str());
183 }
184 } else {
185 _pathvector = sp_svg_read_pathv(strvalue);
186 }
187
188 emit_changed();
189 return true;
190 }
191
192 return false;
193}
194
195Glib::ustring
197{
198 if (href) {
199 return href;
200 } else {
202 }
203}
204
205Glib::ustring
207{
208 return defvalue;
209}
210
211void
212PathParam::set_buttons(bool edit_button, bool copy_button, bool paste_button, bool link_button)
213{
214 _edit_button = edit_button;
215 _copy_button = copy_button;
216 _paste_button = paste_button;
217 _link_button = link_button;
218}
219
220Gtk::Widget *
222{
223 auto const _widget = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::HORIZONTAL);
224
225 auto const pLabel = Gtk::make_managed<Gtk::Label>(param_label);
226 UI::pack_start(*_widget, *pLabel, true, true);
227 pLabel->set_tooltip_text(param_tooltip);
228 Gtk::Image * pIcon = nullptr;
229 Gtk::Button * pButton = nullptr;
230 if (_edit_button) {
231 pIcon = Gtk::manage(sp_get_icon_image("tool-node-editor", Gtk::IconSize::NORMAL));
232 pButton = Gtk::make_managed<Gtk::Button>();
233 pButton->set_has_frame(false);
234 pButton->set_child(*pIcon);
235 pButton->signal_clicked().connect(sigc::mem_fun(*this, &PathParam::on_edit_button_click));
236 UI::pack_start(*_widget, *pButton, true, true);
237 pButton->set_tooltip_text(_("Edit on-canvas"));
238 }
239
240 if (_copy_button) {
241 pIcon = Gtk::manage(sp_get_icon_image("edit-copy", Gtk::IconSize::NORMAL));
242 pButton = Gtk::make_managed<Gtk::Button>();
243 pButton->set_has_frame(false);
244 pButton->set_child(*pIcon);
245
246 pButton->signal_clicked().connect(sigc::mem_fun(*this, &PathParam::on_copy_button_click));
247 UI::pack_start(*_widget, *pButton, true, true);
248 pButton->set_tooltip_text(_("Copy path"));
249 }
250
251 if (_paste_button) {
252 pIcon = Gtk::manage(sp_get_icon_image("edit-paste", Gtk::IconSize::NORMAL));
253 pButton = Gtk::make_managed<Gtk::Button>();
254 pButton->set_has_frame(false);
255 pButton->set_child(*pIcon);
256 pButton->signal_clicked().connect(sigc::mem_fun(*this, &PathParam::on_paste_button_click));
257 UI::pack_start(*_widget, *pButton, true, true);
258 pButton->set_tooltip_text(_("Paste path"));
259 }
260 if (_link_button) {
261 pIcon = Gtk::manage(sp_get_icon_image("edit-clone", Gtk::IconSize::NORMAL));
262 pButton = Gtk::make_managed<Gtk::Button>();
263 pButton->set_has_frame(false);
264 pButton->set_child(*pIcon);
265 pButton->signal_clicked().connect(sigc::mem_fun(*this, &PathParam::on_link_button_click));
266 UI::pack_start(*_widget, *pButton, true, true);
267 pButton->set_tooltip_text(_("Link to path in clipboard"));
268 }
269
270 return _widget;
271}
272
273void
275{
276 SPDocument *document = dt->getDocument();
277 DocumentUndo::ScopedInsensitive _no_undo(document);
278 using namespace Inkscape::UI;
279
280 auto nt = dynamic_cast<Inkscape::UI::Tools::NodeTool *>(dt->getTool());
281 if (!nt) {
282 set_active_tool(dt, "Node");
283 nt = dynamic_cast<Inkscape::UI::Tools::NodeTool *>(dt->getTool());
284 }
285
286 std::set<ShapeRecord> shapes;
287 ShapeRecord r;
288
289 r.role = SHAPE_ROLE_LPE_PARAM;
290 r.edit_transform = item->i2dt_affine(); // TODO is it right?
291 if (!href) {
293 r.lpe_key = param_key;
294 Geom::PathVector stored_pv = _pathvector;
295 if (_pathvector.empty()) {
296 param_write_to_repr("M0,0 L1,0");
297 } else {
298 param_write_to_repr(sp_svg_write_path(stored_pv).c_str());
299 }
300 } else {
301 r.object = ref.getObject();
302 }
303 shapes.insert(r);
304 nt->_multipath->setItems(shapes);
305}
306
307void
308PathParam::param_setup_nodepath(Inkscape::NodePath::Path *)
309{
310 // TODO this method should not exist at all!
311}
312
313void
314PathParam::addCanvasIndicators(SPLPEItem const*/*lpeitem*/, std::vector<Geom::PathVector> &hp_vec)
315{
316 hp_vec.push_back(_pathvector);
317}
318
319/*
320 * Only applies transform when not referring to other path!
321 */
322void
324{
325 // only apply transform when not referring to other path
326 if (!href) {
327 set_new_value( _pathvector * postmul, true );
328 }
329}
330
331/*
332 * See comments for set_new_value(Geom::PathVector).
333 */
334void
336{
337 unlink();
338
339 _pathvector = Geom::path_from_piecewise(newpath, LPE_CONVERSION_TOLERANCE);
340
341 if (write_to_svg) {
343 return;
344 }
346
347 // After the whole "writing to svg avalanche of function calling": force value upon pwd2 and don't recalculate.
348 _pwd2 = newpath;
349 must_recalculate_pwd2 = false;
350 } else {
351 _pwd2 = newpath;
352 must_recalculate_pwd2 = false;
353 emit_changed();
354 }
355}
356
357/*
358 * This method sets new path data.
359 * If this PathParam refers to another path, this link is removed (and replaced with explicit path data).
360 *
361 * If write_to_svg = true :
362 * The new path data is written to SVG. In this case the signal_path_changed signal
363 * is not directly emitted in this method, because writing to SVG
364 * triggers the LPEObject to which this belongs to call Effect::setParameter which calls
365 * PathParam::readSVGValue, which finally emits the signal_path_changed signal.
366 * If write_to_svg = false :
367 * The new path data is not written to SVG. This method will emit the signal_path_changed signal.
368 */
369void
370PathParam::set_new_value (Geom::PathVector const &newpath, bool write_to_svg)
371{
372 unlink();
373 if (newpath.empty()) {
375 return;
376 } else {
377 _pathvector = newpath;
378 }
380
381 if (write_to_svg) {
383 } else {
384 emit_changed();
385 }
386}
387
388void
390{
392 _pwd2.clear();
393 for (const auto & i : _pathvector) {
394 _pwd2.concat( i.toPwSb() );
395 }
396
397 must_recalculate_pwd2 = false;
398 }
399}
400
401void
403{
404 changed = true;
405 signal_path_changed.emit();
406}
407
408void
410{
411 if ( to == nullptr ) {
412 return;
413 }
417 if (is<SPItem>(to)) {
418 linked_transformed_connection = cast<SPItem>(to)->connectTransformed(sigc::mem_fun(*this, &PathParam::linked_transformed));
419 }
420 linked_modified(to, SP_OBJECT_MODIFIED_FLAG); // simulate linked_modified signal, so that path data is updated
421}
422
423void
430
431void
433{
435 if ( new_ref ) {
436 start_listening(new_ref);
437 }
438}
439
441{
442 if (href) {
443 ref.detach();
444 g_free(href);
445 href = nullptr;
446 }
447}
448
449// Why release signal is not fired sometimes and need delete one?
450void
457
458void PathParam::linked_modified(SPObject *linked_obj, guint flags)
459{
460 if (flags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG |
461 SP_OBJECT_CHILD_MODIFIED_FLAG | SP_OBJECT_VIEWPORT_MODIFIED_FLAG))
462 {
463 linked_modified_callback(linked_obj, flags);
464 }
465}
466
467void PathParam::linked_transformed(Geom::Affine const *rel_transf, SPItem *moved_item)
468{
469 linked_modified_callback(moved_item, SP_OBJECT_MODIFIED_FLAG);
470}
471
472void
474{
475 if (!_updating && flags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG |
476 SP_OBJECT_CHILD_MODIFIED_FLAG | SP_OBJECT_VIEWPORT_MODIFIED_FLAG))
477 {
478 std::optional<SPCurve> curve;
479 if (auto shape = cast<SPShape>(linked_obj)) {
480 if (_from_original_d) {
481 curve = SPCurve::ptr_to_opt(shape->curveForEdit());
482 } else {
483 curve = SPCurve::ptr_to_opt(shape->curve());
484 }
485 }
486
487 auto text = cast<SPText>(linked_obj);
488 if (text) {
489 bool hidden = text->isHidden();
490 if (hidden) {
491 if (_pathvector.empty()) {
492 text->setHidden(false);
493 curve = text->getNormalizedBpath();
494 text->setHidden(true);
495 } else {
496 if (!curve) {
497 curve.emplace();
498 }
499 curve->set_pathvector(_pathvector);
500 }
501 } else {
502 curve = text->getNormalizedBpath();
503 }
504 }
505
506 if (!curve) {
507 // curve invalid, set default value
509 } else {
510 _pathvector = curve->get_pathvector();
511 }
512
514 emit_changed();
515 if (!param_effect->is_load || ownerlocator || (!SP_ACTIVE_DESKTOP && param_effect->isReady())) {
516 param_effect->getLPEObj()->requestModified(SP_OBJECT_MODIFIED_FLAG);
517 }
518 }
519}
520
521void
522PathParam::param_update_default(const gchar * default_value){
523 defvalue = strdup(default_value);
524}
525
526/* CALLBACK FUNCTIONS FOR THE BUTTONS */
527void
529{
530 SPItem * item = SP_ACTIVE_DESKTOP->getSelection()->singleItem();
531 if (item != nullptr) {
532 param_editOncanvas(item, SP_ACTIVE_DESKTOP);
533 }
534}
535
536void
538{
539 // only recognize a non-null, non-empty string
540 if (svgd && *svgd) {
541 // remove possible link to path
542 unlink();
543 SPItem * item = SP_ACTIVE_DESKTOP->getSelection()->singleItem();
544 std::string svgd_new;
545 if (item != nullptr) {
546 Geom::PathVector path_clipboard = sp_svg_read_pathv(svgd);
547 path_clipboard *= item->i2doc_affine().inverse();
548 svgd_new = sp_svg_write_path(path_clipboard);
549 svgd = svgd_new.c_str();
550 }
551
553 signal_path_pasted.emit();
554 }
555}
556
557void
559{
561 Glib::ustring svgd = cm->getPathParameter(SP_ACTIVE_DESKTOP);
562 paste_param_path(svgd.data());
563 param_effect->makeUndoDone(_("Paste path parameter"));
564}
565
566void
572
573void
574PathParam::linkitem(Glib::ustring pathid)
575{
576 if (pathid.empty()) {
577 return;
578 }
579
580 // add '#' at start to make it an uri.
581 pathid.insert(pathid.begin(), '#');
582 if ( href && strcmp(pathid.c_str(), href) == 0 ) {
583 // no change, do nothing
584 return;
585 } else {
586 // TODO:
587 // check if id really exists in document, or only in clipboard document: if only in clipboard then invalid
588 // check if linking to object to which LPE is applied (maybe delegated to PathReference
589 param_write_to_repr(pathid.c_str());
590 param_effect->makeUndoDone(_("Link path parameter to path"));
591 }
592}
593
594void
596{
598 Glib::ustring pathid = cm->getShapeOrTextObjectId(SP_ACTIVE_DESKTOP);
599
600 linkitem(pathid);
601}
602
603} /* namespace LivePathEffect */
604} /* namespace Inkscape */
605
606/*
607 Local Variables:
608 mode:c++
609 c-file-style:"stroustrup"
610 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
611 indent-tabs-mode:nil
612 fill-column:99
613 End:
614*/
615// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
void set_active_tool(InkscapeWindow *win, Glib::ustring const &tool)
TODO: insert short description here.
3x3 matrix representing an affine transformation.
Definition affine.h:70
Affine inverse() const
Compute the inverse matrix.
Definition affine.cpp:388
Adaptor that creates 2D functions from 1D ones.
Definition d2.h:55
Sequence of subpaths.
Definition pathvector.h:122
void clear()
Remove all paths from the vector.
Definition pathvector.h:195
bool empty() const
Check whether the vector contains any paths.
Definition pathvector.h:145
Function defined as discrete pieces.
Definition piecewise.h:71
void concat(const Piecewise< T > &other)
Definition piecewise.h:235
RAII-style mechanism for creating a temporary undo-insensitive context.
bool isOnClipboard()
The lpe is on clipboard.
Definition effect.cpp:1248
std::vector< SPLPEItem * > getCurrrentLPEItems() const
Definition effect.cpp:1187
void makeUndoDone(Glib::ustring message)
Definition effect.cpp:1521
LivePathEffectObject * getLPEObj()
Definition effect.h:151
Inkscape::Display::TemporaryItem * ownerlocator
Definition parameter.h:111
void setUpdating(bool updating)
Definition parameter.h:73
void param_write_to_repr(const char *svgd)
Definition parameter.cpp:52
void linked_transformed(Geom::Affine const *rel_transf, SPItem *moved_item)
Definition path.cpp:467
Geom::PathVector const & get_pathvector() const
Definition path.cpp:99
bool param_readSVGValue(const gchar *strvalue) override
Definition path.cpp:142
virtual void linked_modified_callback(SPObject *linked_obj, guint flags)
Definition path.cpp:473
sigc::connection ref_changed_connection
Definition path.h:87
sigc::connection linked_modified_connection
Definition path.h:89
void linked_deleted(SPObject *deleted)
Definition path.cpp:451
void param_editOncanvas(SPItem *item, SPDesktop *dt) override
Definition path.cpp:274
Geom::Piecewise< Geom::D2< Geom::SBasis > > const & get_pwd2()
Definition path.cpp:105
void addCanvasIndicators(SPLPEItem const *lpeitem, std::vector< Geom::PathVector > &hp_vec) override
Definition path.cpp:314
void set_new_value(Geom::PathVector const &newpath, bool write_to_svg)
Definition path.cpp:370
void param_setup_nodepath(Inkscape::NodePath::Path *np) override
Definition path.cpp:308
sigc::connection linked_deleted_connection
Definition path.h:88
void paste_param_path(const char *svgd)
Definition path.cpp:537
void linkitem(Glib::ustring pathid)
Definition path.cpp:574
void param_set_default() override
Definition path.cpp:112
PathParam(const Glib::ustring &label, const Glib::ustring &tip, const Glib::ustring &key, Inkscape::UI::Widget::Registry *wr, Effect *effect, const gchar *default_value="M0,0 L1,1")
Definition path.cpp:47
sigc::signal< void()> signal_path_changed
Definition path.h:60
void start_listening(SPObject *to)
Definition path.cpp:409
std::vector< SPObject * > param_get_satellites() override
Definition path.cpp:123
Glib::ustring param_getSVGValue() const override
Definition path.cpp:196
void param_transform_multiply(Geom::Affine const &postmul, bool set) override
Definition path.cpp:323
void ref_changed(SPObject *old_ref, SPObject *new_ref)
Definition path.cpp:432
void set_buttons(bool edit_button, bool copy_button, bool paste_button, bool link_button)
Definition path.cpp:212
sigc::signal< void()> signal_path_pasted
Definition path.h:59
sigc::connection linked_transformed_connection
Definition path.h:90
Geom::PathVector _pathvector
Definition path.h:70
void linked_modified(SPObject *linked_obj, guint flags)
Definition path.cpp:458
Glib::ustring param_getDefaultSVGValue() const override
Definition path.cpp:206
void param_update_default(const gchar *default_value) override
Definition path.cpp:522
Geom::Affine get_relative_affine()
Definition path.cpp:86
Geom::Piecewise< Geom::D2< Geom::SBasis > > _pwd2
Definition path.h:72
Gtk::Widget * param_newWidget() override
Definition path.cpp:221
System-wide clipboard manager.
Definition clipboard.h:44
virtual void copyPathParameter(Inkscape::LivePathEffect::PathParam *)=0
static ClipboardManager * get()
virtual Glib::ustring getShapeOrTextObjectId(SPDesktop *desktop)=0
virtual Glib::ustring getPathParameter(SPDesktop *desktop)=0
void detach()
Detaches from the currently attached URI target, if any; the current referrent is signaled as NULL.
bool isAttached() const
Returns true if there is currently an attached URI.
sigc::signal< void(SPObject *, SPObject *)> changedSignal()
Accessor for the referrent change notification signal; this signal is emitted whenever the URIReferen...
void attach(URI const &uri)
Attaches to a URI, relative to the specified document.
Represents an URI as per RFC 2396.
Definition uri.h:36
static std::optional< SPCurve > ptr_to_opt(T const &p)
Definition curve.h:83
To do: update description of desktop.
Definition desktop.h:149
SPDocument * getDocument() const
Definition desktop.h:189
Inkscape::UI::Tools::ToolBase * getTool() const
Definition desktop.h:187
Typed SVG document implementation.
Definition document.h:103
SPObject * getObjectByHref(std::string const &href) 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:1828
Geom::Affine getRelativeTransform(SPObject const *obj) const
Definition sp-item.cpp:1819
Geom::Affine i2doc_affine() const
Returns the accumulated transformation of the item and all its ancestors, including root's viewport.
Definition sp-item.cpp:1823
SPObject is an abstract base class of all of the document nodes at the SVG document level.
Definition sp-object.h:160
void requestModified(unsigned int flags)
Requests that a modification notification signal be emitted later (e.g.
sigc::connection connectDelete(sigc::slot< void(SPObject *)> slot)
Connects a slot to be called when an object is deleted.
Definition sp-object.h:545
SPObject * _tmpsuccessor
Definition sp-object.h:731
char const * getId() const
Returns the objects current ID string.
sigc::connection connectModified(sigc::slot< void(SPObject *, unsigned int)> slot)
Connects to the modification notification signal.
Definition sp-object.h:705
void requestDisplayUpdate(unsigned int flags)
Queues an deferred update of this object's display.
System-wide clipboard management - class declaration.
Lifts one dimensional objects into 2D.
Editable view implementation.
TODO: insert short description here.
Gtk::Image * sp_get_icon_image(Glib::ustring const &icon_name, int size)
Icon Loader.
SPItem * item
Glib::ustring label
Multi path manipulator - a tool component that edits multiple paths at once.
PathVector path_from_piecewise(Piecewise< D2< SBasis > > const &B, double tol, bool only_cubicbeziers=false)
Make a path from a d2 sbasis.
Affine identity()
Create an identity matrix.
Definition affine.h:210
User interface code.
Definition desktop.h:113
void pack_start(Gtk::Box &box, Gtk::Widget &child, bool const expand, bool const fill, unsigned const padding)
Adds child to box, packed with reference to the start of box.
Definition pack.cpp:141
Helper class to stream background task notifications as a series of messages.
New node tool with support for multiple path editing.
static cairo_user_data_key_t key
Helpers for using Gtk::Boxes, encapsulating large changes between GTK3 & GTK4.
PathVector - a sequence of subpaths.
Ocnode ** ref
Definition quantize.cpp:32
Conversion between SBasis and Bezier basis polynomials.
Structures that store data needed for shape editing which are not contained directly in the XML node.
Some things pertinent to all visible shapes: SPItem, SPItemView, SPItemCtx.
Definition curve.h:24
parse SVG path specifications
Geom::PathVector sp_svg_read_pathv(char const *str)
Definition svg-path.cpp:37
static void sp_svg_write_path(Inkscape::SVG::PathString &str, Geom::Path const &p, bool normalize=false)
Definition svg-path.cpp:109