// SPDX-License-Identifier: GPL-2.0-or-later /** @file * @brief A dialog for Live Path Effects (LPE) */ /* Authors: * Jabiertxof * Adam Belis (UX/Design) * * Released under GNU GPL v2+, read the file 'COPYING' for more information. */ #include "livepatheffect-editor.h" #include "live_effects/effect-enum.h" #include "livepatheffect-add.h" #include "live_effects/effect.h" #include "live_effects/lpeobject-reference.h" #include "live_effects/lpeobject.h" #include "object/sp-lpe-item.h" #include "svg/svg.h" #include "ui/icon-names.h" #include "ui/icon-loader.h" #include "ui/builder-utils.h" #include "io/resource.h" #include "object/sp-use.h" #include "object/sp-shape.h" #include "object/sp-path.h" #include "object/sp-flowtext.h" #include "object/sp-tspan.h" #include "object/sp-item-group.h" #include "object/sp-text.h" #include "ui/tools/node-tool.h" #include "ui/widget/custom-tooltip.h" #include "util/optstr.h" #include #include namespace Inkscape { namespace UI { bool sp_can_apply_lpeffect(SPLPEItem* item, LivePathEffect::EffectType etype) { if (!item) return false; auto shape = cast(item); auto path = cast(item); auto group = cast(item); Glib::ustring item_type; if (group) { item_type = "group"; } else if (path) { item_type = "path"; } else if (shape) { item_type = "shape"; } bool has_clip = item->getClipObject() != nullptr; bool has_mask = item->getMaskObject() != nullptr; bool applicable = true; if (!has_clip && etype == LivePathEffect::POWERCLIP) { applicable = false; } if (!has_mask && etype == LivePathEffect::POWERMASK) { applicable = false; } if (item_type == "group" && !Inkscape::LivePathEffect::LPETypeConverter.get_on_group(etype)) { applicable = false; } else if (item_type == "shape" && !Inkscape::LivePathEffect::LPETypeConverter.get_on_shape(etype)) { applicable = false; } else if (item_type == "path" && !Inkscape::LivePathEffect::LPETypeConverter.get_on_path(etype)) { applicable = false; } return applicable; } void sp_apply_lpeffect(SPDesktop* desktop, SPLPEItem* item, LivePathEffect::EffectType etype) { if (!sp_can_apply_lpeffect(item, etype)) return; Glib::ustring key = Inkscape::LivePathEffect::LPETypeConverter.get_key(etype); LivePathEffect::Effect::createAndApply(key.c_str(), item->document, item); item->getCurrentLPE()->refresh_widgets = true; DocumentUndo::done(item->document, _("Create and apply path effect"), INKSCAPE_ICON("dialog-path-effects")); if (desktop) { // this is rotten - UI LPE knots refresh // force selection change desktop->getSelection()->clear(); desktop->getSelection()->add(item); Inkscape::UI::Tools::sp_update_helperpath(desktop); } } namespace Dialog { /*#################### * Callback functions */ bool sp_has_fav(Glib::ustring effect) { Inkscape::Preferences *prefs = Inkscape::Preferences::get(); Glib::ustring favlist = prefs->getString("/dialogs/livepatheffect/favs"); size_t pos = favlist.find(effect); if (pos != Glib::ustring::npos) { return true; } return false; } void sp_add_fav(Glib::ustring effect) { Inkscape::Preferences *prefs = Inkscape::Preferences::get(); Glib::ustring favlist = prefs->getString("/dialogs/livepatheffect/favs"); if (!sp_has_fav(effect)) { prefs->setString("/dialogs/livepatheffect/favs", favlist + effect + ";"); } } void sp_remove_fav(Glib::ustring effect) { if (sp_has_fav(effect)) { Inkscape::Preferences *prefs = Inkscape::Preferences::get(); Glib::ustring favlist = prefs->getString("/dialogs/livepatheffect/favs"); effect += ";"; size_t pos = favlist.find(effect); if (pos != Glib::ustring::npos) { favlist.erase(pos, effect.length()); prefs->setString("/dialogs/livepatheffect/favs", favlist); } } } void sp_toggle_fav(Glib::ustring effect, Gtk::MenuItem *LPEtoggleFavorite) { Inkscape::Preferences *prefs = Inkscape::Preferences::get(); Glib::ustring favlist = prefs->getString("/dialogs/livepatheffect/favs"); if (sp_has_fav(effect)) { sp_remove_fav(effect); LPEtoggleFavorite->set_label(_("Set Favorite")); } else { sp_add_fav(effect); LPEtoggleFavorite->set_label(_("Unset Favorite")); } } void LivePathEffectEditor::selectionChanged(Inkscape::Selection * selection) { if (selection_changed_lock) { return; } onSelectionChanged(selection); clearMenu(); } void LivePathEffectEditor::selectionModified(Inkscape::Selection * selection, guint flags) { current_lpeitem = cast(selection->singleItem()); if (!selection_changed_lock && current_lpeitem && effectlist != current_lpeitem->getEffectList()) { onSelectionChanged(selection); } else if (current_lpeitem && current_lperef.first) { showParams(current_lperef, false); } clearMenu(); } /** * Constructor */ LivePathEffectEditor::LivePathEffectEditor() : DialogBase("/dialogs/livepatheffect", "LivePathEffect"), _builder(create_builder("dialog-livepatheffect.glade")), LPEListBox(get_widget(_builder, "LPEListBox")), _LPEContainer(get_widget(_builder, "LPEContainer")), _LPEAddContainer(get_widget(_builder, "LPEAddContainer")), _LPEParentBox(get_widget(_builder, "LPEParentBox")), _LPECurrentItem(get_widget(_builder, "LPECurrentItem")), _LPESelectionInfo(get_widget(_builder, "LPESelectionInfo")), _LPEGallery(get_widget(_builder, "LPEGallery")), _showgallery_observer(Preferences::PreferencesObserver::create( "/dialogs/livepatheffect/showgallery", sigc::mem_fun(*this, &LivePathEffectEditor::on_showgallery_notify))), converter(Inkscape::LivePathEffect::LPETypeConverter) { _LPEGallery.signal_clicked().connect(sigc::mem_fun(*this, &LivePathEffectEditor::onAddGallery)); _showgallery_observer->call(); // Set initial visibility per Preference (widget is :no-show-all) Glib::RefPtr LPECompletionList = Glib::RefPtr::cast_dynamic(_builder->get_object("LPECompletionList")); _LPEContainer.signal_map().connect(sigc::mem_fun(*this, &LivePathEffectEditor::map_handler) ); _LPEContainer.signal_button_press_event().connect([=](GdkEventButton* const evt){dnd = false; /*hack to fix dnd freze expander*/ return false; }, false); setMenu(); add(_LPEContainer); selection_info(); _lpes_popup.get_entry().set_placeholder_text(_("Add Live Path Effect")); _lpes_popup.on_match_selected().connect([=](int id){ onAdd((LivePathEffect::EffectType)id); }); _lpes_popup.on_button_press().connect([=](){ setMenu(); }); _lpes_popup.on_focus().connect([=](){ setMenu(); return true; }); _LPEAddContainer.pack_start(_lpes_popup); show_all(); } LivePathEffectEditor::~LivePathEffectEditor() { sp_clear_custom_tooltip(); } bool separator_func(const Glib::RefPtr& model, const Gtk::TreeModel::iterator& iter) { Gtk::TreeModel::Row row = *iter; bool *separator; row->get_value(3, separator); return separator; } bool LivePathEffectEditor::is_appliable(LivePathEffect::EffectType etype, Glib::ustring item_type, bool has_clip, bool has_mask) { bool appliable = true; if (!has_clip && etype == LivePathEffect::POWERCLIP) { appliable = false; } if (!has_mask && etype == LivePathEffect::POWERMASK) { appliable = false; } if (item_type == "group" && !converter.get_on_group(etype)) { appliable = false; } else if (item_type == "shape" && !converter.get_on_shape(etype)) { appliable = false; } else if (item_type == "path" && !converter.get_on_path(etype)) { appliable = false; } return appliable; } void align(Gtk::Widget* top, gint spinbutton_width_chars) { auto box = dynamic_cast(top); if (!box) return; box->set_spacing(2); // traverse container, locate n-th child in each row auto for_child_n = [=](int child_index, const std::function& action) { for (auto child : box->get_children()) { auto container = dynamic_cast(child); if (!container) continue; container->set_spacing(2); const auto& children = container->get_children(); if (children.size() > child_index) { action(children[child_index]); } } }; // column 0 - labels int max_width = 0; for_child_n(0, [&](Gtk::Widget* child){ if (auto label = dynamic_cast(child)) { label->set_xalign(0); // left-align int label_width = 0, dummy = 0; label->get_preferred_width(dummy, label_width); if (label_width > max_width) { max_width = label_width; } } }); // align for_child_n(0, [=](Gtk::Widget* child) { if (auto label = dynamic_cast(child)) { label->set_size_request(max_width); } }); // column 1 - align spin buttons, if any int button_width = 0; for_child_n(1, [&](Gtk::Widget* child) { if (auto spin = dynamic_cast(child)) { // selected spinbutton size by each LPE default 7 spin->set_width_chars(spinbutton_width_chars); int dummy = 0; spin->get_preferred_width(dummy, button_width); } }); // set min size for comboboxes, if any int combo_size = button_width > 0 ? button_width : 50; // match with spinbuttons, or just min of 50px for_child_n(1, [=](Gtk::Widget* child) { if (auto combo = dynamic_cast(child)) { combo->set_size_request(combo_size); } }); } void LivePathEffectEditor::clearMenu() { sp_clear_custom_tooltip(); _reload_menu = true; } void LivePathEffectEditor::toggleVisible(Inkscape::LivePathEffect::Effect *lpe , Gtk::EventBox *visbutton) { auto *visimage = dynamic_cast(dynamic_cast(visbutton->get_children()[0])->get_image()); bool hide = false; if (!g_strcmp0(lpe->getRepr()->attribute("is_visible"),"true")) { visimage->set_from_icon_name("object-hidden-symbolic", Gtk::IconSize(Gtk::ICON_SIZE_SMALL_TOOLBAR)); lpe->getRepr()->setAttribute("is_visible", "false"); hide = true; } else { visimage->set_from_icon_name("object-visible-symbolic", Gtk::IconSize(Gtk::ICON_SIZE_SMALL_TOOLBAR)); lpe->getRepr()->setAttribute("is_visible", "true"); } lpe->doOnVisibilityToggled(current_lpeitem); DocumentUndo::done(getDocument(), hide ? _("Deactivate path effect") : _("Activate path effect"), INKSCAPE_ICON("dialog-path-effects")); } const Glib::ustring& get_category_name(Inkscape::LivePathEffect::LPECategory category) { static const std::map category_names = { { Inkscape::LivePathEffect::LPECategory::Favorites, _("Favorites") }, { Inkscape::LivePathEffect::LPECategory::EditTools, _("Edit/Tools") }, { Inkscape::LivePathEffect::LPECategory::Distort, _("Distort") }, { Inkscape::LivePathEffect::LPECategory::Generate, _("Generate") }, { Inkscape::LivePathEffect::LPECategory::Convert, _("Convert") }, { Inkscape::LivePathEffect::LPECategory::Experimental, _("Experimental") }, }; return category_names.at(category); } struct LPEMetadata { Inkscape::LivePathEffect::LPECategory category; Glib::ustring icon_name; Glib::ustring tooltip; bool sensitive; }; static std::map g_lpes; // populate popup with lpes and completion list for a search box void LivePathEffectEditor::add_lpes(Inkscape::UI::Widget::CompletionPopup& popup, bool symbolic) { auto& menu = popup.get_menu(); struct LPE { Inkscape::LivePathEffect::EffectType type; Glib::ustring label; Inkscape::LivePathEffect::LPECategory category; Glib::ustring icon_name; Glib::ustring tooltip; bool sensitive; }; std::vector lpes; lpes.reserve(g_lpes.size()); for (auto&& lpe : g_lpes) { lpes.push_back({ lpe.first, g_dpgettext2(0, "path effect", converter.get_label(lpe.first).c_str()), lpe.second.category, lpe.second.icon_name, lpe.second.tooltip, lpe.second.sensitive }); } std::sort(begin(lpes), end(lpes), [=](auto&& a, auto&& b) { if (a.category != b.category) { return a.category < b.category; } return a.label < b.label; }); popup.clear_completion_list(); // 2-column menu for (auto w:menu.get_children()) { menu.remove(*w); } Inkscape::UI::ColumnMenuBuilder builder(menu, 3, Gtk::ICON_SIZE_LARGE_TOOLBAR); for (auto& lpe : lpes) { // build popup menu auto type = lpe.type; auto *menuitem = builder.add_item(lpe.label, lpe.category, lpe.tooltip, lpe.icon_name, lpe.sensitive, true, [=](){ onAdd((LivePathEffect::EffectType)type); }); gint id = (gint)type; menuitem->property_has_tooltip() = true; menuitem->signal_query_tooltip().connect([=](int x, int y, bool kbd, const Glib::RefPtr& tooltipw){ return sp_query_custom_tooltip(x, y, kbd, tooltipw, id, lpe.tooltip, lpe.icon_name); }); if (builder.new_section()) { builder.set_section(get_category_name(lpe.category)); } // build completion list if (lpe.sensitive) { popup.add_to_completion_list(static_cast(lpe.type), lpe.label, lpe.icon_name + (symbolic ? "-symbolic" : "")); } } if (symbolic) { menu.get_style_context()->add_class("symbolic"); } } void LivePathEffectEditor::setMenu() { if (!_reload_menu) { return; } auto shape = cast(current_lpeitem); auto path = cast(current_lpeitem); auto group = cast(current_lpeitem); bool has_clip = current_lpeitem && (current_lpeitem->getClipObject() != nullptr); bool has_mask = current_lpeitem && (current_lpeitem->getMaskObject() != nullptr); Glib::ustring item_type = ""; if (group) { item_type = "group"; } else if (path) { item_type = "path"; } else if (shape) { item_type = "shape"; } if (_item_type != item_type || has_clip != _has_clip || has_mask != _has_mask) { _item_type = item_type; _has_clip = has_clip; _has_mask = has_mask; g_lpes.clear(); Inkscape::Preferences *prefs = Inkscape::Preferences::get(); bool experimental = prefs->getBool("/dialogs/livepatheffect/showexperimental", false); std::map *> > lpesorted; for (int i = 0; i < static_cast(converter._length); ++i) { const LivePathEffect::EnumEffectData *data = &converter.data(i); const Glib::ustring label = _(converter.get_label(data->id).c_str()); const Glib::ustring untranslated_label = converter.get_label(data->id); Glib::ustring name = label; if (untranslated_label != label) { name =+ "\n" + untranslated_label + ""; } Inkscape::LivePathEffect::LPECategory category = converter.get_category(data->id); if (sp_has_fav(untranslated_label)) { //category = 0; category = Inkscape::LivePathEffect::LPECategory::Favorites; } if (!experimental && category == Inkscape::LivePathEffect::LPECategory::Experimental) { continue; } lpesorted[category][name] = data; } for (auto e : lpesorted) { for (auto e2 : e.second) { const Glib::ustring label = _(converter.get_label(e2.second->id).c_str()); const Glib::ustring untranslated_label = converter.get_label(e2.second->id); Glib::ustring tooltip = _(converter.get_description(e2.second->id).c_str()); if (untranslated_label != label) { tooltip = "[" + untranslated_label + "] " + _(converter.get_description(e2.second->id).c_str()); } Glib::ustring name = label; Glib::ustring icon = converter.get_icon(e2.second->id); LPEMetadata mdata; mdata.category = e.first; mdata.icon_name = icon; mdata.tooltip = tooltip; mdata.sensitive = is_appliable(e2.second->id, item_type, has_clip, has_mask); g_lpes[e2.second->id] = mdata; } } auto symbolic = Inkscape::Preferences::get()->getBool("/theme/symbolicIcons", true); add_lpes(_lpes_popup, symbolic); } } void LivePathEffectEditor::onAdd(LivePathEffect::EffectType etype) { selection_changed_lock = true; Glib::ustring key = converter.get_key(etype); SPLPEItem *fromclone = clonetolpeitem(); if (fromclone) { current_lpeitem = fromclone; if (key == "clone_original") { current_lpeitem->getCurrentLPE()->refresh_widgets = true; selection_changed_lock = false; DocumentUndo::done(getDocument(), _("Create and apply path effect"), INKSCAPE_ICON("dialog-path-effects")); return; } } selection_changed_lock = false; if (current_lpeitem) { LivePathEffect::Effect::createAndApply(key.c_str(), getDocument(), current_lpeitem); current_lpeitem->getCurrentLPE()->refresh_widgets = true; DocumentUndo::done(getDocument(), _("Create and apply path effect"), INKSCAPE_ICON("dialog-path-effects")); } } void LivePathEffectEditor::map_handler() { ensure_size(); } void LivePathEffectEditor::selection_info() { auto selection = getSelection(); SPItem * selected = nullptr; _LPESelectionInfo.hide(); if (selection && (selected = selection->singleItem()) ) { if (is(selected) || is(selected)) { _LPESelectionInfo.set_text(_("Text objects do not support Live Path Effects")); _LPESelectionInfo.show(); Glib::ustring labeltext = _("Convert text to paths"); Gtk::Button *selectbutton = Gtk::manage(new Gtk::Button()); Gtk::Box *boxc = Gtk::manage(new Gtk::Box()); Gtk::Label *lbl = Gtk::manage(new Gtk::Label(labeltext)); std::string shape_type = "group"; std::string highlight = SPColor(selected->highlight_color()).toString(); Gtk::Image *type = Gtk::manage(new Gtk::Image(sp_get_shape_icon(shape_type, Gdk::RGBA(highlight),20, 1))); boxc->pack_start(*type, false, false); boxc->pack_start(*lbl, false, false); type->set_margin_start(4); type->set_margin_end(4); selectbutton->add(*boxc); selectbutton->signal_clicked().connect([=](){ selection->toCurves(); }); _LPEParentBox.add(*selectbutton); Glib::ustring labeltext2 = _("Clone"); Gtk::Button *selectbutton2 = Gtk::manage(new Gtk::Button()); Gtk::Box *boxc2 = Gtk::manage(new Gtk::Box()); Gtk::Label *lbl2 = Gtk::manage(new Gtk::Label(labeltext2)); std::string shape_type2 = "clone"; std::string highlight2 = SPColor(selected->highlight_color()).toString(); Gtk::Image *type2 = Gtk::manage(new Gtk::Image(sp_get_shape_icon(shape_type2, Gdk::RGBA(highlight2),20, 1))); boxc2->pack_start(*type2, false, false); boxc2->pack_start(*lbl2, false, false); type2->set_margin_start(4); type2->set_margin_end(4); selectbutton2->add(*boxc2); selectbutton2->signal_clicked().connect([=](){ selection->clone();; }); _LPEParentBox.add(*selectbutton2); _LPEParentBox.show_all(); } else if (!is(selected) && !is(selected)) { _LPESelectionInfo.set_text(_("Select a path, shape, clone or group")); _LPESelectionInfo.show(); } else { if (selected->getId()) { Glib::ustring labeltext = selected->label() ? selected->label() : selected->getId(); Gtk::Box *boxc = Gtk::manage(new Gtk::Box()); Gtk::Label *lbl = Gtk::manage(new Gtk::Label(labeltext)); lbl->set_ellipsize(Pango::ELLIPSIZE_END); std::string shape_type = selected->typeName(); std::string highlight = SPColor(selected->highlight_color()).toString(); Gtk::Image *type = Gtk::manage(new Gtk::Image(sp_get_shape_icon(shape_type, Gdk::RGBA(highlight),20, 1))); boxc->pack_start(*type, false, false); boxc->pack_start(*lbl, false, false); _LPECurrentItem.add(*boxc); _LPECurrentItem.get_children()[0]->set_halign(Gtk::ALIGN_CENTER); _LPESelectionInfo.hide(); } std::vector > newrootsatellites; for (auto root : selected->rootsatellites) { auto lpeobj = cast(selected->document->getObjectById(root.second)); Inkscape::LivePathEffect::Effect *lpe = nullptr; if (lpeobj) { lpe = lpeobj->get_lpe(); } if (lpe) { const Glib::ustring label = _(converter.get_label(lpe->effectType()).c_str()); Glib::ustring labeltext = Glib::ustring::compose(_("Select %1 with %2 LPE"), root.first, label); auto lpeitem = cast(selected->document->getObjectById(root.first)); if (lpeitem && lpeitem->getLPEIndex(lpe) != Glib::ustring::npos) { newrootsatellites.emplace_back(root.first, root.second); Gtk::Button *selectbutton = Gtk::manage(new Gtk::Button()); Gtk::Box *boxc = Gtk::manage(new Gtk::Box()); Gtk::Label *lbl = Gtk::manage(new Gtk::Label(labeltext)); std::string shape_type = selected->typeName(); std::string highlight = SPColor(selected->highlight_color()).toString(); Gtk::Image *type = Gtk::manage(new Gtk::Image(sp_get_shape_icon(shape_type, Gdk::RGBA(highlight),20, 1))); boxc->pack_start(*type, false, false); boxc->pack_start(*lbl, false, false); type->set_margin_start(4); type->set_margin_end(4); selectbutton->add(*boxc); selectbutton->signal_clicked().connect([=](){ selection->set(lpeitem); }); _LPEParentBox.add(*selectbutton); } } } selected->rootsatellites = newrootsatellites; _LPEParentBox.show_all(); _LPEParentBox.drag_dest_unset(); _LPECurrentItem.show_all(); } } else if (!selection || selection->isEmpty()) { _LPESelectionInfo.set_text(_("Select a path, shape, clone or group")); _LPESelectionInfo.show(); } else if (selection->size() > 1) { _LPESelectionInfo.set_text(_("Select only one path, shape, clone or group")); _LPESelectionInfo.show(); } } void LivePathEffectEditor::onSelectionChanged(Inkscape::Selection *sel) { SPUse *use = nullptr; _reload_menu = true; if ( sel && !sel->isEmpty() ) { SPItem *item = sel->singleItem(); if ( item ) { auto lpeitem = cast(item); use = cast(item); if (lpeitem) { lpeitem->update_satellites(); current_lpeitem = lpeitem; _LPEAddContainer.set_sensitive(true); effect_list_reload(lpeitem); return; } } } current_lpeitem = nullptr; _LPEAddContainer.set_sensitive(use != nullptr); clear_lpe_list(); selection_info(); } void LivePathEffectEditor::move_list(gint origin, gint dest) { Inkscape::Selection *sel = getDesktop()->getSelection(); if ( sel && !sel->isEmpty() ) { SPItem *item = sel->singleItem(); if ( item ) { auto lpeitem = cast(item); if ( lpeitem ) { lpeitem->movePathEffect(origin, dest); } } } } static const std::vector entries = {Gtk::TargetEntry("GTK_LIST_BOX_ROW", Gtk::TARGET_SAME_APP, 0 )}; void LivePathEffectEditor::showParams(std::pair > expanderdata, bool changed) { LivePathEffectObject *lpeobj = expanderdata.second->lpeobject; if (lpeobj) { Inkscape::LivePathEffect::Effect *lpe = lpeobj->get_lpe(); if (lpe) { if (effectwidget && !lpe->refresh_widgets && expanderdata == current_lperef && !changed) { return; } if (effectwidget) { effectwidget->get_parent()->remove(*effectwidget); delete effectwidget; effectwidget = nullptr; } effectwidget = lpe->newWidget(); if (!dynamic_cast(effectwidget)->get_children().size()) { auto * label = new Gtk::Label("", Gtk::ALIGN_START, Gtk::ALIGN_CENTER); label->set_markup(_("Without parameters")); label->set_margin_top(5); label->set_margin_bottom(5); label->set_margin_start(5); effectwidget = label; } expanderdata.first->add(*effectwidget); expanderdata.first->show_all_children(); align(effectwidget, lpe->spinbutton_width_chars); // fixme: add resizing of dialog lpe->refresh_widgets = false; ensure_size(); } else { current_lperef = std::make_pair(nullptr, nullptr); } } else { current_lperef = std::make_pair(nullptr, nullptr); } // effectwidget = effect.newWidget(); // effectcontrol_frame.set_label(effect.getName()); // effectcontrol_vbox.pack_start(*effectwidget, true, true); // button_remove.show(); // status_label.hide(); // effectcontrol_vbox.show_all_children(); // align(effectwidget); // effectcontrol_frame.show(); // // fixme: add resizing of dialog // effect.refresh_widgets = false; } bool LivePathEffectEditor::closeExpander(GdkEventButton * evt) { current_lperef.first->set_expanded(false); return false; } /* * First clears the effectlist_store, then appends all effects from the effectlist. */ void LivePathEffectEditor::effect_list_reload(SPLPEItem *lpeitem) { clear_lpe_list(); _LPEExpanders.clear(); auto gladefile = get_filename_string(Inkscape::IO::Resource::UIS, "dialog-livepatheffect-item.glade"); gint counter = -1; Gtk::Expander *LPEExpanderCurrent = nullptr; effectlist = lpeitem->getEffectList(); gint total = effectlist.size(); if (total > 1) { _LPECurrentItem.drag_dest_unset(); _lpes_popup.drag_dest_unset(); _lpes_popup.get_entry().drag_dest_unset(); _LPEAddContainer.drag_dest_unset(); _LPEContainer.drag_dest_set(entries, Gtk::DEST_DEFAULT_ALL, Gdk::ACTION_MOVE); _LPEContainer.signal_drag_data_received().connect([=](const Glib::RefPtr& context, int x, int y, const Gtk::SelectionData& selection_data, guint info, guint time) { if (dnd) { unsigned int pos_target, pos_source; Gtk::Widget *target = &_LPEContainer; pos_source = atoi(reinterpret_cast(selection_data.get_data())); pos_target = LPEListBox.get_children().size()-1; if (y < 90) { pos_target = 0; } if (pos_target == pos_source) { gtk_drag_finish(context->gobj(), FALSE, FALSE, time); dnd = false; return; } Glib::RefPtr stylec = target->get_style_context(); if (pos_source > pos_target) { if (stylec->has_class("after")) { pos_target ++; } } else if (pos_source < pos_target) { if (stylec->has_class("before")) { pos_target --; } } Gtk::Widget *source = LPEListBox.get_row_at_index(pos_source); g_object_ref(source->gobj()); LPEListBox.remove(*source); LPEListBox.insert(*source, pos_target); g_object_unref(source->gobj()); move_list(pos_source,pos_target); gtk_drag_finish(context->gobj(), TRUE, TRUE, time); dnd = false; } }); _LPEContainer.signal_drag_motion().connect([=](const Glib::RefPtr& context, int x, int y, guint time) { Glib::RefPtr stylec = _LPEContainer.get_style_context(); if (y < 90) { stylec->add_class("before"); stylec->remove_class("after"); } else { stylec->remove_class("before"); stylec->add_class("after"); } return true; }, true); } PathEffectList::iterator it; Gtk::MenuItem *LPEMoveUpExtrem = nullptr; Gtk::MenuItem *LPEMoveDownExtrem = nullptr; Gtk::EventBox *LPEDrag = nullptr; for( it = effectlist.begin() ; it!=effectlist.end(); ++it) { if ( !(*it)->lpeobject ) { continue; } auto lpe = (*it)->lpeobject->get_lpe(); bool current = lpeitem->getCurrentLPE() == lpe; counter++; Glib::RefPtr builder; if (lpe) { try { builder = Gtk::Builder::create_from_file(gladefile); } catch (const Glib::Error &ex) { g_warning("Glade file loading failed for path effect dialog"); return; } Gtk::Box *LPEEffect; Gtk::Box *LPEExpanderBox; Gtk::Box *LPEActionButtons; Gtk::EventBox *LPEOpenExpander; Gtk::Expander *LPEExpander; Gtk::Image *LPEIconImage; Gtk::EventBox *LPEErase; Gtk::EventBox *LPEHide; Gtk::MenuItem *LPEtoggleFavorite; Gtk::Label *LPENameLabel; Gtk::Menu *LPEEffectMenu; Gtk::MenuItem *LPEMoveUp; Gtk::MenuItem *LPEMoveDown; Gtk::MenuItem *LPEResetDefault; Gtk::MenuItem *LPESetDefault; builder->get_widget("LPEMoveUp", LPEMoveUp); builder->get_widget("LPEMoveDown", LPEMoveDown); builder->get_widget("LPEResetDefault", LPEResetDefault); builder->get_widget("LPESetDefault", LPESetDefault); builder->get_widget("LPENameLabel", LPENameLabel); builder->get_widget("LPEEffectMenu", LPEEffectMenu); builder->get_widget("LPEHide", LPEHide); builder->get_widget("LPEIconImage", LPEIconImage); builder->get_widget("LPEExpanderBox", LPEExpanderBox); builder->get_widget("LPEEffect", LPEEffect); builder->get_widget("LPEExpander", LPEExpander); builder->get_widget("LPEOpenExpander", LPEOpenExpander); builder->get_widget("LPEErase", LPEErase); builder->get_widget("LPEDrag", LPEDrag); builder->get_widget("LPEActionButtons", LPEActionButtons); builder->get_widget("LPEtoggleFavorite", LPEtoggleFavorite); LPEExpander->drag_dest_unset(); LPEActionButtons->drag_dest_unset(); LPEMoveUp->show(); LPEMoveDown->show(); LPEDrag->get_children()[0]->show(); LPEDrag->set_tooltip_text(_("Drag to change position in path effects stack")); if (current) { LPEExpanderCurrent = LPEExpander; } if (counter == 0) { LPEMoveUpExtrem = LPEMoveUp; } LPEMoveDownExtrem = LPEMoveDown; auto effectype = lpe->effectType(); const Glib::ustring label = _(converter.get_label(effectype).c_str()); const Glib::ustring untranslated_label = converter.get_label(effectype); const Glib::ustring icon = converter.get_icon(effectype); LPEIconImage->set_from_icon_name(icon, Gtk::IconSize(Gtk::ICON_SIZE_SMALL_TOOLBAR)); Glib::ustring lpename = ""; if (untranslated_label == label) { lpename = label; } else { lpename = (label + "\n" + untranslated_label + ""); } auto *visimage = dynamic_cast(dynamic_cast(LPEHide->get_children()[0])->get_image()); if (!g_strcmp0(lpe->getRepr()->attribute("is_visible"),"true")) { visimage->set_from_icon_name("object-visible-symbolic", Gtk::IconSize(Gtk::ICON_SIZE_SMALL_TOOLBAR)); } else { visimage->set_from_icon_name("object-hidden-symbolic", Gtk::IconSize(Gtk::ICON_SIZE_SMALL_TOOLBAR)); } _LPEExpanders.emplace_back(LPEExpander, (*it)); LPEListBox.add(*LPEEffect); Glib::ustring name = "drag_"; name += Glib::ustring::format(counter); LPEDrag->set_name(name); if (total > 1) { //DnD LPEDrag->drag_source_set(entries, Gdk::BUTTON1_MASK, Gdk::ACTION_MOVE); } Glib::ustring tooltip = _(converter.get_description(effectype).c_str()); if (untranslated_label != label) { tooltip = "[" + untranslated_label + "] " + _(converter.get_description(effectype).c_str()); } gint id = (gint)effectype; LPEExpanderBox->property_has_tooltip() = true; LPEExpanderBox->signal_query_tooltip().connect([=](int x, int y, bool kbd, const Glib::RefPtr& tooltipw){ return sp_query_custom_tooltip(x, y, kbd, tooltipw, id, tooltip, icon); }); size_t pos = 0; std::shared_ptr lperef = (*it); for (auto w : LPEEffectMenu->get_children()) { auto * mitem = dynamic_cast(w); if (mitem) { mitem->signal_activate().connect([=](){ if (pos == 0) { current_lpeitem->setCurrentPathEffect(lperef); current_lpeitem->duplicateCurrentPathEffect(); effect_list_reload(current_lpeitem); DocumentUndo::done(getDocument(), _("Duplicate path effect"), INKSCAPE_ICON("dialog-path-effects")); } else if (pos == 1) { current_lpeitem->setCurrentPathEffect(lperef); current_lpeitem->upCurrentPathEffect(); effect_list_reload(current_lpeitem); DocumentUndo::done(getDocument(), _("Move path effect up"), INKSCAPE_ICON("dialog-path-effects")); } else if (pos == 2) { current_lpeitem->setCurrentPathEffect(lperef); current_lpeitem->downCurrentPathEffect(); effect_list_reload(current_lpeitem); DocumentUndo::done(getDocument(), _("Move path effect down"), INKSCAPE_ICON("dialog-path-effects")); } else if (pos == 3) { lpeFlatten(lperef); } else if (pos == 4) { lpe->setDefaultParameters(); effect_list_reload(current_lpeitem); } else if (pos == 5) { lpe->resetDefaultParameters(); effect_list_reload(current_lpeitem); } else if (pos == 6) { sp_toggle_fav(untranslated_label, LPEtoggleFavorite); _reload_menu = true; _item_type = ""; // here we force reload even with the same tipe item selected } }); if (pos == 6) { if (sp_has_fav(untranslated_label)) { LPEtoggleFavorite->set_label(_("Unset Favorite")); } else { LPEtoggleFavorite->set_label(_("Set Favorite")); } } } pos ++; } if (total > 1) { LPEDrag->signal_drag_begin().connect([=](const Glib::RefPtr context){ cairo_surface_t *surface; cairo_t *cr; int x, y; double sx = 1; double sy = 1; dnd = true; Gtk::Allocation alloc = LPEEffect->get_allocation (); auto device_scale = get_scale_factor(); surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, alloc.get_width() * device_scale, alloc.get_height() * device_scale); cairo_surface_set_device_scale(surface, device_scale, device_scale); cr = cairo_create (surface); LPEEffect->get_style_context()->add_class("drag-icon"); gtk_widget_draw (GTK_WIDGET(LPEEffect->gobj()), cr); LPEEffect->get_style_context()->remove_class("drag-icon"); LPEDrag->translate_coordinates(*LPEEffect, dndx, dndy, x, y); #ifndef __APPLE__ cairo_surface_get_device_scale (surface, &sx, &sy); #endif cairo_surface_set_device_offset (surface, -x * sx, -y * sy); gtk_drag_set_icon_surface (context->gobj(), surface); cairo_destroy (cr); cairo_surface_destroy (surface); }); auto row = dynamic_cast(LPEEffect->get_parent()); LPEDrag->signal_drag_data_get().connect([=](const Glib::RefPtr& context, Gtk::SelectionData& selection_data, guint info, guint time) { selection_data.set("GTK_LIST_BOX_ROW", Glib::ustring::format(row->get_index())); }); LPEDrag->signal_drag_end().connect([=](const Glib::RefPtr& context) { dnd = false; }); row->signal_drag_data_received().connect([=](const Glib::RefPtr& context, int x, int y, const Gtk::SelectionData& selection_data, guint info, guint time) { if (dnd) { unsigned int pos_target, pos_source; Gtk::Widget *target = row; pos_target = row->get_index(); pos_source = atoi(reinterpret_cast(selection_data.get_data())); Glib::RefPtr stylec = target->get_style_context(); if (pos_source > pos_target) { if (stylec->has_class("after")) { pos_target ++; } } else if (pos_source < pos_target) { if (stylec->has_class("before")) { pos_target --; } } Gtk::Widget *source = LPEListBox.get_row_at_index(pos_source); if (source == target) { gtk_drag_finish(context->gobj(), FALSE, FALSE, time); dnd = false; return; } g_object_ref(source->gobj()); LPEListBox.remove(*source); LPEListBox.insert(*source, pos_target); g_object_unref(source->gobj()); move_list(pos_source,pos_target); gtk_drag_finish(context->gobj(), TRUE, TRUE, time); dnd = false; } }); row->drag_dest_set(entries, Gtk::DEST_DEFAULT_ALL, Gdk::ACTION_MOVE); row->signal_drag_motion().connect([=](const Glib::RefPtr& context, int x, int y, guint time) { gint half = row->get_allocated_height()/2; Glib::RefPtr stylec = row->get_style_context(); if (y < half) { stylec->add_class("before"); stylec->remove_class("after"); } else { stylec->remove_class("before"); stylec->add_class("after"); } return true; }, true); } // other LPEEffect->set_name("LPEEffectItem"); LPENameLabel->set_label(g_dpgettext2(nullptr, "path effect", (*it)->lpeobject->get_lpe()->getName().c_str())); LPEExpander->property_expanded().signal_changed().connect(sigc::bind(sigc::mem_fun(*this, &LivePathEffectEditor::expanded_notify),LPEExpander)); LPEOpenExpander->signal_button_press_event().connect([=](GdkEventButton* const evt){ LPEExpander->set_expanded(!LPEExpander->property_expanded()); return false; }, false); dynamic_cast(LPEHide->get_children()[0])->signal_clicked().connect(sigc::bind(sigc::mem_fun(*this, &LivePathEffectEditor::toggleVisible), lpe, LPEHide)); LPEDrag->signal_button_press_event().connect([=](GdkEventButton* const evt){dndx = evt->x; dndy = evt->y; return false; }, false); dynamic_cast(LPEErase->get_children()[0])->signal_clicked().connect([=](){ removeEffect(LPEExpander);}); if (total > 1) { LPEDrag->signal_enter_notify_event().connect([=](GdkEventCrossing*){ auto window = get_window(); auto display = get_display(); auto cursor = Gdk::Cursor::create(display, "grab"); window->set_cursor(cursor); return false; }, false); LPEDrag->signal_leave_notify_event().connect([=](GdkEventCrossing*){ auto window = get_window(); auto display = get_display(); auto cursor = Gdk::Cursor::create(display, "default"); window->set_cursor(cursor); return false; }, false); } if (lpe->hasDefaultParameters()) { LPEResetDefault->show(); LPESetDefault->hide(); } else { LPEResetDefault->hide(); LPESetDefault->show(); } } } if (counter == 0 && LPEDrag) { LPEDrag->get_children()[0]->hide(); LPEDrag->set_tooltip_text(""); } if (LPEMoveUpExtrem) { LPEMoveUpExtrem->hide(); LPEMoveDownExtrem->hide(); } if (LPEExpanderCurrent) { _LPESelectionInfo.hide(); LPEExpanderCurrent->set_expanded(true); Gtk::Window *current_window = dynamic_cast(LPEExpanderCurrent->get_toplevel()); if (current_window) { current_window->set_focus(*LPEExpanderCurrent); } } selection_info(); LPEListBox.show_all_children(); ensure_size(); } void LivePathEffectEditor::expanded_notify(Gtk::Expander *expander) { if (updating) { return; } if (!dnd) { _freezeexpander = false; } if (_freezeexpander) { _freezeexpander = false; return; } if (dnd) { _freezeexpander = true; expander->set_expanded(!expander->get_expanded()); return; }; updating = true; if (expander->get_expanded()) { for (auto &w : _LPEExpanders){ if (w.first == expander) { w.first->set_expanded(true); w.first->get_parent()->get_parent()->get_parent()->set_name("currentlpe"); current_lperef = w; current_lpeitem->setCurrentPathEffect(w.second); showParams(w, true); } else { w.first->set_expanded(false); w.first->get_parent()->get_parent()->get_parent()->set_name("unactive_lpe"); } } } auto selection = SP_ACTIVE_DESKTOP->getSelection(); if (selection && current_lpeitem && !selection->isEmpty()) { selection_changed_lock = true; selection->clear(); selection->add(current_lpeitem); Inkscape::UI::Tools::sp_update_helperpath(getDesktop()); selection_changed_lock = false; } updating = false; } bool LivePathEffectEditor::lpeFlatten(std::shared_ptr lperef) { current_lpeitem->setCurrentPathEffect(lperef); current_lpeitem = current_lpeitem->flattenCurrentPathEffect(); auto selection = getSelection(); if (selection && selection->isEmpty() ) { selection->add(current_lpeitem); } DocumentUndo::done(getDocument(), _("Flatten path effect(s)"), INKSCAPE_ICON("dialog-path-effects")); return false; } void LivePathEffectEditor::removeEffect(Gtk::Expander * expander) { bool reload = current_lperef.first != expander; auto current_lperef_tmp = current_lperef; for (auto &w : _LPEExpanders){ if (w.first == expander) { current_lpeitem->setCurrentPathEffect(w.second); current_lpeitem = current_lpeitem->removeCurrentPathEffect(false); } } if (reload) { current_lpeitem->setCurrentPathEffect(current_lperef_tmp.second); } effect_list_reload(current_lpeitem); DocumentUndo::done(getDocument(), _("Remove path effect"), INKSCAPE_ICON("dialog-path-effects")); } bool LivePathEffectEditor::toggleFavInLpe(GdkEventButton * evt, Glib::ustring name, Gtk::Button *favbutton) { auto *favimage = dynamic_cast(favbutton->get_image()); if (favimage->get_icon_name() == "draw-star") { favbutton->set_image_from_icon_name("draw-star-outline", Gtk::IconSize(Gtk::ICON_SIZE_SMALL_TOOLBAR)); sp_remove_fav(name); } else { favbutton->set_image_from_icon_name("draw-star", Gtk::IconSize(Gtk::ICON_SIZE_SMALL_TOOLBAR)); sp_add_fav(name); } clearMenu(); return false; } /* * Clears the effectlist */ void LivePathEffectEditor::clear_lpe_list() { for (auto &w : LPEListBox.get_children()) { LPEListBox.remove(*w); } for (auto &w : _LPEParentBox.get_children()) { _LPEParentBox.remove(*w); } for (auto &w : _LPECurrentItem.get_children()) { _LPECurrentItem.remove(*w); } } SPLPEItem * LivePathEffectEditor::clonetolpeitem() { auto selection = getSelection(); if (selection && !selection->isEmpty() ) { auto use = cast(selection->singleItem()); if ( use ) { DocumentUndo::ScopedInsensitive tmp(getDocument()); // item is a clone. do not show effectlist dialog. // convert to path, apply CLONE_ORIGINAL LPE, link it to the cloned path // test whether linked object is supported by the CLONE_ORIGINAL LPE SPItem *orig = use->get_original(); if ( is(orig) || is(orig) || is(orig) ) { // select original selection->set(orig); // delete clone but remember its id and transform auto id_copy = Util::to_opt(use->getAttribute("id")); auto transform_copy = Util::to_opt(use->getAttribute("transform")); use->deleteObject(false); use = nullptr; // run sp_selection_clone_original_path_lpe selection->cloneOriginalPathLPE(true, true, true); SPItem *new_item = selection->singleItem(); // Check that the cloning was successful. We don't want to change the ID of the original referenced path! if (new_item && (new_item != orig)) { new_item->setAttribute("id", Util::to_cstr(id_copy)); if (Util::to_cstr(transform_copy)) { Geom::Affine item_t(Geom::identity()); sp_svg_transform_read(Util::to_cstr(transform_copy), &item_t); new_item->transform *= item_t; new_item->doWriteTransform(new_item->transform); new_item->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); } new_item->setAttribute("class", "fromclone"); } auto *lpeitem = cast(new_item); if (lpeitem) { sp_lpe_item_update_patheffect(lpeitem, true, true); return lpeitem; } } } } return nullptr; } void LivePathEffectEditor::onAddGallery() { // show effectlist dialog using Inkscape::UI::Dialog::LivePathEffectAdd; LivePathEffectAdd::show(getDesktop()); clearMenu(); if ( !LivePathEffectAdd::isApplied()) { return; } const LivePathEffect::EnumEffectData *data = LivePathEffectAdd::getActiveData();; if (!data) { return; } selection_changed_lock = true; SPLPEItem *fromclone = clonetolpeitem(); if (fromclone) { current_lpeitem = fromclone; if (data->key == "clone_original") { current_lpeitem->getCurrentLPE()->refresh_widgets = true; selection_changed_lock = false; DocumentUndo::done(getDocument(), _("Create and apply path effect"), INKSCAPE_ICON("dialog-path-effects")); return; } } selection_changed_lock = false; if (current_lpeitem) { LivePathEffect::Effect::createAndApply(data->key.c_str(), getDocument(), current_lpeitem); current_lpeitem->getCurrentLPE()->refresh_widgets = true; DocumentUndo::done(getDocument(), _("Create and apply path effect"), INKSCAPE_ICON("dialog-path-effects")); } } void LivePathEffectEditor::on_showgallery_notify(Preferences::Entry const &new_val) { _LPEGallery.set_visible(new_val.getBool()); } } // namespace Dialog } // namespace UI } // namespace Inkscape /* Local Variables: mode:c++ c-file-style:"stroustrup" c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) indent-tabs-mode:nil fill-column:99 End: */ // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :