Inkscape
Vector Graphics Editor
Loading...
Searching...
No Matches
livepatheffect-editor.cpp
Go to the documentation of this file.
1// SPDX-License-Identifier: GPL-2.0-or-later
5/* Authors:
6 * Jabiertxof
7 * Adam Belis (UX/Design)
8 *
9 * Released under GNU GPL v2+, read the file 'COPYING' for more information.
10 */
11
13
14#include <algorithm>
15#include <cstddef>
16#include <cstdlib>
17#include <functional>
18#include <map>
19#include <tuple>
20#include <glibmm/i18n.h>
21#include <glibmm/quark.h>
22#include <glibmm/value.h>
23#include <giomm/simpleactiongroup.h>
24#include <gtkmm/box.h>
25#include <gtkmm/builder.h>
26#include <gtkmm/button.h>
27#include <gtkmm/combobox.h>
28#include <gtkmm/dragsource.h>
29#include <gtkmm/droptarget.h>
30#include <gtkmm/expander.h>
31#include <gtkmm/gestureclick.h>
32#include <gtkmm/image.h>
33#include <gtkmm/label.h>
34#include <gtkmm/listbox.h>
35#include <gtkmm/liststore.h>
36#include <gtkmm/menubutton.h>
37#include <gtkmm/searchentry2.h>
38#include <gtkmm/spinbutton.h>
39#include <gtkmm/widgetpaintable.h>
40#include <sigc++/adaptors/bind.h>
41#include <sigc++/adaptors/hide.h>
42#include <sigc++/functors/mem_fun.h>
43
44#include "inkscape.h"
45#include "live_effects/effect.h"
49#include "io/resource.h"
50#include "object/sp-flowtext.h"
52#include "object/sp-lpe-item.h"
53#include "object/sp-path.h"
54#include "object/sp-shape.h"
55#include "object/sp-text.h"
56#include "object/sp-tspan.h"
57#include "object/sp-use.h"
58#include "selection.h"
59#include "svg/svg.h"
60#include "ui/builder-utils.h"
62#include "ui/controller.h"
63#include "ui/icon-loader.h"
64#include "ui/icon-names.h"
65#include "ui/pack.h"
66#include "ui/tools/node-tool.h"
67#include "ui/util.h"
69#include "util/optstr.h"
70
71namespace Inkscape::UI::Dialog {
72
73/*
74* * favourites
75 */
76
77static constexpr auto favs_path = "/dialogs/livepatheffect/favs";
78
79static bool sp_has_fav(Glib::ustring const &effect)
80{
82 Glib::ustring favlist = prefs->getString(favs_path);
83 return favlist.find(effect) != favlist.npos;
84}
85
86static void sp_add_fav(Glib::ustring const &effect)
87{
88 if (sp_has_fav(effect)) return;
89
91 Glib::ustring favlist = prefs->getString(favs_path);
92 favlist.append(effect).append(";");
93 prefs->setString(favs_path, favlist);
94}
95
96static void sp_remove_fav(Glib::ustring effect)
97{
98 if (!sp_has_fav(effect)) return;
99
101 Glib::ustring favlist = prefs->getString(favs_path);
102 effect += ";";
103 auto const pos = favlist.find(effect);
104 if (pos == favlist.npos) return;
105
106 favlist.erase(pos, effect.length());
107 prefs->setString(favs_path, favlist);
108}
109
110bool sp_set_experimental(bool &_experimental)
111{
113 bool experimental = prefs->getBool("/dialogs/livepatheffect/showexperimental", false);
114 if (experimental != _experimental) {
115 _experimental = experimental;
116 return true;
117 }
118 return false;
119}
120
121/*####################
122 * Callback functions
123 */
124
133
135{
136 current_lpeitem = cast<SPLPEItem>(selection->singleItem());
137 _current_use = cast<SPUse>(selection->singleItem());
140 } else if (current_lpeitem && current_lperef.first) {
142 }
143 clearMenu();
144}
145
150 : DialogBase("/dialogs/livepatheffect", "LivePathEffect"),
151 _builder(create_builder("dialog-livepatheffect.glade")),
152 LPEListBox(get_widget<Gtk::ListBox>(_builder, "LPEListBox")),
153 _LPEContainer(get_widget<Gtk::Box>(_builder, "LPEContainer")),
154 _LPEAddContainer(get_widget<Gtk::Box>(_builder, "LPEAddContainer")),
155 _LPEParentBox(get_widget<Gtk::ListBox>(_builder, "LPEParentBox")),
156 _LPECurrentItem(get_widget<Gtk::Box>(_builder, "LPECurrentItem")),
157 _LPESelectionInfo(get_widget<Gtk::Label>(_builder, "LPESelectionInfo")),
158 converter(Inkscape::LivePathEffect::LPETypeConverter)
159{
160 // hack to fix DnD freezing expander
161 auto const click = Gtk::GestureClick::create();
162 click->signal_pressed().connect([this](auto &&...) { dnd = false; });
163 _LPEContainer.add_controller(click);
164
165 setMenu();
168
169 _lpes_popup.get_entry().set_placeholder_text(_("Add Live Path Effect"));
170 _lpes_popup.on_match_selected().connect([this](int const id)
171 { onAdd(static_cast<LivePathEffect::EffectType>(id)); });
172 _lpes_popup.on_button_press().connect([this]{ setMenu(); });
173 _lpes_popup.on_focus().connect([this]{ setMenu(); return true; });
175
177
178 set_visible(true);
179}
180
185
186bool
188 bool const has_clip, bool const has_mask)
189{
190 if (!has_clip && etype == LivePathEffect::POWERCLIP) {
191 return false;
192 }
193
194 if (!has_mask && etype == LivePathEffect::POWERMASK) {
195 return false;
196 }
197
198 if (item_type == "group" && !converter.get_on_group(etype)) {
199 return false;
200 } else if (item_type == "shape" && !converter.get_on_shape(etype)) {
201 return false;
202 } else if (item_type == "path" && !converter.get_on_path(etype)) {
203 return false;
204 }
205
206 return true;
207}
208
209void align(Gtk::Widget *top, int const spinbutton_width_chars)
210{
211 auto box = dynamic_cast<Gtk::Box*>(top);
212 if (!box) return;
213 box->set_spacing(2);
214
215 // traverse container, locate n-th child in each row
216 auto for_child_n = [=](int child_index, const std::function<void (Gtk::Widget*)>& action) {
217 for (auto const child : UI::get_children(*box)) {
218 auto const container = dynamic_cast<Gtk::Box *>(child);
219 if (!container) continue;
220
221 container->set_spacing(2);
222 auto const children = UI::get_children(*container);
223 if (child_index < children.size()) {
224 action(children[child_index]);
225 }
226 }
227 };
228
229 // column 0 - labels
230 int max_width = 0;
231 for_child_n(0, [&](Gtk::Widget* child){
232 if (auto label = dynamic_cast<Gtk::Label*>(child)) {
233 label->set_xalign(0); // left-align
234 int label_width = 0, dummy = 0;
235 label->measure(Gtk::Orientation::HORIZONTAL, -1, dummy, label_width, dummy, dummy);
236 if (label_width > max_width) {
237 max_width = label_width;
238 }
239 }
240 });
241 // align
242 for_child_n(0, [=](Gtk::Widget* child) {
243 if (auto label = dynamic_cast<Gtk::Label*>(child)) {
244 label->set_size_request(max_width);
245 }
246 });
247
248 // column 1 - align spin buttons, if any
249 int button_width = 0;
250 for_child_n(1, [&](Gtk::Widget* child) {
251 if (auto spin = dynamic_cast<Gtk::SpinButton*>(child)) {
252 // selected spinbutton size by each LPE default 7
253 spin->set_width_chars(spinbutton_width_chars);
254 int dummy = 0;
255 spin->measure(Gtk::Orientation::HORIZONTAL, -1, dummy, button_width, dummy, dummy);
256 }
257 });
258 // set min size for comboboxes, if any
259 int combo_size = button_width > 0 ? button_width : 50; // match with spinbuttons, or just min of 50px
260 for_child_n(1, [=](Gtk::Widget* child) {
261 if (auto combo = dynamic_cast<Gtk::ComboBox*>(child)) {
262 combo->set_size_request(combo_size);
263 }
264 });
265}
266
267void
273
274static void
275set_visible_icon(Gtk::Button &button, bool const visible)
276{
277 auto &image = dynamic_cast<Gtk::Image &>(*button.get_child());
278 auto const icon_name = visible ? "object-visible-symbolic" : "object-hidden-symbolic";
279 image.set_from_icon_name(icon_name);
280}
281
282void
284 g_assert(lpe);
285 g_assert(visbutton);
286
287 bool visible = g_strcmp0(lpe->getRepr()->attribute("is_visible"), "true") == 0;
288 visible = !visible;
289
290 set_visible_icon(*visbutton, visible);
291
292 if (!visible) {
293 lpe->getRepr()->setAttribute("is_visible", "false");
294 } else {
295 lpe->getRepr()->setAttribute("is_visible", "true");
296 }
297
299
301 !visible ? _("Deactivate path effect") : _("Activate path effect"),
302 INKSCAPE_ICON("dialog-path-effects"));
303}
304
306 static const std::map<Inkscape::LivePathEffect::LPECategory, Glib::ustring> category_names = {
313 };
314 return category_names.at(category);
315}
316
317struct LivePathEffectEditor::LPEMetadata final {
320 Glib::ustring label, icon_name, tooltip;
321 bool sensitive{};
322};
323
324// populate popup with lpes and completion list for a search box
326 std::vector<LPEMetadata> &&lpes)
327{
328 popup.clear_completion_list();
329
330 // 3-column menu
331 // Due to when we rebuild, itʼs not so easy to only populate when the MenuButton is clicked, so
332 // we remove existing children.
333 // TODO: Use MenuButton.set_create_popup_func() to create new menu every time, on demand?
334
335 auto &menu = popup.get_menu();
336 menu.remove_all();
337
338 ColumnMenuBuilder<LivePathEffect::LPECategory> builder{menu, 3, Gtk::IconSize::LARGE};
339 auto const tie = [](LPEMetadata const &lpe){ return std::tie(lpe.category, lpe.label); };
340 std::sort(lpes.begin(), lpes.end(), [=](auto &l, auto &r){ return tie(l) < tie(r); });
341 for (auto const &lpe : lpes) {
342 // build popup menu
343 auto const type = lpe.type;
344 int const id = static_cast<int>(type);
345 auto const menuitem = builder.add_item(lpe.label, lpe.category, lpe.tooltip, lpe.icon_name,
346 lpe.sensitive, true, [=, this]{ onAdd(type); });
347 menuitem->signal_query_tooltip().connect([lpe, id, this](int x, int y, bool kbd, const Glib::RefPtr<Gtk::Tooltip>& tooltipw){
348 return sp_query_custom_tooltip(this, x, y, kbd, tooltipw, id, lpe.tooltip, lpe.icon_name);
349 }, false); // before
350 if (builder.new_section()) {
351 builder.set_section(get_category_name(lpe.category));
352 }
353 }
354
355 // build completion list
356 std::sort(lpes.begin(), lpes.end(), [=](auto &l, auto &r){ return l.label < r.label; });
357 for (auto const &lpe: lpes) {
358 if (lpe.sensitive) {
359 int const id = static_cast<int>(lpe.type);
360 Glib::ustring untranslated_label = converter.get_label(lpe.type);
361 Glib::ustring untranslated_description = converter.get_description(lpe.type);
362 Glib::ustring search = Glib::ustring::compose("%1_%2", untranslated_label, untranslated_description);
363 if (lpe.label != untranslated_label) {
364 search = Glib::ustring::compose("%1_%2_%3", search, lpe.label, _(converter.get_description(lpe.type).c_str()));
365 }
366 popup.add_to_completion_list(id, lpe.label , lpe.icon_name + (symbolic ? "-symbolic" : ""), search);
367 }
368 }
369
370 if (symbolic) {
371 menu.add_css_class("symbolic");
372 }
373}
374
375Glib::ustring LivePathEffectEditor::get_tooltip(LivePathEffect::EffectType const type,
376 Glib::ustring const &untranslated_label)
377{
378 Glib::ustring tooltip = _(converter.get_description(type).c_str());
379 if (tooltip != untranslated_label) {
380 // TRANSLATORS: %1 is the untranslated label. %2 is the effect type description.
381 tooltip = Glib::ustring::compose("[%1] %2", untranslated_label, tooltip);
382 }
383 return tooltip;
384}
385
386void
387LivePathEffectEditor::setMenu()
388{
389 if (!_reload_menu) {
390 return;
391 }
392
393 _reload_menu = false;
394
395 auto shape = cast<SPShape>(current_lpeitem);
396 auto path = cast<SPPath>(current_lpeitem);
397 auto group = cast<SPGroup>(current_lpeitem);
398 bool has_clip = current_lpeitem && (current_lpeitem->getClipObject() != nullptr);
399 bool has_mask = current_lpeitem && (current_lpeitem->getMaskObject() != nullptr);
400
401 Glib::ustring item_type;
402 if (group) {
403 item_type = "group";
404 } else if (path) {
405 item_type = "path";
406 } else if (shape) {
407 item_type = "shape";
408 } else if (_current_use) {
409 item_type = "use";
410 }
411
412 if (!(sp_set_experimental(_experimental) || _item_type != item_type || has_clip != _has_clip || has_mask != _has_mask)) {
413 return;
414 }
415 _item_type = item_type;
416 _has_clip = has_clip;
417 _has_mask = has_mask;
418
419 bool symbolic = Inkscape::Preferences::get()->getBool("/theme/symbolicIcons", true);
420
421 auto lpes = std::vector<LPEMetadata>{};
422 lpes.reserve(converter._length);
423 for (int i = 0; i < static_cast<int>(converter._length); ++i) {
424 auto const * const data = &converter.data(i);
425 auto const &type = data->id;
426 auto const &untranslated_label = converter.get_label(type);
427
428 auto category = converter.get_category(type);
429 if (sp_has_fav(untranslated_label)) {
431 }
432
433 if (!_experimental && category == Inkscape::LivePathEffect::LPECategory::Experimental) {
434 continue;
435 }
436
437 Glib::ustring label = g_dpgettext2(0, "path effect", untranslated_label.c_str());
438 auto const &icon = converter.get_icon(type);
439 auto tooltip = get_tooltip(type, untranslated_label);
440 auto const sensitive = can_apply(type, item_type, has_clip, has_mask);
441 lpes.push_back({type, category, std::move(label), icon, std::move(tooltip), sensitive});
442 }
443
444 add_lpes(_lpes_popup, symbolic, std::move(lpes));
445}
446
447void LivePathEffectEditor::onAdd(LivePathEffect::EffectType etype)
448{
449 selection_changed_lock = true;
450 Glib::ustring key = converter.get_key(etype);
451 SPLPEItem *fromclone = clonetolpeitem();
452 if (fromclone) {
453 current_lpeitem = fromclone;
454 _current_use = nullptr;
455 if (key == "clone_original") {
456 current_lpeitem->getCurrentLPE()->refresh_widgets = true;
457 selection_changed_lock = false;
458 DocumentUndo::done(getDocument(), _("Create and apply path effect"), INKSCAPE_ICON("dialog-path-effects"));
459 return;
460 }
461 }
462 selection_changed_lock = false;
463 if (current_lpeitem) {
464 LivePathEffect::Effect::createAndApply(key.c_str(), getDocument(), current_lpeitem);
465 current_lpeitem->getCurrentLPE()->refresh_widgets = true;
466 DocumentUndo::done(getDocument(), _("Create and apply path effect"), INKSCAPE_ICON("dialog-path-effects"));
467 }
468}
469
470void
471LivePathEffectEditor::selection_info()
472{
473 auto selection = getSelection();
474 SPItem * selected = nullptr;
475 _LPESelectionInfo.set_visible(false);
476 if (selection && (selected = selection->singleItem()) ) {
477 auto highlight = selected->highlight_color().toRGBA();
478 if (is<SPText>(selected) || is<SPFlowtext>(selected)) {
479 _LPESelectionInfo.set_text(_("Text objects do not support Live Path Effects"));
480 _LPESelectionInfo.set_visible(true);
481
482 Glib::ustring labeltext = _("Convert text to paths");
483 auto const selectbutton = Gtk::make_managed<Gtk::Button>();
484 auto const boxc = Gtk::make_managed<Gtk::Box>();
485 auto const lbl = Gtk::make_managed<Gtk::Label>(labeltext);
486 auto const type = get_shape_image("group", highlight);
487 UI::pack_start(*boxc, *type, false, false);
488 UI::pack_start(*boxc, *lbl, false, false);
489 type->set_margin_start(4);
490 type->set_margin_end(4);
491 selectbutton->set_child(*boxc);
492 selectbutton->signal_clicked().connect([=](){
493 selection->toCurves();
494 });
495 _LPEParentBox.append(*selectbutton);
496
497 Glib::ustring labeltext2 = _("Clone");
498 auto const selectbutton2 = Gtk::make_managed<Gtk::Button>();
499 auto const boxc2 = Gtk::make_managed<Gtk::Box>();
500 auto const lbl2 = Gtk::make_managed<Gtk::Label>(labeltext2);
501 auto const type2 = get_shape_image("clone", highlight);
502 UI::pack_start(*boxc2, *type2, false, false);
503 UI::pack_start(*boxc2, *lbl2, false, false);
504 type2->set_margin_start(4);
505 type2->set_margin_end(4);
506 selectbutton2->set_child(*boxc2);
507 selectbutton2->signal_clicked().connect([=](){
508 selection->clone();
509 });
510 _LPEParentBox.append(*selectbutton2);
511 } else if (!is<SPLPEItem>(selected) && !is<SPUse>(selected)) {
512 _LPESelectionInfo.set_text(_("Select a path, shape, clone or group"));
513 _LPESelectionInfo.set_visible(true);
514 } else {
515 if (selected->getId()) {
516 Glib::ustring labeltext = selected->label() ? selected->label() : selected->getId();
517 auto const boxc = Gtk::make_managed<Gtk::Box>();
518 auto const lbl = Gtk::make_managed<Gtk::Label>(labeltext);
519 lbl->set_ellipsize(Pango::EllipsizeMode::END);
520 auto const type = get_shape_image(selected->typeName(), highlight);
521 UI::pack_start(*boxc, *type, false, false);
522 UI::pack_start(*boxc, *lbl, false, false);
523 _LPECurrentItem.append(*boxc);
524 UI::get_children(_LPECurrentItem).at(0)->set_halign(Gtk::Align::CENTER);
525 _LPESelectionInfo.set_visible(false);
526 }
527 std::vector<std::pair <Glib::ustring, Glib::ustring> > newrootsatellites;
528 for (auto root : selected->rootsatellites) {
529 auto lpeobj = cast<LivePathEffectObject>(selected->document->getObjectById(root.second));
531 if (lpeobj) {
532 lpe = lpeobj->get_lpe();
533 }
534 if (lpe) {
535 const Glib::ustring label = _(converter.get_label(lpe->effectType()).c_str());
536 Glib::ustring labeltext = Glib::ustring::compose(_("Select %1 with %2 LPE"), root.first, label);
537 auto lpeitem = cast<SPLPEItem>(selected->document->getObjectById(root.first));
538 if (lpeitem && lpeitem->getLPEIndex(lpe) != Glib::ustring::npos) {
539 newrootsatellites.emplace_back(root.first, root.second);
540 auto const selectbutton = Gtk::make_managed<Gtk::Button>();
541 auto const boxc = Gtk::make_managed<Gtk::Box>();
542 auto const lbl = Gtk::make_managed<Gtk::Label>(labeltext);
543 auto const type = get_shape_image(selected->typeName(), highlight);
544 UI::pack_start(*boxc, *type, false, false);
545 UI::pack_start(*boxc, *lbl, false, false);
546 type->set_margin_start(4);
547 type->set_margin_end(4);
548 selectbutton->set_child(*boxc);
549 selectbutton->signal_clicked().connect([=](){
550 selection->set(lpeitem);
551 });
552 _LPEParentBox.append(*selectbutton);
553 }
554 }
555 }
556 selected->rootsatellites = newrootsatellites;
557 _LPEParentBox.set_visible(true);
558 _LPECurrentItem.set_visible(true);
559 }
560 } else if (!selection || selection->isEmpty()) {
561 _LPESelectionInfo.set_text(_("Select a path, shape, clone or group"));
562 _LPESelectionInfo.set_visible(true);
563 } else if (selection->size() > 1) {
564 _LPESelectionInfo.set_text(_("Select only one path, shape, clone or group"));
565 _LPESelectionInfo.set_visible(true);
566 }
567}
568
569void
570LivePathEffectEditor::onSelectionChanged(Inkscape::Selection *sel)
571{
572 _reload_menu = true;
573 if ( sel && !sel->isEmpty() ) {
574 SPItem *item = sel->singleItem();
575 if ( item ) {
576 auto lpeitem = cast<SPLPEItem>(item);
577 _current_use = cast<SPUse>(item);
578 if (lpeitem) {
579 lpeitem->update_satellites();
580 current_lpeitem = lpeitem;
581 _LPEAddContainer.set_sensitive(true);
582 effect_list_reload(lpeitem);
583 return;
584 } else if (_current_use) {
585 clear_lpe_list();
586 _LPEAddContainer.set_sensitive(true);
587 selection_info();
588 return;
589 }
590 }
591 }
592 _current_use = nullptr;
593 current_lpeitem = nullptr;
594 _LPEAddContainer.set_sensitive(false);
595 clear_lpe_list();
596 selection_info();
597}
598
599void
600LivePathEffectEditor::move_list(int const origin, int const dest)
601{
602 Inkscape::Selection *sel = getDesktop()->getSelection();
603
604 if ( sel && !sel->isEmpty() ) {
605 SPItem *item = sel->singleItem();
606 if ( item ) {
607 auto lpeitem = cast<SPLPEItem>(item);
608 if ( lpeitem ) {
609 lpeitem->movePathEffect(origin, dest);
610 }
611 }
612 }
613}
614
615void
616LivePathEffectEditor::showParams(LPEExpander const &expanderdata, bool const changed)
617{
618 LivePathEffectObject *lpeobj = expanderdata.second->lpeobject;
619
620 if (lpeobj) {
622 if (lpe) {
623 if (effectwidget && !lpe->refresh_widgets && expanderdata == current_lperef && !changed) {
624 return;
625 }
626
627 if (effectwidget) {
628 current_lperef.first->unset_child(); // deletes effectwidget
629 effectwidget = nullptr;
630 }
631
632 effectwidget = lpe->newWidget();
633
634 if (UI::get_children(*effectwidget).empty()) {
635 auto const label = Gtk::make_managed<Gtk::Label>("", Gtk::Align::START, Gtk::Align::CENTER);
636 label->set_markup(_("<small>Without parameters</small>"));
637 label->set_margin_top(5);
638 label->set_margin_bottom(5);
639 label->set_margin_start(5);
640 effectwidget = label;
641 }
642
643 expanderdata.first->set_child(*effectwidget);
644 align(effectwidget, lpe->spinbutton_width_chars);
645
646 // fixme: add resizing of dialog
647 lpe->refresh_widgets = false;
648 } else {
649 current_lperef = std::make_pair(nullptr, nullptr);
650 }
651 } else {
652 current_lperef = std::make_pair(nullptr, nullptr);
653 }
654}
655
656bool
657LivePathEffectEditor::on_drop(Gtk::Widget &widget,
658 Glib::ValueBase const &value, int pos_target)
659{
660 g_assert(dnd);
661
662 int const pos_source = static_cast<Glib::Value<int> const &>(value).get();
663
664 if (pos_target == pos_source) {
665 return false;
666 }
667
668 if (pos_source > pos_target) {
669 if (widget.has_css_class("after")) {
670 pos_target ++;
671 }
672 } else if (pos_source < pos_target) {
673 if (widget.has_css_class("before")) {
674 pos_target --;
675 }
676 }
677
678 Gtk::Widget *source = LPEListBox.get_row_at_index(pos_source);
679
680 if (source == &widget) {
681 return false;
682 }
683
684 g_object_ref(source->gobj());
685 LPEListBox.remove(*source);
686 LPEListBox.insert(*source, pos_target);
687 g_object_unref(source->gobj());
688
689 move_list(pos_source,pos_target);
690
691 return true;
692}
693
694static void update_before_after_classes(Gtk::Widget &widget, bool const before)
695{
696 if (before) {
697 widget.remove_css_class("after" );
698 widget.add_css_class ("before");
699 } else {
700 widget.remove_css_class("before");
701 widget.add_css_class ("after" );
702 }
703}
704
705/*
706 * First clears the effectlist_store, then appends all effects from the effectlist.
707 */
708void
709LivePathEffectEditor::effect_list_reload(SPLPEItem *lpeitem)
710{
711 clear_lpe_list();
712 _LPEExpanders.clear();
713
714 int counter = -1;
715 Gtk::Expander *LPEExpanderCurrent = nullptr;
716 effectlist = lpeitem->getEffectList();
717 int const total = static_cast<int>(effectlist.size());
718
719 if (total > 1) {
720 auto const target = Gtk::DropTarget::create(Glib::Value<int>::value_type(), Gdk::DragAction::MOVE);
721 _LPEContainer.add_controller(target);
722
723 target->signal_drop().connect([this](Glib::ValueBase const &value, double /*x*/, double const y)
724 {
725 if (!dnd) return false;
726
727 int pos_target = y < 90 ? 0 : UI::get_children(LPEListBox).size() - 1;
728 bool const accepted = on_drop(_LPEContainer, value, pos_target);
729 dnd = false;
730 return accepted;
731 }, false); // before
732
733 target->signal_motion().connect([this](double /*x*/, double const y)
734 {
735 update_before_after_classes(_LPEContainer, y < 90);
736 return Gdk::DragAction::MOVE;
737 }, false); // before
738 }
739
740 Gtk::Button *LPEDrag = nullptr;
741
742 for (auto const &lperef: effectlist) {
743 if (!lperef->lpeobject) continue;
744
745 auto const lpe = lperef->lpeobject->get_lpe();
746 bool const current = lpeitem->getCurrentLPE() == lpe;
747 counter++;
748
749 if (!lpe) continue; // TODO: Should this be a warning or error?
750
751 auto builder = create_builder("dialog-livepatheffect-item.glade");
752 auto LPENameLabel = &get_widget<Gtk::Label> (builder, "LPENameLabel");
753 auto LPEHide = &get_widget<Gtk::Button> (builder, "LPEHide");
754 auto LPEIconImage = &get_widget<Gtk::Image> (builder, "LPEIconImage");
755 auto LPEExpanderBox = &get_widget<Gtk::Box> (builder, "LPEExpanderBox");
756 auto LPEEffect = &get_widget<Gtk::Box> (builder, "LPEEffect");
757 auto LPEExpander = &get_widget<Gtk::Expander>(builder, "LPEExpander");
758 auto LPEOpenExpander = &get_widget<Gtk::Box> (builder, "LPEOpenExpander");
759 auto LPEErase = &get_widget<Gtk::Button> (builder, "LPEErase");
760 LPEDrag = &get_widget<Gtk::Button> (builder, "LPEDrag");
761
762 LPEDrag->set_tooltip_text(_("Drag to change position in path effects stack"));
763 if (current) {
764 LPEExpanderCurrent = LPEExpander;
765 }
766
767 auto const effectype = lpe->effectType();
768 int const id = static_cast<int>(effectype);
769 auto const &untranslated_label = converter.get_label(effectype);
770 auto const &icon = converter.get_icon(effectype);
771 auto const tooltip = get_tooltip(effectype, untranslated_label);
772
773 LPEIconImage->set_from_icon_name(icon);
774
775 bool const visible = g_strcmp0(lpe->getRepr()->attribute("is_visible"), "true") == 0;
776 set_visible_icon(*LPEHide, visible);
777
778 _LPEExpanders.emplace_back(LPEExpander, lperef);
779 LPEListBox.append(*LPEEffect);
780
781 LPEDrag->set_name(Glib::ustring::compose("drag_%1", counter));
782
783 LPEExpanderBox->signal_query_tooltip().connect([=, this](int x, int y, bool kbd, const Glib::RefPtr<Gtk::Tooltip>& tooltipw){
784 return sp_query_custom_tooltip(this, x, y, kbd, tooltipw, id, tooltip, icon);
785 }, false); // before
786
787 // Add actions used by LPEEffectMenuButton
788 add_item_actions(lperef, untranslated_label, get_widget<Gtk::MenuButton>(builder, "LPEEffectMenuButton"),
789 counter == 0, counter == total - 1);
790
791 if (total > 1) {
792 auto const source = Gtk::DragSource::create();
793 source->set_actions(Gdk::DragAction::MOVE);
794 LPEDrag->add_controller(source);
795
796 // TODO: GTK4: Figure out how to replicate previous 50% transparency. CSS or Paintable?
797 LPEEffect->add_css_class("drag-icon");
798 source->set_icon(Gtk::WidgetPaintable::create(*LPEEffect), 0, 0);
799 LPEEffect->remove_css_class("drag-icon");
800
801 source->signal_drag_begin().connect([this](Glib::RefPtr<Gdk::Drag> const &){
802 dnd = true;
803 });
804
805 auto row = dynamic_cast<Gtk::ListBoxRow *>(LPEEffect->get_parent());
806 g_assert(row);
807
808 source->signal_prepare().connect([=](double, double)
809 {
810 Glib::Value<int> value;
811 value.init(G_TYPE_INT);
812 value.set(row->get_index());
813 return Gdk::ContentProvider::create(value);
814 }, false); // before
815
816 source->signal_drag_end().connect([this](Glib::RefPtr<Gdk::Drag> const &, bool)
817 {
818 dnd = false;
819 });
820
821 auto const target = Gtk::DropTarget::create(Glib::Value<int>::value_type(), Gdk::DragAction::MOVE);
822 row->add_controller(target);
823
824 target->signal_drop().connect([=, this](Glib::ValueBase const &value, double, double)
825 {
826 if (!dnd) return false;
827
828 bool const accepted = on_drop(*row, value, row->get_index());
829 dnd = false;
830 return accepted;
831 }, false); // before
832
833 target->signal_motion().connect([=](double /*x*/, double const y)
834 {
835 int const half = row->get_allocated_height() / 2;
836 update_before_after_classes(*row, y < half);
837 return Gdk::DragAction::MOVE;
838 }, false); // before
839 }
840
841 LPEEffect->set_name("LPEEffectItem");
842 LPENameLabel->set_label(g_dpgettext2(nullptr, "path effect",
843 lperef->lpeobject->get_lpe()->getName().c_str()));
844
845 LPEExpander->property_expanded().signal_changed().connect(sigc::bind(
846 sigc::mem_fun(*this, &LivePathEffectEditor::expanded_notify), LPEExpander));
847
848 auto const expander_click = Gtk::GestureClick::create();
849 expander_click->set_button(1); // left
850 expander_click->signal_pressed().connect([this, LPEExpander, &expander_click = *expander_click](auto &&...) {
851 LPEExpander->set_expanded(!LPEExpander->get_expanded());
852 expander_click.set_state(Gtk::EventSequenceState::CLAIMED);
853 });
854 LPEOpenExpander->add_controller(expander_click);
855
856 LPEHide->signal_clicked().connect(sigc::bind(sigc::mem_fun(*this, &LivePathEffectEditor::toggleVisible), lpe, LPEHide));
857 LPEErase->signal_clicked().connect([=, this](){ removeEffect(LPEExpander);});
858
859 auto const drag_click = Gtk::GestureClick::create();
860 drag_click->signal_pressed().connect([this](int, double x, double y) {
861 dndx = x;
862 dndy = y;
863 });
864 LPEDrag->add_controller(drag_click);
865
866 if (total > 1) {
867 LPEDrag->set_cursor("grab");
868 }
869 }
870
871 if (counter == 0 && LPEDrag) {
872 LPEDrag->set_visible(false);
873 LPEDrag->set_tooltip_text("");
874 }
875
876 if (LPEExpanderCurrent) {
877 _LPESelectionInfo.set_visible(false);
878 LPEExpanderCurrent->set_expanded(true);
879 if (auto const current_window = dynamic_cast<Gtk::Window *>(LPEExpanderCurrent->get_root())) {
880 current_window->set_focus(*LPEExpanderCurrent);
881 }
882 }
883
884 selection_info();
885}
886
887void LivePathEffectEditor::expanded_notify(Gtk::Expander *expander) {
888 if (updating) {
889 return;
890 }
891
892 if (!dnd) {
893 _freezeexpander = false;
894 }
895
896 if (_freezeexpander) {
897 _freezeexpander = false;
898 return;
899 }
900
901 if (dnd) {
902 _freezeexpander = true;
903 expander->set_expanded(!expander->get_expanded());
904 return;
905 };
906
907 updating = true;
908
909 if (expander->get_expanded()) {
910 for (auto const &w : _LPEExpanders) {
911 if (w.first == expander) {
912 w.first->set_expanded(true);
913 w.first->get_parent()->get_parent()->get_parent()->set_name("currentlpe");
914 current_lperef = w;
915 current_lpeitem->setCurrentPathEffect(w.second);
916 showParams(w, true);
917 } else {
918 w.first->set_expanded(false);
919 w.first->get_parent()->get_parent()->get_parent()->set_name("unactive_lpe");
920 }
921 }
922 }
923
924 auto selection = SP_ACTIVE_DESKTOP->getSelection();
925 if (selection && current_lpeitem && !selection->isEmpty()) {
926 selection_changed_lock = true;
927 selection->clear();
928 selection->add(current_lpeitem);
930 selection_changed_lock = false;
931 }
932
933 updating = false;
934}
935
936bool
937LivePathEffectEditor::lpeFlatten(PathEffectSharedPtr const &lperef)
938{
939 current_lpeitem->setCurrentPathEffect(lperef);
940 current_lpeitem = current_lpeitem->flattenCurrentPathEffect();
941 _current_use = nullptr;
942 auto selection = getSelection();
943 if (selection && selection->isEmpty() ) {
944 selection->add(current_lpeitem);
945 }
946 DocumentUndo::done(getDocument(), _("Flatten path effect(s)"), INKSCAPE_ICON("dialog-path-effects"));
947 return false;
948}
949
950void
951LivePathEffectEditor::removeEffect(Gtk::Expander * expander) {
952 bool reload = current_lperef.first != expander;
953 auto current_lperef_tmp = current_lperef;
954 for (auto const &w : _LPEExpanders) {
955 if (w.first == expander) {
956 current_lpeitem->setCurrentPathEffect(w.second);
957 current_lpeitem = current_lpeitem->removeCurrentPathEffect(false);
958 _current_use = nullptr;
959 }
960 }
961 // Check if current_lpeitem detached during clean up
962 if (current_lpeitem && current_lpeitem->getParentGroup() != nullptr) {
963 if (reload) {
964 current_lpeitem->setCurrentPathEffect(current_lperef_tmp.second);
965 }
966 effect_list_reload(current_lpeitem);
967 }
968 DocumentUndo::done(getDocument(), _("Remove path effect"), INKSCAPE_ICON("dialog-path-effects"));
969}
970
971/*
972 * Clears the effectlist
973 */
974void
975LivePathEffectEditor::clear_lpe_list()
976{
977 UI::remove_all_children( LPEListBox );
978 UI::remove_all_children(_LPEParentBox );
979 UI::remove_all_children(_LPECurrentItem);
980
981 _LPEExpanders.clear();
982 current_lperef = std::make_pair(nullptr, nullptr);
983}
984
985SPLPEItem * LivePathEffectEditor::clonetolpeitem()
986{
987 auto selection = getSelection();
988 if (!(selection && !selection->isEmpty())) {
989 return nullptr;
990 }
991
992 auto use = cast<SPUse>(selection->singleItem());
993 if (!use) {
994 return nullptr;
995 }
996
997 DocumentUndo::ScopedInsensitive tmp(getDocument());
998 // item is a clone. do not show effectlist dialog.
999 // convert to path, apply CLONE_ORIGINAL LPE, link it to the cloned path
1000
1001 // test whether linked object is supported by the CLONE_ORIGINAL LPE
1002 SPItem *orig = use->trueOriginal();
1003 if (!(is<SPShape>(orig) || is<SPGroup>(orig) || is<SPText>(orig))) {
1004 return nullptr;
1005 }
1006
1007 // select original
1008 selection->set(orig);
1009
1010 // delete clone but remember its id and transform
1011 auto id_copy = Util::to_opt(use->getAttribute("id"));
1012 auto transform_use = use->get_root_transform();
1013 use->deleteObject(false);
1014 use = nullptr;
1015
1016 // run sp_selection_clone_original_path_lpe
1017 selection->cloneOriginalPathLPE(true, true, true);
1018
1019 SPItem *new_item = selection->singleItem();
1020 // Check that the cloning was successful. We don't want to change the ID of the original referenced path!
1021 if (new_item && (new_item != orig)) {
1022 new_item->setAttribute("id", Util::to_cstr(id_copy));
1023 if (transform_use != Geom::identity()) {
1024 // update use real transform
1025 new_item->transform *= transform_use;
1026 new_item->doWriteTransform(new_item->transform);
1027 new_item->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
1028 }
1029 new_item->setAttribute("class", "fromclone");
1030 }
1031
1032 auto *lpeitem = cast<SPLPEItem>(new_item);
1033 if (!lpeitem) {
1034 return nullptr;
1035 }
1036
1037 sp_lpe_item_update_patheffect(lpeitem, true, true);
1038 return lpeitem;
1039}
1040
1041/*
1042* * Actions
1043 */
1044
1045static constexpr auto item_action_group_name = "lpe-item";
1046static const auto item_action_group_quark = Glib::Quark{item_action_group_name};
1047
1048template <typename Method, typename ...Args>
1049static void add_action(Glib::RefPtr<Gio::SimpleActionGroup> const &group,
1050 Glib::ustring const &name, bool const enable,
1051 LivePathEffectEditor &self, Method const method, Args ...args)
1052{
1053 auto slot = sigc::hide_return(sigc::bind(sigc::mem_fun(self, method), std::move(args)...));
1054 auto const action = group->add_action(name, std::move(slot));
1055 action->set_enabled(enable);
1056}
1057
1058void LivePathEffectEditor::add_item_actions(PathEffectSharedPtr const &lperef,
1059 Glib::ustring const &untranslated_label,
1060 Gtk::Widget &item,
1061 bool const is_first, bool const is_last)
1062{
1063 using Self = LivePathEffectEditor;
1064 auto const has_defs = lperef->lpeobject->get_lpe()->hasDefaultParameters();
1065 auto const has_fav = sp_has_fav(untranslated_label);
1066 auto group = Gio::SimpleActionGroup::create();
1067 add_action(group, "duplicate" , true , *this , &Self::do_item_action_undoable, lperef,
1068 &SPLPEItem::duplicateCurrentPathEffect, _("Duplicate path effect") );
1069 add_action(group, "move-up" , !is_first, *this , &Self::do_item_action_undoable, lperef,
1070 &SPLPEItem::upCurrentPathEffect , _("Move path effect up") );
1071 add_action(group, "move-down" , !is_last , *this , &Self::do_item_action_undoable, lperef,
1072 &SPLPEItem::downCurrentPathEffect , _("Move path effect down") );
1073 add_action(group, "flatten" , true , *this , &Self::lpeFlatten , lperef);
1074 add_action(group, "set-def" , !has_defs, *this , &Self::do_item_action_defaults, lperef,
1076 add_action(group, "forget-def", has_defs, *this , &Self::do_item_action_defaults, lperef,
1078 add_action(group, "set-fav" , !has_fav , *this , &Self::do_item_action_favorite, lperef,
1079 untranslated_label , std::ref(item) , true );
1080 add_action(group, "unset-fav" , has_fav , *this , &Self::do_item_action_favorite, lperef,
1081 untranslated_label , std::ref(item) , false );
1082 item.set_data(item_action_group_quark, &*group);
1083 item.insert_action_group(item_action_group_name, std::move(group));
1084}
1085
1086void LivePathEffectEditor::enable_item_action(Gtk::Widget &item,
1087 Glib::ustring const &action_name, bool const enabled)
1088{
1089 auto const data = item.get_data(item_action_group_quark);
1090 g_assert(data);
1091 auto &simple_group = *static_cast<Gio::SimpleActionGroup *>(data);
1092 auto const action = simple_group.lookup_action(action_name);
1093 auto const simple_action = std::dynamic_pointer_cast<Gio::SimpleAction>(action);
1094 g_assert(simple_action);
1095 simple_action->set_enabled(enabled);
1096}
1097
1098void LivePathEffectEditor::enable_fav_actions(Gtk::Widget &item, bool const has_fav)
1099{
1100 enable_item_action(item, "set-fav" , !has_fav);
1101 enable_item_action(item, "unset-fav", has_fav);
1102}
1103
1104void LivePathEffectEditor::do_item_action_undoable(PathEffectSharedPtr const &lperef,
1105 void (SPLPEItem::* const method)(),
1106 Glib::ustring const &description)
1107{
1109 (current_lpeitem->*method)();
1111 DocumentUndo::done(getDocument(), description, INKSCAPE_ICON("dialog-path-effects"));
1112}
1113
1115 void (LivePathEffect::Effect::* const method)())
1116{
1117 (lperef->lpeobject->get_lpe()->*method)();
1119}
1120
1122 Glib::ustring const &untranslated_label,
1123 Gtk::Widget &item, bool const has_fav)
1124{
1125 if (has_fav) sp_add_fav(untranslated_label);
1126 else sp_remove_fav(untranslated_label);
1127
1128 enable_fav_actions(item, has_fav);
1129
1130 _reload_menu = true;
1131 _item_type = ""; // here we force reload even with the same tipe item selected
1132}
1134{
1136 _lpes_popup.get_entry().grab_focus();
1137 _lpes_popup.get_entry().queue_draw(); // force redraw to fix delay in hover style
1138}
1139
1140} // namespace Inkscape::UI::Dialog
1141
1142/*
1143 Local Variables:
1144 mode:c++
1145 c-file-style:"stroustrup"
1146 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1147 indent-tabs-mode:nil
1148 fill-column:99
1149 End:
1150*/
1151// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
Point origin
Definition aa.cpp:227
const char * action_name
Gtk builder utilities.
Geom::IntRect visible
Definition canvas.cpp:154
uint32_t toRGBA(double opacity=1.0) const
Return an sRGB conversion of the color in RGBA int32 format.
Definition color.cpp:117
RAII-style mechanism for creating a temporary undo-insensitive context.
static void done(SPDocument *document, Glib::ustring const &event_description, Glib::ustring const &undo_icon, unsigned int object_modified_tag=0)
virtual void doOnVisibilityToggled(SPLPEItem const *lpeitem)
Definition effect.cpp:1473
virtual Gtk::Widget * newWidget()
This creates a managed widget.
Definition effect.cpp:1790
static void createAndApply(const char *name, SPDocument *doc, SPItem *item)
Definition effect.cpp:1118
Inkscape::XML::Node * getRepr()
Definition effect.cpp:1934
void setDefaultParameters()
Set this LPE defaults.
Definition effect.cpp:1829
EffectType effectType() const
Definition effect.cpp:1182
void resetDefaultParameters()
Reset this LPE defaults.
Definition effect.cpp:1889
bool isEmpty()
Returns true if no items are selected.
SPItem * singleItem()
Returns a single selected item.
Preference storage class.
Definition preferences.h:66
bool getBool(Glib::ustring const &pref_path, bool def=false)
Retrieve a Boolean value.
Glib::ustring getString(Glib::ustring const &pref_path, Glib::ustring const &def="")
Retrieve an UTF-8 string.
static Preferences * get()
Access the singleton Preferences object.
void setString(Glib::ustring const &pref_path, Glib::ustring const &value)
Set an UTF-8 string value.
The set of selected SPObjects for a given document and layer model.
Definition selection.h:80
DialogBase is the base class for the dialog system.
Definition dialog-base.h:42
SPDocument * getDocument() const
Definition dialog-base.h:83
void do_item_action_favorite(PathEffectSharedPtr const &lpreref, Glib::ustring const &untranslated_label, Gtk::Widget &item, bool has_fav)
bool can_apply(LivePathEffect::EffectType, Glib::ustring const &item_type, bool has_clip, bool has_mask)
void onAdd(Inkscape::LivePathEffect::EffectType etype)
void do_item_action_defaults(PathEffectSharedPtr const &lpreref, void(LivePathEffect::Effect::*const method)())
void onSelectionChanged(Inkscape::Selection *selection)
void enable_fav_actions(Gtk::Widget &item, bool has_fav)
void selectionChanged(Inkscape::Selection *selection) final
Inkscape::UI::Widget::CompletionPopup _lpes_popup
void add_lpes(UI::Widget::CompletionPopup &, bool symbolic, std::vector< LPEMetadata > &&lpes)
void toggleVisible(Inkscape::LivePathEffect::Effect *lpe, Gtk::Button *visbutton)
void selectionModified(Inkscape::Selection *selection, unsigned flags) final
std::pair< Gtk::Expander *, PathEffectSharedPtr > LPEExpander
const LivePathEffect::EnumEffectDataConverter< LivePathEffect::EffectType > & converter
void showParams(LPEExpander const &expanderdata, bool changed)
sigc::signal< void(int)> & on_match_selected()
sigc::signal< void()> & on_button_press()
void remove_all()
Remove all items.
void setAttribute(Util::const_char_ptr key, Util::const_char_ptr value)
Change an attribute of this node.
Definition node.cpp:25
virtual char const * attribute(char const *key) const =0
Get the string representation of a node's attribute.
Inkscape::LivePathEffect::Effect * get_lpe()
Definition lpeobject.h:51
SPObject * getObjectById(std::string const &id) const
Base class for visual SVG elements.
Definition sp-item.h:109
std::vector< std::pair< Glib::ustring, Glib::ustring > > rootsatellites
Definition sp-item.h:153
virtual Inkscape::Colors::Color highlight_color() const
Definition sp-item.cpp:284
Geom::Affine transform
Definition sp-item.h:138
virtual const char * typeName() const
The item's type name, not node tag name.
Definition sp-item.cpp:1192
void doWriteTransform(Geom::Affine const &transform, Geom::Affine const *adv=nullptr, bool compensate=true)
Set a new transform on an object.
Definition sp-item.cpp:1674
void duplicateCurrentPathEffect()
void downCurrentPathEffect()
Inkscape::LivePathEffect::Effect * getCurrentLPE()
void upCurrentPathEffect()
PathEffectList getEffectList()
bool setCurrentPathEffect(PathEffectSharedPtr const &lperef)
char const * label() const
Gets the author-visible label property for the object or a default if no label is defined.
void setAttribute(Inkscape::Util::const_char_ptr key, Inkscape::Util::const_char_ptr value)
SPDocument * document
Definition sp-object.h:188
char const * getId() const
Returns the objects current ID string.
void requestDisplayUpdate(unsigned int flags)
Queues an deferred update of this object's display.
const double w
Definition conic-4.cpp:19
RootCluster root
Utilities to more easily use Gtk::EventController & subclasses like Gesture.
void sp_clear_custom_tooltip()
bool sp_query_custom_tooltip(Gtk::Widget *widg, int x, int y, bool keyboard_tooltip, const Glib::RefPtr< Gtk::Tooltip > &tooltipw, gint id, Glib::ustring tooltip, Glib::ustring icon, Gtk::IconSize iconsize, int delaytime)
Geom::Point orig
static char const *const current
Definition dir-util.cpp:71
Icon Loader.
Macro for icon names used in Inkscape.
std::unique_ptr< Magick::Image > image
SPItem * item
A dialog for Live Path Effects (LPE)
Glib::ustring label
Affine identity()
Create an identity matrix.
Definition affine.h:210
Definition desktop.h:50
Dialog code.
Definition desktop.h:117
const Glib::ustring & get_category_name(EffectCategory category)
static bool sp_has_fav(Glib::ustring const &effect)
static constexpr auto item_action_group_name
static void sp_add_fav(Glib::ustring const &effect)
static constexpr auto favs_path
static void sp_remove_fav(Glib::ustring effect)
static void set_visible_icon(Gtk::Button &button, bool const visible)
void align(Gtk::Widget *top, int const spinbutton_width_chars)
static void add_action(Glib::RefPtr< Gio::SimpleActionGroup > const &group, Glib::ustring const &name, bool const enable, LivePathEffectEditor &self, Method const method, Args ...args)
static const auto item_action_group_quark
static void update_before_after_classes(Gtk::Widget &widget, bool const before)
bool sp_set_experimental(bool &_experimental)
void sp_update_helperpath(SPDesktop *desktop)
void remove_all_children(Widget &widget)
For each child in get_children(widget), call widget.remove(*child). May not cause delete child!
Definition util.h:88
std::vector< Gtk::Widget * > get_children(Gtk::Widget &widget)
Get a vector of the widgetʼs children, from get_first_child() through each get_next_sibling().
Definition util.cpp:156
W & get_widget(const Glib::RefPtr< Gtk::Builder > &builder, const char *id)
Gtk::Image * get_shape_image(Glib::ustring const &shape_type, std::uint32_t const rgba_color, Gtk::IconSize const icon_size)
As get_shape_icon(), but returns a ready-made, managed Image having that icon name & CSS class.
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
Glib::RefPtr< Gtk::Builder > create_builder(const char *filename)
auto to_opt(char const *s)
Definition optstr.h:21
auto to_cstr(std::optional< std::string > const &s)
Definition optstr.h:26
Helper class to stream background task notifications as a series of messages.
static void append(std::vector< T > &target, std::vector< T > &&source)
New node tool with support for multiple path editing.
static cairo_user_data_key_t key
static gint counter
Definition box3d.cpp:39
Helpers for using Gtk::Boxes, encapsulating large changes between GTK3 & GTK4.
Ocnode * child[8]
Definition quantize.cpp:33
Inkscape::IO::Resource - simple resource API.
TODO: insert short description here.
void sp_lpe_item_update_patheffect(SPLPEItem *lpeitem, bool wholetree, bool write, bool with_satellites)
Calls any registered handlers for the update_patheffect action.
Base class for live path effect items.
std::shared_ptr< Inkscape::LivePathEffect::LPEObjectReference > PathEffectSharedPtr
Definition sp-lpe-item.h:45
TODO: insert short description here.
static const Point data[]
Glib::ustring name
Definition toolbars.cpp:55
Glib::RefPtr< Gtk::Builder > builder