Inkscape
Vector Graphics Editor
Loading...
Searching...
No Matches
patharray.cpp
Go to the documentation of this file.
1// SPDX-License-Identifier: GPL-2.0-or-later
2/*
3 * Copyright (C) Theodore Janeczko 2012 <flutterguy317@gmail.com>
4 *
5 * Released under GNU GPL v2+, read the file 'COPYING' for more information.
6 */
7
8#include "patharray.h"
9
10#include <utility>
11#include <glibmm/i18n.h>
12#include <gtkmm/box.h>
13#include <gtkmm/button.h>
14#include <gtkmm/cellrenderertext.h>
15#include <gtkmm/cellrenderertoggle.h>
16#include <gtkmm/image.h>
17#include <gtkmm/scrolledwindow.h>
18#include <gtkmm/treemodel.h>
19#include <gtkmm/treestore.h>
20#include <gtkmm/treeview.h>
21
22#include "document.h"
23#include "inkscape.h"
24
25#include "display/curve.h"
26#include "live_effects/effect.h"
32#include "object/sp-shape.h"
33#include "object/sp-text.h"
34#include "object/uri.h"
35#include "svg/stringstream.h"
36#include "ui/clipboard.h"
37#include "ui/icon-loader.h"
38#include "ui/pack.h"
39
41
42class PathArrayParam::ModelColumns : public Gtk::TreeModel::ColumnRecord
43{
44public:
45 ModelColumns()
46 {
47 add(_colObject);
48 add(_colLabel);
49 add(_colReverse);
50 add(_colVisible);
51 }
52
53 Gtk::TreeModelColumn<PathAndDirectionAndVisible*> _colObject;
54 Gtk::TreeModelColumn<Glib::ustring> _colLabel;
55 Gtk::TreeModelColumn<bool> _colReverse;
56 Gtk::TreeModelColumn<bool> _colVisible;
57};
58
59PathArrayParam::PathArrayParam(const Glib::ustring &label, const Glib::ustring &tip, const Glib::ustring &key,
61 : Parameter(label, tip, key, wr, effect)
62{
63 // refresh widgets on load to allow to remove the
64 // memory leak calling initui here
66 oncanvas_editable = true;
67}
68
70 while (!_vector.empty()) {
72 unlink(w);
73 }
74
75 _model.reset();
76}
77
79{
80 SPDesktop * desktop = SP_ACTIVE_DESKTOP;
81
82 if (!desktop) {
83 return;
84 }
85
86 if (!_tree) {
87 _tree = std::make_unique<Gtk::TreeView>();
88 _model = std::make_unique<ModelColumns>();
89 _store = Gtk::TreeStore::create(*_model);
90 _tree->set_model(_store);
91
92 _tree->set_reorderable(true);
93 _tree->enable_model_drag_dest (Gdk::DragAction::MOVE);
94
95 auto const toggle_reverse = Gtk::make_managed<Gtk::CellRendererToggle>();
96 int reverseColNum = _tree->append_column(_("Reverse"), *toggle_reverse) - 1;
97 Gtk::TreeViewColumn* col_reverse = _tree->get_column(reverseColNum);
98 toggle_reverse->set_activatable(true);
99 toggle_reverse->signal_toggled().connect(sigc::mem_fun(*this, &PathArrayParam::on_reverse_toggled));
100 col_reverse->add_attribute(toggle_reverse->property_active(), _model->_colReverse);
101
102
103 auto const toggle_visible = Gtk::make_managed<Gtk::CellRendererToggle>();
104 int visibleColNum = _tree->append_column(_("Visible"), *toggle_visible) - 1;
105 Gtk::TreeViewColumn* col_visible = _tree->get_column(visibleColNum);
106 toggle_visible->set_activatable(true);
107 toggle_visible->signal_toggled().connect(sigc::mem_fun(*this, &PathArrayParam::on_visible_toggled));
108 col_visible->add_attribute(toggle_visible->property_active(), _model->_colVisible);
109
110 auto const text_renderer = Gtk::make_managed<Gtk::CellRendererText>();
111 int nameColNum = _tree->append_column(_("Name"), *text_renderer) - 1;
112 Gtk::TreeView::Column *name_column = _tree->get_column(nameColNum);
113 name_column->add_attribute(text_renderer->property_text(), _model->_colLabel);
114
115 _tree->set_expander_column(*_tree->get_column(nameColNum) );
116 _tree->set_search_column(_model->_colLabel);
117
118 _scroller = std::make_unique<Gtk::ScrolledWindow>();
119 //quick little hack -- newer versions of gtk gave the item zero space allotment
120 _scroller->set_size_request(-1, 120);
121 _scroller->set_child(*_tree);
122 _scroller->set_policy( Gtk::PolicyType::AUTOMATIC, Gtk::PolicyType::AUTOMATIC );
123 }
125}
126
127void PathArrayParam::on_reverse_toggled(const Glib::ustring &path)
128{
129 auto const iter = _store->get_iter(path);
130 Gtk::TreeModel::Row row = *iter;
131 PathAndDirectionAndVisible *w = row[_model->_colObject];
132 row[_model->_colReverse] = !row[_model->_colReverse];
133 w->reversed = row[_model->_colReverse];
135 param_effect->makeUndoDone(_("Link path parameter to path"));
136}
137
138void PathArrayParam::on_visible_toggled(const Glib::ustring &path)
139{
140 auto const iter = _store->get_iter(path);
141 Gtk::TreeModel::Row row = *iter;
142 PathAndDirectionAndVisible *w = row[_model->_colObject];
143 row[_model->_colVisible] = !row[_model->_colVisible];
144 w->visibled = row[_model->_colVisible];
146 param_effect->makeUndoDone(_("Toggle path parameter visibility"));
147}
148
150
152{
153
154 auto const vbox = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::VERTICAL);
155 auto const hbox = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::HORIZONTAL);
156
157 _tree.reset();
158 _model.reset();
159 _scroller.reset();
160
161 initui();
162
164
165
166 { // Paste path to link button
167 auto const pIcon = Gtk::manage(sp_get_icon_image("edit-clone", Gtk::IconSize::NORMAL));
168 auto const pButton = Gtk::make_managed<Gtk::Button>();
169 pButton->set_has_frame(false);
170 pButton->set_child(*pIcon);
171 pButton->signal_clicked().connect(sigc::mem_fun(*this, &PathArrayParam::on_link_button_click));
172 UI::pack_start(*hbox, *pButton, UI::PackOptions::shrink);
173 pButton->set_tooltip_text(_("Link to path in clipboard"));
174 }
175
176 { // Remove linked path
177 auto const pIcon = Gtk::manage(sp_get_icon_image("list-remove", Gtk::IconSize::NORMAL));
178 auto const pButton = Gtk::make_managed<Gtk::Button>();
179 pButton->set_has_frame(false);
180 pButton->set_child(*pIcon);
181 pButton->signal_clicked().connect(sigc::mem_fun(*this, &PathArrayParam::on_remove_button_click));
182 UI::pack_start(*hbox, *pButton, UI::PackOptions::shrink);
183 pButton->set_tooltip_text(_("Remove Path"));
184 }
185
186 { // Move Down
187 auto const pIcon = Gtk::manage(sp_get_icon_image("go-down", Gtk::IconSize::NORMAL));
188 auto const pButton = Gtk::make_managed<Gtk::Button>();
189 pButton->set_has_frame(false);
190 pButton->set_child(*pIcon);
191 pButton->signal_clicked().connect(sigc::mem_fun(*this, &PathArrayParam::on_down_button_click));
192 UI::pack_end(*hbox, *pButton, UI::PackOptions::shrink);
193 pButton->set_tooltip_text(_("Move Down"));
194 }
195
196 { // Move Down
197 auto const pIcon = Gtk::manage(sp_get_icon_image("go-up", Gtk::IconSize::NORMAL));
198 auto const pButton = Gtk::make_managed<Gtk::Button>();
199 pButton->set_has_frame(false);
200 pButton->set_child(*pIcon);
201 pButton->signal_clicked().connect(sigc::mem_fun(*this, &PathArrayParam::on_up_button_click));
202 UI::pack_end(*hbox, *pButton, UI::PackOptions::shrink);
203 pButton->set_tooltip_text(_("Move Up"));
204 }
205
207
208 return vbox;
209}
210
211bool PathArrayParam::_selectIndex(const Gtk::TreeModel::iterator &iter, int *i)
212{
213 if ((*i)-- <= 0) {
214 _tree->get_selection()->select(iter);
215 return true;
216 }
217 return false;
218}
219
221{
222 std::vector<SPObject *> objs;
223 for (auto const &iter : _vector) {
224 if (iter && iter->ref.isAttached()) {
225 SPObject *obj = iter->ref.getObject();
226 if (obj) {
227 objs.push_back(obj);
228 }
229 }
230 }
231 return objs;
232}
233
235{
236 auto const iter = _tree->get_selection()->get_selected();
237 if (iter) {
238 Gtk::TreeModel::Row row = *iter;
239
240 int i = -1;
241 auto piter = _vector.begin();
242 for (auto iter = _vector.begin(); iter != _vector.end(); piter = iter, ++i, ++iter) {
243 if (*iter == row[_model->_colObject]) {
244 _vector.erase(iter);
245 _vector.insert(piter, row[_model->_colObject]);
246 break;
247 }
248 }
250 param_effect->makeUndoDone(_("Move path up"));
251 _store->foreach_iter(sigc::bind(sigc::mem_fun(*this, &PathArrayParam::_selectIndex), &i));
252 }
253}
254
256{
257 auto const iter = _tree->get_selection()->get_selected();
258 if (iter) {
259 Gtk::TreeModel::Row row = *iter;
260
261 int i = 0;
262 for (auto iter = _vector.begin(); iter != _vector.end(); ++i, ++iter) {
263 if (*iter == row[_model->_colObject]) {
264 auto niter = _vector.erase(iter);
265 if (niter != _vector.end()) {
266 ++niter;
267 i++;
268 }
269 _vector.insert(niter, row[_model->_colObject]);
270 break;
271 }
272 }
274 param_effect->makeUndoDone(_("Move path down"));
275 _store->foreach_iter(sigc::bind(sigc::mem_fun(*this, &PathArrayParam::_selectIndex), &i));
276 }
277}
278
280{
281 auto const iter = _tree->get_selection()->get_selected();
282 if (iter) {
283 Gtk::TreeModel::Row row = *iter;
284 unlink(row[_model->_colObject]);
286 param_effect->makeUndoDone(_("Remove path"));
287 }
288}
289
291{
293
294 std::vector<Glib::ustring> pathsid = cm->getElementsOfType(SP_ACTIVE_DESKTOP, "svg:path");
295 std::vector<Glib::ustring> textsid = cm->getElementsOfType(SP_ACTIVE_DESKTOP, "svg:text");
296 pathsid.insert(pathsid.end(), textsid.begin(), textsid.end());
297 if (pathsid.empty()) {
298 return;
299 }
300
301 bool foundOne = false;
303
304 for (auto const &iter : _vector) {
305 if (foundOne) {
306 os << "|";
307 } else {
308 foundOne = true;
309 }
310 os << iter->href.c_str() << "," << (iter->reversed ? "1" : "0") << "," << (iter->visibled ? "1" : "0");
311 }
312
313 for (auto &&pathid : std::move(pathsid)) {
314 // add '#' at start to make it an uri.
315 pathid.insert(pathid.begin(), '#');
316
317 if (foundOne) {
318 os << "|";
319 } else {
320 foundOne = true;
321 }
322 os << pathid.c_str() << ",0,1";
323 }
324
325 param_write_to_repr(os.str().c_str());
326 param_effect->makeUndoDone(_("Link patharray parameter to path"));
327}
328
330{
331 to->linked_modified_connection.disconnect();
332 to->linked_release_connection.disconnect();
333 to->ref.detach();
335 to->href.clear();
336
337 for (auto iter = _vector.begin(); iter != _vector.end(); ++iter) {
338 if (*iter == to) {
340 _vector.erase(iter);
341 delete w;
342 return;
343 }
344 }
345}
346
348{
349 for (auto const &w : _vector) {
350 linked_changed(nullptr,w->ref.getObject(), w);
351 }
352}
353
355{
356 if (to && param_effect->getLPEObj()) {
357 to->linked_modified_connection.disconnect();
358 to->linked_release_connection.disconnect();
359 }
360}
361
362bool PathArrayParam::_updateLink(const Gtk::TreeModel::iterator &iter, PathAndDirectionAndVisible *pd)
363{
364 Gtk::TreeModel::Row row = *iter;
365 if (row[_model->_colObject] == pd) {
366 SPObject *obj = pd->ref.getObject();
367 row[_model->_colLabel] = obj && obj->getId() ? ( obj->label() ? obj->label() : obj->getId() ) : pd->href.c_str();
368 return true;
369 }
370 return false;
371}
372
374{
375 if (to) {
376 to->linked_modified_connection.disconnect();
377
378 if (new_obj && is<SPItem>(new_obj)) {
379 to->linked_release_connection.disconnect();
381 sigc::bind(sigc::mem_fun(*this, &PathArrayParam::linked_release), to));
383 sigc::bind(sigc::mem_fun(*this, &PathArrayParam::linked_modified), to));
384
385 linked_modified(new_obj, SP_OBJECT_MODIFIED_FLAG, to);
386 } else if (to->linked_release_connection.connected()){
387 param_effect->getLPEObj()->requestModified(SP_OBJECT_MODIFIED_FLAG);
388 if (_store.get()) {
389 _store->foreach_iter(
390 sigc::bind(sigc::mem_fun(*this, &PathArrayParam::_updateLink), to));
391 }
392 }
393 }
394}
395
397{
398 if (!to) {
399 return;
400 }
401 std::optional<Geom::PathVector> curve;
402 auto text = cast<SPText>(linked_obj);
403 if (auto shape = cast<SPShape>(linked_obj)) {
404 auto lpe_item = cast<SPLPEItem>(linked_obj);
405 if (_from_original_d) {
406 curve = ptr_to_opt(shape->curveForEdit());
407 } else if (_allow_only_bspline_spiro && lpe_item && lpe_item->hasPathEffect()){
408 curve = ptr_to_opt(shape->curveForEdit());
409 PathEffectList lpelist = lpe_item->getEffectList();
410 for (auto const &i : lpelist) {
411 if (auto const lpeobj = i->lpeobject) {
412 auto const lpe = lpeobj->get_lpe();
413 if (auto bspline = dynamic_cast<Inkscape::LivePathEffect::LPEBSpline *>(lpe)) {
415 LivePathEffect::sp_bspline_do_effect(*curve, 0, hp, bspline->uniform);
416 } else if (dynamic_cast<Inkscape::LivePathEffect::LPESpiro *>(lpe)) {
418 }
419 }
420 }
421 } else {
422 curve = ptr_to_opt(shape->curve());
423 }
424 } else if (text) {
425 bool hidden = text->isHidden();
426 if (hidden) {
427 if (to->_pathvector.empty()) {
428 text->setHidden(false);
429 curve = text->getNormalizedBpath();
430 text->setHidden(true);
431 } else {
432 if (!curve) {
433 curve.emplace();
434 }
435 curve = to->_pathvector;
436 }
437 } else {
438 curve = text->getNormalizedBpath();
439 }
440 }
441
442 if (!curve) {
443 // curve invalid, set empty pathvector
444 to->_pathvector = {};
445 } else {
446 to->_pathvector = *curve;
447 }
448}
449
451{
452 if (!_updating && flags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG |
453 SP_OBJECT_CHILD_MODIFIED_FLAG | SP_OBJECT_VIEWPORT_MODIFIED_FLAG)) {
454 if (!to) {
455 return;
456 }
457 setPathVector(linked_obj, flags, to);
458 if (!param_effect->is_load || ownerlocator || (!SP_ACTIVE_DESKTOP && param_effect->isReady())) {
459 param_effect->getLPEObj()->requestModified(SP_OBJECT_MODIFIED_FLAG);
460 }
461 if (_store.get()) {
462 _store->foreach_iter(
463 sigc::bind(sigc::mem_fun(*this, &PathArrayParam::_updateLink), to));
464 }
465 }
466}
467
468bool PathArrayParam::param_readSVGValue(char const * const strvalue)
469{
470 if (strvalue) {
471 while (!_vector.empty()) {
473 unlink(w);
474 }
475
476 if (_store.get()) {
477 _store->clear();
478 }
479
480 auto const strarray = g_strsplit(strvalue, "|", 0);
481 bool write = false;
482 for (auto iter = strarray; *iter != nullptr; ++iter) {
483 if ((*iter)[0] == '#') {
484 auto const substrarray = g_strsplit(*iter, ",", 0);
485 SPObject * old_ref = param_effect->getSPDoc()->getObjectByHref(*substrarray);
486 if (old_ref) {
487 SPObject * tmpsuccessor = old_ref->_tmpsuccessor;
488 Glib::ustring id = *substrarray;
489 if (tmpsuccessor && tmpsuccessor->getId()) {
490 id = tmpsuccessor->getId();
491 id.insert(id.begin(), '#');
492 write = true;
493 }
494 *(substrarray) = g_strdup(id.c_str());
495 }
497 w->href = *substrarray;
498 w->reversed = *(substrarray+1) != nullptr && (*(substrarray+1))[0] == '1';
499 //Like this to make backwards compatible, new value added in 0.93
500 w->visibled = *(substrarray+2) == nullptr || (*(substrarray+2))[0] == '1';
501 w->linked_changed_connection = w->ref.changedSignal().connect(
502 sigc::bind(sigc::mem_fun(*this, &PathArrayParam::linked_changed), w));
503 w->ref.attach(URI(w->href.c_str()));
504
505 _vector.push_back(w);
506
507 if (_store.get()) {
508 auto const iter = _store->append();
509 Gtk::TreeModel::Row row = *iter;
510 SPObject *obj = w->ref.getObject();
511
512 row[_model->_colObject] = w;
513 row[_model->_colLabel] = obj ? ( obj->label() ? obj->label() : obj->getId() ) : w->href.c_str();
514 row[_model->_colReverse] = w->reversed;
515 row[_model->_colVisible] = w->visibled;
516 }
517
518 g_strfreev (substrarray);
519 }
520 }
521
522 g_strfreev (strarray);
523
524 if (write) {
526 }
527
528 return true;
529
530 }
531 return false;
532}
533
535{
537 bool foundOne = false;
538 for (auto const &iter : _vector) {
539 if (foundOne) {
540 os << "|";
541 } else {
542 foundOne = true;
543 }
544 os << iter->href.c_str() << "," << (iter->reversed ? "1" : "0") << "," << (iter->visibled ? "1" : "0");
545 }
546 return os.str();
547}
548
550{
551 return "";
552}
553
555{
556 for (auto const &iter : _vector) {
557 SPObject *linked_obj = iter->ref.getObject();
558 linked_modified(linked_obj, SP_OBJECT_MODIFIED_FLAG, iter);
559 }
560}
561
562} // namespace Inkscape::LivePathEffect
563
564/*
565 Local Variables:
566 mode:c++
567 c-file-style:"stroustrup"
568 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
569 indent-tabs-mode:nil
570 fill-column:99
571 End:
572*/
573// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
Sequence of subpaths.
Definition pathvector.h:122
bool empty() const
Check whether the vector contains any paths.
Definition pathvector.h:145
void makeUndoDone(Glib::ustring message)
Definition effect.cpp:1521
LivePathEffectObject * getLPEObj()
Definition effect.h:150
Inkscape::Display::TemporaryItem * ownerlocator
Definition parameter.h:111
void param_write_to_repr(const char *svgd)
Definition parameter.cpp:52
Gtk::Widget * param_newWidget() override
void on_reverse_toggled(const Glib::ustring &path)
void unlink(PathAndDirectionAndVisible *to)
std::vector< SPObject * > param_get_satellites() override
void linked_modified(SPObject *linked_obj, guint flags, PathAndDirectionAndVisible *to)
PathArrayParam(const Glib::ustring &label, const Glib::ustring &tip, const Glib::ustring &key, Inkscape::UI::Widget::Registry *wr, Effect *effect)
Definition patharray.cpp:59
void linked_changed(SPObject *old_obj, SPObject *new_obj, PathAndDirectionAndVisible *to)
void linked_release(SPObject *release, PathAndDirectionAndVisible *to)
bool _updateLink(const Gtk::TreeModel::iterator &iter, PathAndDirectionAndVisible *pd)
std::unique_ptr< Gtk::TreeView > _tree
Definition patharray.h:97
std::vector< PathAndDirectionAndVisible * > _vector
Definition patharray.h:79
Glib::RefPtr< Gtk::TreeStore > _store
Definition patharray.h:96
bool param_readSVGValue(char const *strvalue) override
bool _selectIndex(const Gtk::TreeModel::iterator &iter, int *i)
Glib::ustring param_getDefaultSVGValue() const override
void setPathVector(SPObject *linked_obj, guint flags, PathAndDirectionAndVisible *to)
Glib::ustring param_getSVGValue() const override
std::unique_ptr< Gtk::ScrolledWindow > _scroller
Definition patharray.h:98
std::unique_ptr< ModelColumns > _model
Definition patharray.h:95
void on_visible_toggled(const Glib::ustring &path)
std::string str() const
System-wide clipboard manager.
Definition clipboard.h:44
virtual std::vector< Glib::ustring > getElementsOfType(SPDesktop *desktop, gchar const *type="*", gint maxdepth=-1)=0
static ClipboardManager * get()
SPObject * getObject() const
Returns a pointer to the current referrent of the attached URI, or NULL.
void detach()
Detaches from the currently attached URI target, if any; the current referrent is signaled as NULL.
Represents an URI as per RFC 2396.
Definition uri.h:36
To do: update description of desktop.
Definition desktop.h:149
SPObject * getObjectByHref(std::string const &href) const
SPObject is an abstract base class of all of the document nodes at the SVG document level.
Definition sp-object.h:160
char const * label() const
Gets the author-visible label property for the object or a default if no label is defined.
void requestModified(unsigned int flags)
Requests that a modification notification signal be emitted later (e.g.
SPObject * _tmpsuccessor
Definition sp-object.h:731
char const * getId() const
Returns the objects current ID string.
sigc::connection connectRelease(sigc::slot< void(SPObject *)> slot)
Connects to the release request signal.
Definition sp-object.h:237
sigc::connection connectModified(sigc::slot< void(SPObject *, unsigned int)> slot)
Connects to the modification notification signal.
Definition sp-object.h:705
System-wide clipboard management - class declaration.
const double w
Definition conic-4.cpp:19
auto ptr_to_opt(T const &p)
Create a std::optional<T> from a (generalised) pointer to T.
Definition curve.h:90
Gtk::Image * sp_get_icon_image(Glib::ustring const &icon_name, int size)
Icon Loader.
Glib::ustring label
Live Path Effects code.
void sp_bspline_do_effect(Geom::PathVector &curve, double helper_size, Geom::PathVector &hp, bool uniform)
void sp_spiro_do_effect(Geom::PathVector &curve)
Definition lpe-spiro.cpp:29
void pack_end(Gtk::Box &box, Gtk::Widget &child, bool const expand, bool const fill, unsigned const padding)
Adds child to box, packed with reference to the end of box.
Definition pack.cpp:153
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
static cairo_user_data_key_t key
Helpers for using Gtk::Boxes, encapsulating large changes between GTK3 & GTK4.
std::list< PathEffectSharedPtr > PathEffectList
Definition sp-lpe-item.h:45
Definition curve.h:24
TODO: insert short description here.
SPDesktop * desktop