From 320e0d4365fcf48aac42a8118b7fa9893dbfe1f5 Mon Sep 17 00:00:00 2001 From: Jabier Arraiza Date: Sun, 8 Jan 2023 03:10:06 +0100 Subject: [PATCH] Add symbol tool --- po/POTFILES.src.in | 2 + .../Tango/scalable/actions/color-palette.svg | 160 +++++ .../icons/Tango/scalable/actions/symbols.svg | 166 +++++ share/icons/hicolor/cursors/symbol.svg | 129 ++++ .../scalable/actions/color-palette.svg | 160 +++++ .../hicolor/scalable/actions/symbols.svg | 167 ++++- .../actions/color-palette-symbolic.svg | 117 +++ .../symbolic/actions/symbols-symbolic.svg | 157 +++- .../actions/color-palette-symbolic.svg | 119 +++ .../symbolic/actions/symbols-symbolic.svg | 166 ++++- share/ui/style.css | 17 +- share/ui/toolbar-tool-prefs.ui | 27 + share/ui/toolbar-tool.ui | 29 + src/actions/actions-tools.cpp | 4 + src/actions/actions-tools.h | 1 + src/desktop-style.cpp | 36 +- src/desktop-style.h | 2 +- src/display/drawing-item.cpp | 4 +- src/document.cpp | 50 +- src/document.h | 2 + src/live_effects/lpeobject.cpp | 2 +- src/object/sp-lpe-item.cpp | 29 +- src/object/sp-lpe-item.h | 4 +- src/object/sp-use.cpp | 28 +- src/preferences-skeleton.h | 1 + src/ui/CMakeLists.txt | 4 + src/ui/clipboard.cpp | 118 +-- src/ui/clipboard.h | 3 +- src/ui/dialog/inkscape-preferences.cpp | 7 + src/ui/dialog/inkscape-preferences.h | 5 +- src/ui/dialog/symbols.cpp | 436 ++++++----- src/ui/dialog/symbols.h | 18 +- src/ui/knot/knot-holder.cpp | 4 + src/ui/tool-factory.cpp | 3 + src/ui/toolbar/symbol-toolbar.cpp | 241 +++++++ src/ui/toolbar/symbol-toolbar.h | 76 ++ src/ui/tools/symbol-tool.cpp | 676 ++++++++++++++++++ src/ui/tools/symbol-tool.h | 57 ++ src/widgets/mappings.xml | 1 + src/widgets/toolbox.cpp | 14 +- 40 files changed, 2898 insertions(+), 344 deletions(-) create mode 100644 share/icons/Tango/scalable/actions/color-palette.svg create mode 100644 share/icons/Tango/scalable/actions/symbols.svg create mode 100644 share/icons/hicolor/cursors/symbol.svg create mode 100644 share/icons/hicolor/scalable/actions/color-palette.svg create mode 100644 share/icons/hicolor/symbolic/actions/color-palette-symbolic.svg create mode 100644 share/icons/multicolor/symbolic/actions/color-palette-symbolic.svg create mode 100644 src/ui/toolbar/symbol-toolbar.cpp create mode 100644 src/ui/toolbar/symbol-toolbar.h create mode 100644 src/ui/tools/symbol-tool.cpp create mode 100644 src/ui/tools/symbol-tool.h diff --git a/po/POTFILES.src.in b/po/POTFILES.src.in index d4df233054..09fce41ba4 100644 --- a/po/POTFILES.src.in +++ b/po/POTFILES.src.in @@ -354,6 +354,7 @@ ${CMAKE_BINARY_DIR}/share/templates/templates.h ../src/ui/toolbar/spiral-toolbar.cpp ../src/ui/toolbar/spray-toolbar.cpp ../src/ui/toolbar/star-toolbar.cpp +../src/ui/toolbar/symbol-toolbar.cpp ../src/ui/toolbar/text-toolbar.cpp ../src/ui/toolbar/tweak-toolbar.cpp ../src/ui/tools/arc-tool.cpp @@ -377,6 +378,7 @@ ${CMAKE_BINARY_DIR}/share/templates/templates.h ../src/ui/tools/spiral-tool.cpp ../src/ui/tools/spray-tool.cpp ../src/ui/tools/star-tool.cpp +../src/ui/tools/symbol-tool.cpp ../src/ui/tools/text-tool.cpp ../src/ui/tools/tool-base.cpp ../src/ui/tools/tweak-tool.cpp diff --git a/share/icons/Tango/scalable/actions/color-palette.svg b/share/icons/Tango/scalable/actions/color-palette.svg new file mode 100644 index 0000000000..6f49847b30 --- /dev/null +++ b/share/icons/Tango/scalable/actions/color-palette.svg @@ -0,0 +1,160 @@ + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + Escalator + + + + + + diff --git a/share/icons/Tango/scalable/actions/symbols.svg b/share/icons/Tango/scalable/actions/symbols.svg new file mode 100644 index 0000000000..94782b5e78 --- /dev/null +++ b/share/icons/Tango/scalable/actions/symbols.svg @@ -0,0 +1,166 @@ + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + diff --git a/share/icons/hicolor/cursors/symbol.svg b/share/icons/hicolor/cursors/symbol.svg new file mode 100644 index 0000000000..17adc0fe6a --- /dev/null +++ b/share/icons/hicolor/cursors/symbol.svg @@ -0,0 +1,129 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/share/icons/hicolor/scalable/actions/color-palette.svg b/share/icons/hicolor/scalable/actions/color-palette.svg new file mode 100644 index 0000000000..6f49847b30 --- /dev/null +++ b/share/icons/hicolor/scalable/actions/color-palette.svg @@ -0,0 +1,160 @@ + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + Escalator + + + + + + diff --git a/share/icons/hicolor/scalable/actions/symbols.svg b/share/icons/hicolor/scalable/actions/symbols.svg index f889a69a99..94782b5e78 100644 --- a/share/icons/hicolor/scalable/actions/symbols.svg +++ b/share/icons/hicolor/scalable/actions/symbols.svg @@ -1,3 +1,166 @@ - - + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + diff --git a/share/icons/hicolor/symbolic/actions/color-palette-symbolic.svg b/share/icons/hicolor/symbolic/actions/color-palette-symbolic.svg new file mode 100644 index 0000000000..9b18bb3c8d --- /dev/null +++ b/share/icons/hicolor/symbolic/actions/color-palette-symbolic.svg @@ -0,0 +1,117 @@ + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + diff --git a/share/icons/hicolor/symbolic/actions/symbols-symbolic.svg b/share/icons/hicolor/symbolic/actions/symbols-symbolic.svg index f889a69a99..c11090f4d3 100644 --- a/share/icons/hicolor/symbolic/actions/symbols-symbolic.svg +++ b/share/icons/hicolor/symbolic/actions/symbols-symbolic.svg @@ -1,3 +1,156 @@ - - + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + diff --git a/share/icons/multicolor/symbolic/actions/color-palette-symbolic.svg b/share/icons/multicolor/symbolic/actions/color-palette-symbolic.svg new file mode 100644 index 0000000000..1ca3c1c8bb --- /dev/null +++ b/share/icons/multicolor/symbolic/actions/color-palette-symbolic.svg @@ -0,0 +1,119 @@ + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + diff --git a/share/icons/multicolor/symbolic/actions/symbols-symbolic.svg b/share/icons/multicolor/symbolic/actions/symbols-symbolic.svg index f889a69a99..ee7d9b1b70 100644 --- a/share/icons/multicolor/symbolic/actions/symbols-symbolic.svg +++ b/share/icons/multicolor/symbolic/actions/symbols-symbolic.svg @@ -1,3 +1,165 @@ - - + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + diff --git a/share/ui/style.css b/share/ui/style.css index 33b3af4e91..9d26ab5b42 100644 --- a/share/ui/style.css +++ b/share/ui/style.css @@ -195,14 +195,14 @@ notebook.blink > header > tabs > tab:checked { color: @success_color; } -iconview *:hover { +iconview *:hover ,iconview *:selected{ border-width: 1px; border-color: @theme_fg_color; border-style: solid; border-radius: 4px; } -.dark iconview *:hover { +.dark iconview *:hover, .dark iconview *:selected { border-color: @theme_bg_color; } @@ -1011,3 +1011,16 @@ padding:3px 3px 10px 3px; margin:10px 4px; padding:0; } +.dark toolbar toolbutton.linked button { + margin:0; + border:0; + border-radius:0; + color:@theme_bg_color; + background-color:@theme_fg_color; + background-image:image(@theme_fg_color); + caret-color:@theme_bg_color; + -gtk-secondary-caret-color:@theme_fg_color; +} +.dark toolbar toolbutton.linked button:checked { + background-image:image(alpha(@theme_selected_bg_color, 0.2)); +} \ No newline at end of file diff --git a/share/ui/toolbar-tool-prefs.ui b/share/ui/toolbar-tool-prefs.ui index 98e452a01f..498c898d89 100644 --- a/share/ui/toolbar-tool-prefs.ui +++ b/share/ui/toolbar-tool-prefs.ui @@ -92,6 +92,11 @@ False draw-spiral + + True + False + symbols + True False @@ -367,6 +372,28 @@ + + + True + True + + + True + True + False + center + center + win.tool-switch + 'Symbol' + icon-symbol + none + + + + + diff --git a/share/ui/toolbar-tool.ui b/share/ui/toolbar-tool.ui index 16232806e2..351cb85b12 100644 --- a/share/ui/toolbar-tool.ui +++ b/share/ui/toolbar-tool.ui @@ -107,6 +107,11 @@ False draw-spiral + + True + False + symbols + True False @@ -372,6 +377,30 @@ + + + True + True + + + toolsradio + False + True + True + False + center + center + win.tool-switch + 'Symbol' + icon-symbol + none + + + + + False diff --git a/src/actions/actions-tools.cpp b/src/actions/actions-tools.cpp index 7990c6a41a..f8c06e96db 100644 --- a/src/actions/actions-tools.cpp +++ b/src/actions/actions-tools.cpp @@ -30,6 +30,7 @@ #include "object/sp-spiral.h" #include "object/sp-star.h" #include "object/sp-text.h" +#include "object/sp-symbol.h" #include "object/sp-marker.h" #include "ui/dialog/dialog-container.h" @@ -61,6 +62,7 @@ static std::map const &get_tool_data() {"Star", {TOOLS_SHAPES_STAR, PREFS_PAGE_TOOLS_SHAPES_STAR, "/tools/shapes/star", }}, {"3DBox", {TOOLS_SHAPES_3DBOX, PREFS_PAGE_TOOLS_SHAPES_3DBOX, "/tools/shapes/3dbox", }}, {"Spiral", {TOOLS_SHAPES_SPIRAL, PREFS_PAGE_TOOLS_SHAPES_SPIRAL, "/tools/shapes/spiral", }}, + {"Symbol", {TOOLS_SHAPES_SYMBOL, PREFS_PAGE_TOOLS_SHAPES_SYMBOL, "/tools/shapes/symbol", }}, {"Pencil", {TOOLS_FREEHAND_PENCIL, PREFS_PAGE_TOOLS_PENCIL, "/tools/freehand/pencil", }}, {"Pen", {TOOLS_FREEHAND_PEN, PREFS_PAGE_TOOLS_PEN, "/tools/freehand/pen", }}, {"Calligraphic", {TOOLS_CALLIGRAPHIC, PREFS_PAGE_TOOLS_CALLIGRAPHY, "/tools/calligraphic", }}, @@ -98,6 +100,7 @@ static std::map const &get_tool_msg() {"Star", _("Drag to create a star. Drag controls to edit the star shape. Click to select.") }, {"3DBox", _("Drag to create a 3D box. Drag controls to resize in perspective. Click to select (with Ctrl+Alt for single faces).") }, {"Spiral", _("Drag to create a spiral. Drag controls to edit the spiral shape. Click to select.") }, + {"Symbol", _("Drag to create a drawing using a symbol.") }, {"Marker", _("Click a shape to start editing its markers. Drag controls to change orientation, scale, and position.") }, {"Pencil", _("Drag to create a freehand line. Shift appends to selected path, Alt activates sketch mode.") }, {"Pen", _("Click or click and drag to start a path; with Shift to append to selected path. Ctrl+click to create single dots (straight line modes only).") }, @@ -367,6 +370,7 @@ std::vector> raw_data_tools = {"win.tool-switch('Star')", N_("Star/Polygon Tool"), "Tool Switch", N_("Create stars and polygons") }, {"win.tool-switch('3DBox')", N_("3D Box Tool"), "Tool Switch", N_("Create 3D Boxes") }, {"win.tool-switch('Spiral')", N_("Spiral Tool"), "Tool Switch", N_("Create spirals") }, + {"win.tool-switch('Symbol')", N_("Symbol Tool"), "Tool Switch", N_("Create drawings from symbols") }, {"win.tool-switch('Marker')", N_("Marker Tool"), "Tool Switch", N_("Edit markers") }, {"win.tool-switch('Pen')", N_("Pen Tool"), "Tool Switch", N_("Draw Bezier curves and straight lines") }, diff --git a/src/actions/actions-tools.h b/src/actions/actions-tools.h index ba79d474e4..2eb9684b18 100644 --- a/src/actions/actions-tools.h +++ b/src/actions/actions-tools.h @@ -27,6 +27,7 @@ enum tools_enum { TOOLS_SHAPES_ARC, TOOLS_SHAPES_STAR, TOOLS_SHAPES_SPIRAL, + TOOLS_SHAPES_SYMBOL, TOOLS_FREEHAND_PENCIL, TOOLS_FREEHAND_PEN, TOOLS_CALLIGRAPHIC, diff --git a/src/desktop-style.cpp b/src/desktop-style.cpp index 02caf27deb..89bd343565 100644 --- a/src/desktop-style.cpp +++ b/src/desktop-style.cpp @@ -42,7 +42,7 @@ #include "object/sp-tspan.h" #include "object/sp-use.h" #include "style.h" - +#include "ui/tools/symbol-tool.h" #include "svg/css-ostringstream.h" #include "svg/svg-color.h" #include "svg/svg.h" @@ -101,7 +101,7 @@ sp_desktop_set_color(SPDesktop *desktop, ColorRGBA const &color, bool is_relativ * Apply style on object and children, recursively. */ void -sp_desktop_apply_css_recursive(SPObject *o, SPCSSAttr *css, bool skip_lines) +sp_desktop_apply_css_recursive(SPObject *o, SPCSSAttr *css, bool skip_lines, bool recursive) { // non-items should not have style auto item = cast(o); @@ -159,18 +159,19 @@ sp_desktop_apply_css_recursive(SPObject *o, SPCSSAttr *css, bool skip_lines) if (is(o)) { return; } - - for (auto& child: o->children) { - if (sp_repr_css_property(css, "opacity", nullptr) != nullptr) { - // Unset properties which are accumulating and thus should not be set recursively. - // For example, setting opacity 0.5 on a group recursively would result in the visible opacity of 0.25 for an item in the group. - SPCSSAttr *css_recurse = sp_repr_css_attr_new(); - sp_repr_css_merge(css_recurse, css); - sp_repr_css_set_property(css_recurse, "opacity", nullptr); - sp_desktop_apply_css_recursive(&child, css_recurse, skip_lines); - sp_repr_css_attr_unref(css_recurse); - } else { - sp_desktop_apply_css_recursive(&child, css, skip_lines); + if (recursive) { + for (auto& child: o->children) { + if (sp_repr_css_property(css, "opacity", nullptr) != nullptr) { + // Unset properties which are accumulating and thus should not be set recursively. + // For example, setting opacity 0.5 on a group recursively would result in the visible opacity of 0.25 for an item in the group. + SPCSSAttr *css_recurse = sp_repr_css_attr_new(); + sp_repr_css_merge(css_recurse, css); + sp_repr_css_set_property(css_recurse, "opacity", nullptr); + sp_desktop_apply_css_recursive(&child, css_recurse, skip_lines); + sp_repr_css_attr_unref(css_recurse); + } else { + sp_desktop_apply_css_recursive(&child, css, skip_lines); + } } } } @@ -251,8 +252,11 @@ sp_desktop_set_style(Inkscape::ObjectSet *set, SPDesktop *desktop, SPCSSAttr *cs sp_desktop_apply_css_recursive(item, css, true); } else { - - sp_desktop_apply_css_recursive(item, css_no_text, true); + bool recursive = true; + if (dynamic_cast(desktop->event_context)) { + recursive = false; + } + sp_desktop_apply_css_recursive(item, css_no_text, true, recursive); } } diff --git a/src/desktop-style.h b/src/desktop-style.h index a9d357942d..ad87cee58b 100644 --- a/src/desktop-style.h +++ b/src/desktop-style.h @@ -64,7 +64,7 @@ enum { // which property was queried (add when you need more) QUERY_STYLE_PROPERTY_BLUR // blur }; -void sp_desktop_apply_css_recursive(SPObject *o, SPCSSAttr *css, bool skip_lines); +void sp_desktop_apply_css_recursive(SPObject *o, SPCSSAttr *css, bool skip_lines, bool recursive = true); void sp_desktop_set_color(SPDesktop *desktop, ColorRGBA const &color, bool is_relative, bool fill); void sp_desktop_set_style(Inkscape::ObjectSet *set, SPDesktop *desktop, SPCSSAttr *css, bool change = true, bool write_current = true, bool switch_style = false); void sp_desktop_set_style(SPDesktop *desktop, SPCSSAttr *css, bool change = true, bool write_current = true, bool switch_style = false); diff --git a/src/display/drawing-item.cpp b/src/display/drawing-item.cpp index 7ceb02e6be..518c75bd6a 100644 --- a/src/display/drawing-item.cpp +++ b/src/display/drawing-item.cpp @@ -941,7 +941,9 @@ DrawingItem *DrawingItem::pick(Geom::Point const &p, double delta, unsigned flag // Sometimes there's no BBOX in state, reason unknown (bug 992817) // I made this not an assert to remove the warning if (!(_state & STATE_BBOX) || !(_state & STATE_PICK)) { - g_warning("Invalid state when picking: STATE_BBOX = %d, STATE_PICK = %d", _state & STATE_BBOX, _state & STATE_PICK); + if (_item) { //not removed + g_warning("Invalid state when picking: STATE_BBOX = %d, STATE_PICK = %d", _state & STATE_BBOX, _state & STATE_PICK); + } return nullptr; } // ignore invisible and insensitive items unless sticky diff --git a/src/document.cpp b/src/document.cpp index 7847e30aab..5b98798f8b 100644 --- a/src/document.cpp +++ b/src/document.cpp @@ -54,6 +54,7 @@ #include "inkscape-window.h" #include "profile-manager.h" #include "rdf.h" +#include "style.h" #include "live_effects/effect.h" @@ -1030,6 +1031,15 @@ void SPDocument::do_change_filename(gchar const *const filename, bool const reba this->filename_set_signal.emit(this->document_filename); } +/** set basename or other human-readable label for the document. */ +void +SPDocument::setDocumentName(const gchar *docname) { + if (document_name) { + g_free(document_name); + } + document_name = g_strdup(docname); +} + /** * Sets base, name and filename members of \a document. Doesn't update * any relative hrefs in the document: thus, this is primarily for @@ -1886,7 +1896,6 @@ void SPDocument::importDefs(SPDocument *source) Inkscape::XML::Node *root = source->getReprRoot(); Inkscape::XML::Node *target_defs = this->getDefs()->getRepr(); std::vector defsNodes = sp_repr_lookup_name_many(root, "svg:defs"); - prevent_id_clashes(source, this); for (auto & defsNode : defsNodes) { @@ -1953,32 +1962,28 @@ void SPDocument::_importDefsNode(SPDocument *source, Inkscape::XML::Node *defs, if (src && (s_gr || s_lpeobj)) { for (auto& trg: getDefs()->children) { auto t_gr = cast(&trg); + bool exists = false; if (src != &trg && s_gr && t_gr) { if (s_gr->isEquivalent(t_gr)) { - // Change object references to the existing equivalent gradient - Glib::ustring newid = trg.getId(); - if (newid != defid) { // id could be the same if it is a second paste into the same document - change_def_references(src, &trg); - } - gchar *longid = g_strdup_printf("%s_%9.9d", DuplicateDefString.c_str(), stagger++); - def->setAttribute("id", longid); - g_free(longid); - // do NOT break here, there could be more than 1 duplicate! + exists = true; } } auto t_lpeobj = cast(&trg); if (src != &trg && s_lpeobj && t_lpeobj) { if (t_lpeobj->is_similar(s_lpeobj)) { - // Change object references to the existing equivalent gradient - Glib::ustring newid = trg.getId(); - if (newid != defid) { // id could be the same if it is a second paste into the same document - change_def_references(src, &trg); - } - gchar *longid = g_strdup_printf("%s_%9.9d", DuplicateDefString.c_str(), stagger++); - def->setAttribute("id", longid); - g_free(longid); - // do NOT break here, there could be more than 1 duplicate! + exists = true; + } + } + if (exists) { + // Change object references to the existing equivalent gradient + Glib::ustring newid = trg.getId(); + if (newid != defid) { // id could be the same if it is a second paste into the same document + change_def_references(src, &trg); } + gchar *longid = g_strdup_printf("%s_%9.9d", DuplicateDefString.c_str(), stagger++); + def->setAttribute("id", longid); + g_free(longid); + // do NOT break here, there could be more than 1 duplicate! } } } @@ -2056,16 +2061,13 @@ void SPDocument::_importDefsNode(SPDocument *source, Inkscape::XML::Node *defs, for (auto& trg: getDefs()->children) { if (is(&trg) && src != &trg) { std::string id2 = trg.getRepr()->attribute("id"); - - if( !id.compare( id2 ) ) { + if( !id.compare( id2 ) /* && !g_strcmp0(trg.title(), src->title()) */) { duplicate = true; break; } } } - if ( !duplicate ) { - src->setAttribute("id", id); - } + rename_id(src, id); } } diff --git a/src/document.h b/src/document.h index 1d67fab2a8..cb15e56fd1 100644 --- a/src/document.h +++ b/src/document.h @@ -232,6 +232,8 @@ public: /** basename or other human-readable label for the document. */ char const* getDocumentName() const { return document_name; } + /** set basename or other human-readable label for the document. */ + void setDocumentName(const gchar *docname); // Document geometry ------------------------ Inkscape::Util::Unit const* getDisplayUnit(); diff --git a/src/live_effects/lpeobject.cpp b/src/live_effects/lpeobject.cpp index 4cf4a97bc3..5687a66a50 100644 --- a/src/live_effects/lpeobject.cpp +++ b/src/live_effects/lpeobject.cpp @@ -144,7 +144,7 @@ bool LivePathEffectObject::is_similar(LivePathEffectObject *that) } Inkscape::LivePathEffect::Effect *thislpe = this->get_lpe(); Inkscape::LivePathEffect::Effect *thatlpe = that->get_lpe(); - if (thatlpe && thislpe && thislpe->getName() != thatlpe->getName()) { + if (!thatlpe || !thislpe || thislpe->getName() != thatlpe->getName()) { return false; } } diff --git a/src/object/sp-lpe-item.cpp b/src/object/sp-lpe-item.cpp index a0e8b57d44..2adb1aaccc 100755 --- a/src/object/sp-lpe-item.cpp +++ b/src/object/sp-lpe-item.cpp @@ -44,6 +44,7 @@ #include "sp-path.h" #include "sp-rect.h" #include "sp-root.h" +#include "sp-symbol.h" #include "svg/svg.h" #include "ui/shape-editor.h" #include "uri.h" @@ -83,7 +84,7 @@ SPLPEItem::~SPLPEItem() = default; void SPLPEItem::build(SPDocument *document, Inkscape::XML::Node *repr) { this->readAttr(SPAttr::INKSCAPE_PATH_EFFECT); - + onsymbol = isOnSymbol(); SPItem::build(document, repr); } @@ -218,6 +219,10 @@ bool SPLPEItem::isOnClipboard() return clipnode != nullptr; } +bool SPLPEItem::isOnSymbol() const { + auto p = cast(parent); + return (p && p->onsymbol) || is(this); +} /** * returns true when LPE was successful. */ @@ -261,8 +266,8 @@ bool SPLPEItem::performPathEffect(SPCurve *curve, SPShape *current, bool is_clip bool SPLPEItem::performOnePathEffect(SPCurve *curve, SPShape *current, Inkscape::LivePathEffect::Effect *lpe, bool is_clip_or_mask) { if (!lpe) { /** \todo Investigate the cause of this. - * Not sure, but I think this can happen when an unknown effect type is specified... - */ + * Not sure, but I think this can happen when an unknown effect type is specified... + */ g_warning("SPLPEItem::performPathEffect - lpeobj with invalid lpe in the stack!"); return false; } @@ -1685,8 +1690,8 @@ bool SPLPEItem::forkPathEffectsIfNecessary(unsigned int nr_of_allowed_users, boo std::vector item_list = group->item_list(); for (auto child:item_list) { auto lpeitem = cast(child); - if (lpeitem && lpeitem->forkPathEffectsIfNecessary(nr_of_allowed_users, recursive)) { - forked = true; + if (lpeitem) { + lpeitem->forkPathEffectsIfNecessary(nr_of_allowed_users, recursive, force); } } } @@ -1736,8 +1741,18 @@ bool SPLPEItem::forkPathEffectsIfNecessary(unsigned int nr_of_allowed_users, boo } // Enable or disable the path effects of the item. -void sp_lpe_item_enable_path_effects(SPLPEItem *lpeitem, bool enable) +void sp_lpe_item_enable_path_effects(SPLPEItem *lpeitem, bool enable, bool recursive) { + auto group = cast(lpeitem); + if (group && recursive) { + std::vector item_list = group->item_list(); + for (auto child:item_list) { + auto lpeitemchild = cast(child); + if (lpeitemchild) { + sp_lpe_item_enable_path_effects(lpeitemchild, enable, recursive); + } + } + } if (enable) { lpeitem->path_effects_enabled++; } @@ -1749,7 +1764,7 @@ void sp_lpe_item_enable_path_effects(SPLPEItem *lpeitem, bool enable) // Are the path effects enabled on this item ? bool SPLPEItem::pathEffectsEnabled() const { - return path_effects_enabled > 0; + return !onsymbol && path_effects_enabled > 0; } /* diff --git a/src/object/sp-lpe-item.h b/src/object/sp-lpe-item.h index 4cef73ebcf..bfc9b29ac4 100644 --- a/src/object/sp-lpe-item.h +++ b/src/object/sp-lpe-item.h @@ -91,6 +91,8 @@ public: std::vector getPathEffects() const; std::vector get_satellites(bool force = true, bool recursive = false, bool onchilds = false); bool isOnClipboard(); + bool isOnSymbol() const; + bool onsymbol = false; bool hasBrokenPathEffect() const; bool lpe_initialized = false; PathEffectList getEffectList(); @@ -124,7 +126,7 @@ public: void update_satellites(bool recursive = true); }; void sp_lpe_item_update_patheffect (SPLPEItem *lpeitem, bool wholetree, bool write, bool with_satellites = false); // careful, class already has method with *very* similar name! -void sp_lpe_item_enable_path_effects(SPLPEItem *lpeitem, bool enable); +void sp_lpe_item_enable_path_effects(SPLPEItem *lpeitem, bool enable, bool recursive = false); #endif /* !SP_LPE_ITEM_H_SEEN */ diff --git a/src/object/sp-use.cpp b/src/object/sp-use.cpp index 1fd55bc201..afc0adb698 100644 --- a/src/object/sp-use.cpp +++ b/src/object/sp-use.cpp @@ -741,7 +741,7 @@ SPItem *SPUse::unlink() { // Track the ultimate source of a chain of uses. SPItem *orig = this->root(); - + SPItem *origtrue = this->trueOriginal(); if (!orig) { return nullptr; } @@ -754,29 +754,44 @@ SPItem *SPUse::unlink() { if (auto symbol = cast(orig)) { // make a group, copy children copy = xml_doc->createElement("svg:g"); + copy->setAttribute("display","none"); for (Inkscape::XML::Node *child = orig->getRepr()->firstChild() ; child != nullptr; child = child->next()) { - Inkscape::XML::Node *newchild = child->duplicate(xml_doc); - copy->appendChild(newchild); + Inkscape::XML::Node *newchild = child->duplicate(xml_doc); + copy->appendChild(newchild); } // viewBox transformation t = symbol->c2p * t; } else { // just copy copy = orig->getRepr()->duplicate(xml_doc); + copy->setAttribute("display","none"); } - // Add the duplicate repr just after the existing one. parent->addChild(copy, repr); // Retrieve the SPItem of the resulting repr. SPObject *unlinked = document->getObjectByRepr(copy); - + if (origtrue) { + if (unlinked) { + origtrue->setTmpSuccessor(unlinked); + } + auto newLPEObj = cast(unlinked); + if (newLPEObj) { + // force always fork + newLPEObj->forkPathEffectsIfNecessary(1, true, true); + sp_lpe_item_update_patheffect(newLPEObj, false, true, true); + } + origtrue->fixTmpSuccessors(); + origtrue->unsetTmpSuccessor(); + } + // Merge style from the use. unlinked->style->merge( this->style ); unlinked->style->cascade( unlinked->parent->style ); unlinked->updateRepr(); - + unlinked->removeAttribute("display"); + // Hold onto our SPObject and repr for now. sp_object_ref(this); Inkscape::GC::anchor(repr); @@ -809,6 +824,7 @@ SPItem *SPUse::unlink() { // Advertise ourselves as not moving. item->doWriteTransform(t, &nomove); } + document->fix_lpe_data(); return item; } diff --git a/src/preferences-skeleton.h b/src/preferences-skeleton.h index 57906919fe..3327be9507 100644 --- a/src/preferences-skeleton.h +++ b/src/preferences-skeleton.h @@ -90,6 +90,7 @@ static char const preferences_skeleton[] = + getObjectsByElement("symbol"); + std::vectorids; + for (auto &symb : symbols) { + if (symb->getRepr() && symb->getAttribute("id")) { + Glib::ustring id = symb->getAttribute("id"); + size_t pos = id.find( "_inkscape_duplicate" ); + if( pos != Glib::ustring::npos ) { + // This is our symbol, now get rid of tag + id.erase( pos ); + ids.push_back(id); + rename_id(symb, id); + } + } + } + document->importDefs(_clipboardSPDoc.get()); + for (auto &id : ids) { + auto symb = _clipboardSPDoc.get()->getObjectById(id); + if (symb) { + id += "_inkscape_duplicate"; + rename_id(symb, id); + } + } + } +} + /** * Copy a symbol from the symbol dialog. * @@ -341,7 +372,9 @@ void ClipboardManagerImpl::copySymbol(Inkscape::XML::Node* symbol, gchar const* _discardInternalClipboard(); _createInternalClipboard(); - + + auto original = cast(source->getObjectByRepr(symbol)); + _copyUsedDefs(original); // We add "_duplicate" to have a well defined symbol name that // bypasses the "prevent_id_classes" routine. We'll get rid of it // when we paste. @@ -351,51 +384,56 @@ void ClipboardManagerImpl::copySymbol(Inkscape::XML::Node* symbol, gchar const* symbol_name += "_inkscape_duplicate"; repr->setAttribute("id", symbol_name); _defs->appendChild(repr); - - auto scale = _clipboardSPDoc->getDocumentScale(); - if (auto group = cast(_clipboardSPDoc->getObjectByRepr(repr))) { + auto nsymbol = cast(_clipboardSPDoc->getObjectById(symbol_name)); + if (nsymbol) { + nsymbol->setAttribute("inkscape:symbol-style", style); + const gchar *stylewrap = source->getRoot()->getAttribute("style"); + source->getRoot()->setAttribute("style", nullptr); + _copyCompleteStyle(original, repr, true); + source->getRoot()->setAttribute("style", stylewrap); + auto scale = _clipboardSPDoc->getDocumentScale(); // Convert scale from source to clipboard user units - group->scaleChildItemsRec(scale, Geom::Point(0, 0), false); - } - - auto href = Glib::ustring("#") + symbol->attribute("id"); - Inkscape::XML::Node *use_repr = _doc->createElement("svg:use"); - use_repr->setAttribute("xlink:href", href); - - /** - * If the symbol has a viewBox but no width or height, then take width and - * height from the viewBox and set them on the use element. Otherwise, the - * use element will have 100% document width and height! - */ - { - auto widthAttr = symbol->attribute("width"); - auto heightAttr = symbol->attribute("height"); - auto viewBoxAttr = symbol->attribute("viewBox"); - - if (viewBoxAttr && !(heightAttr || widthAttr)) { - SPViewBox vb; - vb.set_viewBox(viewBoxAttr); - if (vb.viewBox_set) { - use_repr->setAttributeSvgDouble("width", vb.viewBox.width()); - use_repr->setAttributeSvgDouble("height", vb.viewBox.height()); + nsymbol->scaleChildItemsRec(scale, Geom::Point(0, 0), false); + if (!nsymbol->title()) { + nsymbol->setTitle(nsymbol->label() ? nsymbol->label() : nsymbol->getId()); + } + auto href = Glib::ustring("#") + symbol_name; + Inkscape::XML::Node *use_repr = _doc->createElement("svg:use"); + use_repr->setAttribute("xlink:href", href); + /** + * If the symbol has a viewBox but no width or height, then take width and + * height from the viewBox and set them on the use element. Otherwise, the + * use element will have 100% document width and height! + */ + { + auto widthAttr = symbol->attribute("width"); + auto heightAttr = symbol->attribute("height"); + auto viewBoxAttr = symbol->attribute("viewBox"); + if (viewBoxAttr && !(heightAttr || widthAttr)) { + SPViewBox vb; + vb.set_viewBox(viewBoxAttr); + if (vb.viewBox_set) { + use_repr->setAttributeSvgDouble("width", vb.viewBox.width()); + use_repr->setAttributeSvgDouble("height", vb.viewBox.height()); + } } } - } + // Set a default style in rather than so it can be changed. + use_repr->setAttribute("style", style); + _root->appendChild(use_repr); + // because a extrange reason on append use getObjectsByElement("symbol") return 2 elements, + // it not give errrost by the moment; + if (auto use = cast(_clipboardSPDoc->getObjectByRepr(use_repr))) { + Geom::Affine affine = source->getDocumentScale(); + use->doWriteTransform(affine, &affine, false); + } - // Set a default style in rather than so it can be changed. - use_repr->setAttribute("style", style); - _root->appendChild(use_repr); + // Set min and max offsets based on the bounding rectangle. + _clipnode->setAttributePoint("min", bbox.min()); + _clipnode->setAttributePoint("max", bbox.max()); - if (auto use = cast(_clipboardSPDoc->getObjectByRepr(use_repr))) { - Geom::Affine affine = source->getDocumentScale(); - use->doWriteTransform(affine, &affine, false); + fit_canvas_to_drawing(_clipboardSPDoc.get()); } - - // Set min and max offsets based on the bounding rectangle. - _clipnode->setAttributePoint("min", bbox.min()); - _clipnode->setAttributePoint("max", bbox.max()); - - fit_canvas_to_drawing(_clipboardSPDoc.get()); _setClipboardTargets(); } diff --git a/src/ui/clipboard.h b/src/ui/clipboard.h index b2537ed5aa..ef50fb2f7e 100644 --- a/src/ui/clipboard.h +++ b/src/ui/clipboard.h @@ -18,6 +18,7 @@ #include #include <2geom/point.h> #include <2geom/rect.h> +#include // forward declarations class SPDesktop; @@ -43,6 +44,7 @@ class ClipboardManager { public: virtual void copy(ObjectSet *set) = 0; virtual void copyPathParameter(Inkscape::LivePathEffect::PathParam *) = 0; + virtual void copyClipboardDefsToDoc(SPDocument *document) = 0; virtual void copySymbol(Inkscape::XML::Node* symbol, gchar const* style, SPDocument *source, Geom::Rect const &bbox) = 0; virtual void insertSymbol(SPDesktop *desktop, Geom::Point const &shift_dt) = 0; virtual bool paste(SPDesktop *desktop, bool in_place = false, bool on_page = false) = 0; @@ -60,7 +62,6 @@ protected: private: ClipboardManager(const ClipboardManager &) = delete; ///< no copy ClipboardManager &operator=(const ClipboardManager &) = delete; ///< no assign - static ClipboardManager *_instance; }; diff --git a/src/ui/dialog/inkscape-preferences.cpp b/src/ui/dialog/inkscape-preferences.cpp index 7d3f88ac30..1a2263ace4 100644 --- a/src/ui/dialog/inkscape-preferences.cpp +++ b/src/ui/dialog/inkscape-preferences.cpp @@ -861,11 +861,13 @@ void InkscapePreferences::initPageTools() // shapes Gtk::TreeModel::iterator iter_shapes = this->AddPage(_page_shapes, _("Shapes"), iter_tools, PREFS_PAGE_TOOLS_SHAPES); + this->AddPage(_page_rectangle, get_tool_action_name("Rect"), iter_shapes, PREFS_PAGE_TOOLS_SHAPES_RECT); this->AddPage(_page_ellipse, get_tool_action_name("Arc"), iter_shapes, PREFS_PAGE_TOOLS_SHAPES_ELLIPSE); this->AddPage(_page_star, get_tool_action_name("Star"), iter_shapes, PREFS_PAGE_TOOLS_SHAPES_STAR); this->AddPage(_page_3dbox, get_tool_action_name("3DBox"), iter_shapes, PREFS_PAGE_TOOLS_SHAPES_3DBOX); this->AddPage(_page_spiral, get_tool_action_name("Spiral"), iter_shapes, PREFS_PAGE_TOOLS_SHAPES_SPIRAL); + this->AddPage(_page_symbol, get_tool_action_name("Symbol"), iter_shapes, PREFS_PAGE_TOOLS_SHAPES_SYMBOL); this->AddPage(_page_pen, get_tool_action_name("Pen"), iter_tools, PREFS_PAGE_TOOLS_PEN); this->AddPage(_page_pencil, get_tool_action_name("Pencil"), iter_tools, PREFS_PAGE_TOOLS_PENCIL); @@ -990,6 +992,11 @@ void InkscapePreferences::initPageTools() //Spiral this->AddNewObjectsStyle(_page_spiral, "/tools/shapes/spiral"); + //Symbol + _fast_symbol_number.init("/tools/shapes/symbol/ntoolbarsymbols", 1.0, 20.0, 1.0, 1.0, 7.0, true, false); + _page_symbol.add_line(false, _("_Max symbols in toolbar:"), _fast_symbol_number, "", _("Specifies how many icons show up in toolbar"), false); + this->AddNewObjectsStyle(_page_symbol, "/tools/shapes/symbol"); + //Pencil this->AddSelcueCheckbox(_page_pencil, "/tools/freehand/pencil", true); this->AddNewObjectsStyle(_page_pencil, "/tools/freehand/pencil"); diff --git a/src/ui/dialog/inkscape-preferences.h b/src/ui/dialog/inkscape-preferences.h index ebb614e83a..2a55a9db98 100644 --- a/src/ui/dialog/inkscape-preferences.h +++ b/src/ui/dialog/inkscape-preferences.h @@ -67,6 +67,7 @@ enum PREFS_PAGE_TOOLS_SHAPES_ELLIPSE, PREFS_PAGE_TOOLS_SHAPES_STAR, PREFS_PAGE_TOOLS_SHAPES_SPIRAL, + PREFS_PAGE_TOOLS_SHAPES_SYMBOL, PREFS_PAGE_TOOLS_PENCIL, PREFS_PAGE_TOOLS_PEN, PREFS_PAGE_TOOLS_CALLIGRAPHY, @@ -176,6 +177,7 @@ protected: UI::Widget::DialogPage _page_ellipse; UI::Widget::DialogPage _page_star; UI::Widget::DialogPage _page_spiral; + UI::Widget::DialogPage _page_symbol; UI::Widget::DialogPage _page_paintbucket; UI::Widget::DialogPage _page_eraser; @@ -566,7 +568,8 @@ protected: UI::Widget::PrefCheckButton _svgexport_remove_marker_auto_start_reverse; UI::Widget::PrefCheckButton _svgexport_remove_marker_context_paint; - + // SYMBOL TOOL page + UI::Widget::PrefSpinButton _fast_symbol_number; Gtk::Notebook _kb_notebook; UI::Widget::DialogPage _kb_page_shortcuts; UI::Widget::DialogPage _kb_page_modifiers; diff --git a/src/ui/dialog/symbols.cpp b/src/ui/dialog/symbols.cpp index ac49bbd60b..5c6c915461 100644 --- a/src/ui/dialog/symbols.cpp +++ b/src/ui/dialog/symbols.cpp @@ -44,8 +44,11 @@ #include "ui/clipboard.h" #include "ui/icon-loader.h" #include "ui/icon-names.h" +#include "ui/toolbar/symbol-toolbar.h" +#include "ui/tools/symbol-tool.h" #include "ui/widget/scrollprotected.h" #include "xml/href-attribute-helper.h" +#include "widgets/toolbox.h" #ifdef WITH_LIBVISIO #include @@ -65,6 +68,26 @@ namespace UI { namespace Dialog { static std::map > symbol_sets; + +/* + * Return empty doc to render symbols in. + * Symbols are by default not rendered so a element is + * provided. + */ +SPDocument* sp_symbols_preview_doc() +{ + // BUG: must be inside + gchar const *buffer = +"" +" " +" " +""; + return SPDocument::createNewDocFromMem( buffer, strlen(buffer), FALSE ); +} + /** * Constructor */ @@ -74,7 +97,6 @@ SymbolsDialog::SymbolsDialog(gchar const *prefsPath) , store(Gtk::ListStore::create(_columns)) , all_docs_processed(false) , icon_view(nullptr) - , preview_document(nullptr) , gtk_connections() , CURRENTDOC(_("Current document")) , ALLDOCS(_("All symbol sets")) @@ -153,9 +175,6 @@ SymbolsDialog::SymbolsDialog(gchar const *prefsPath) icon_view->signal_drag_data_get().connect(sigc::mem_fun(*this, &SymbolsDialog::iconDragDataGet))); gtk_connections.emplace_back( icon_view->signal_selection_changed().connect(sigc::mem_fun(*this, &SymbolsDialog::iconChanged))); - gtk_connections.emplace_back( - icon_view->signal_drag_begin().connect( - [=](Glib::RefPtr const &) { onDragStart(); })); gtk_connections.emplace_back(icon_view->signal_button_press_event().connect([=](GdkEventButton *ev) -> bool { _last_mousedown = {ev->x, ev->y - icon_view->get_vadjustment()->get_value()}; return false; @@ -313,10 +332,6 @@ SymbolsDialog::SymbolsDialog(gchar const *prefsPath) sensitive = true; - preview_document = symbolsPreviewDoc(); /* Template to render symbols in */ - key = SPItem::display_key_new(1); - renderDrawing.setRoot(preview_document->getRoot()->invoke_show(renderDrawing, key, SP_ITEM_SHOW_DISPLAY )); - getSymbolsTitle(); icons_found = false; } @@ -328,10 +343,47 @@ SymbolsDialog::~SymbolsDialog() } gtk_connections.clear(); idleconn.disconnect(); +} - Inkscape::GC::release(preview_document); - assert(preview_document->_anchored_refcount() == 0); - delete preview_document; +void useInDoc (SPObject *r, std::vector &l) +{ + + if (is(r) ) { + l.push_back(cast(r)); + } + + for (auto& child: r->children) { + useInDoc( &child, l ); + } +} + +std::vector useInDoc( SPDocument* useDocument) { + std::vector l; + useInDoc (useDocument->getRoot(), l); + return l; +} + +// Returns style from first element found that references id. +// This is a last ditch effort to find a style. +gchar const* styleFromUse( gchar const* id, SPDocument* document) { + + gchar const* style = nullptr; + std::vector l = useInDoc( document ); + for( auto use:l ) { + if ( use ) { + gchar const *href = Inkscape::getHrefAttribute(*use->getRepr()).second; + if( href ) { + Glib::ustring href2(href); + Glib::ustring id2(id); + id2 = "#" + id2; + if( !href2.compare(id2) ) { + style = use->getRepr()->attribute("style"); + break; + } + } + } + } + return style; } bool findString(const Glib::ustring & haystack, const Glib::ustring & needle) @@ -493,9 +545,14 @@ void SymbolsDialog::iconDragDataGet(const Glib::RefPtr& /*cont void SymbolsDialog::defsModified(SPObject * /*object*/, guint /*flags*/) { + static size_t nsymbols = 0; Glib::ustring doc_title = get_active_base_text(); - if (doc_title != ALLDOCS && !symbol_sets[doc_title].second ) { - rebuild(); + if (doc_title != ALLDOCS && !symbol_sets[doc_title].second) { + size_t nsyms = getDocument()->getObjectsByElement("symbol").size(); + if (nsymbols != nsyms) { + rebuild(); + nsymbols = nsyms; + } } } @@ -633,76 +690,63 @@ void SymbolsDialog::sendToClipboard(Gtk::TreeModel::Path const &symbol_path, Geo if (SPObject* symbol = symbol_document->getObjectById(symbol_id)) { // Find style for use in // First look for default style stored in - gchar const* style = symbol->getAttribute("inkscape:symbol-style"); + gchar const* style = styleFromUse(symbol_id.c_str(), symbol_document); if (!style) { - // If no default style in , look in documents. - if (symbol_document == getDocument()) { - style = styleFromUse(symbol_id.c_str(), symbol_document); - } else { - style = symbol_document->getReprRoot()->attribute("style"); - } + style = symbol_document->getReprRoot()->attribute("style"); + } + auto symbolis = cast(symbol); + if (!symbolis) { + return; } + // Update all GUI text + std::list< SPDesktop* > listbuf; + // Get list of all available desktop + INKSCAPE.get_all_desktops(listbuf); auto const dims = getSymbolDimensions(symbol_path); ClipboardManager *cm = ClipboardManager::get(); - cm->copySymbol(symbol->getRepr(), style, symbol_document, bbox); + cm->copySymbol(symbolis->getRepr(), style, symbol_document, bbox); + Inkscape::UI::Tools::SymbolTool *st = dynamic_cast(getDesktop()->event_context); + if (st) { + static const char *const tmpSVGOutput = R"""( + + + Symbol Tool (hidden) + + +)"""; + std::shared_ptr doc(SPDocument::createNewDocFromMem( tmpSVGOutput, strlen( tmpSVGOutput), false )); + if (doc) { + cm->copyClipboardDefsToDoc(doc.get()); + doc->setDocumentName("symbol-tool.svg"); + st->set_symbols_tool(doc); + for(SPDesktop *desktop: listbuf) { + auto *sb = dynamic_cast(desktop->get_toolbar_by_name("SymbolToolbar")); + if (sb) { + sb->reloadSymbols(desktop, st); + } + } + } + } } + sensitive = false; + icon_view->select_path(symbol_path); + sensitive = true; } void SymbolsDialog::iconChanged() { + if (!sensitive) { + return; + } if (auto selected = getSelected()) { auto const dims = getSymbolDimensions(selected); sendToClipboard(*selected, Geom::Rect(-0.5 * dims, 0.5 * dims)); } } -/** Handle the start of a drag on a symbol preview icon. */ -void SymbolsDialog::onDragStart() -{ - auto selected = getSelected(); - if (!selected) { - return; - } - - // Get the rectangle of the cell where the drag started. - Gdk::Rectangle temprect; - icon_view->get_cell_rect(*selected, temprect); - auto cell_rect = Geom::IntRect::from_xywh({temprect.get_x(), temprect.get_y()}, - {temprect.get_width(), temprect.get_height()}); - - // Find the rectangle of the actual symbol preview - // (not the same as the cell rectangle, due to fitting and padding). - auto const dims = getSymbolDimensions(selected); - unsigned preview_size = SYMBOL_ICON_SIZES[pack_size]; - Geom::Dim2 larger_dim = dims[Geom::X] > dims[Geom::Y] ? Geom::X : Geom::Y; - Geom::Dim2 smaller_dim = (Geom::Dim2)(!larger_dim); - Geom::Rect preview_rect; ///< The actual rectangle taken up by the bbox of the rendered preview. - - Geom::Interval larger_interval = cell_rect[larger_dim]; - larger_interval.expandBy(0.5 * (preview_size - larger_interval.extent())); // Trim off the padding. - preview_rect[larger_dim] = larger_interval; - - double const proportionally_scaled_smaller = preview_size * dims[smaller_dim] / dims[larger_dim]; - double const smaller_trim = 0.5 * (cell_rect[smaller_dim].extent() - proportionally_scaled_smaller); - Geom::Interval smaller_interval = cell_rect[smaller_dim]; - smaller_interval.expandBy(-smaller_trim); // Trim off padding and the "letterboxes" for non-square bbox. - preview_rect[smaller_dim] = smaller_interval; - - // Map the last mousedown position to [0, 1] x [0, 1] coordinates in the preview rectangle. - Geom::Point normalized_position = _last_mousedown - preview_rect.min(); - normalized_position.x() = std::clamp(normalized_position.x() / preview_rect.width(), 0.0, 1.0); - normalized_position.y() /= preview_rect.height(); - if (auto desktop = getDesktop(); desktop && !desktop->is_yaxisdown()) { - normalized_position.y() = 1.0 - normalized_position.y(); - } - normalized_position.y() = std::clamp(normalized_position.y(), 0.0, 1.0); - - // Push the symbol into the private clipboard with the correct bounding box. This box has dimensions - // `dims` but is positioned in such a way that the origin point (0, 0) lies at `normalized_position`. - auto const box_position = -Geom::Point(normalized_position.x() * dims.x(), normalized_position.y() * dims.y()); - sendToClipboard(*selected, Geom::Rect::from_xywh(box_position, dims)); -} - #ifdef WITH_LIBVISIO // Extend libvisio's native RVNGSVGDrawingGenerator with support for extracting stencil names (to be used as ID/title) @@ -845,12 +889,17 @@ void SymbolsDialog::getSymbolsTitle() { } else { std::ifstream infile(filename); std::string line; + std::string pline = ""; //stored titles can be in 2 lines while (std::getline(infile, line)) { - std::string title_res = std::regex_replace (line, matchtitle,"$1",std::regex_constants::format_no_copy); + std::string title_res = std::regex_replace (pline + line, matchtitle,"$1",std::regex_constants::format_no_copy); + pline = line; if (!title_res.empty()) { + if (title_res == "Symbol Tool (hidden)") { + break; + } title_res = g_dpgettext2(nullptr, "Symbol", title_res.c_str()); if (!symbol_sets[filename].second) - symbol_sets[filename]= std::make_pair(Glib::ustring(title_res),nullptr); + symbol_sets[filename]= std::make_pair(title_res,nullptr); ++number_docs; break; } @@ -930,9 +979,9 @@ void SymbolsDialog::symbolsInDocRecursive (SPObject *r, std::mapgetAttribute("id"); gchar * title = r->title(); if(title) { - l[doc_title + title + id] = std::make_pair(doc_title,cast(r)); + l[Glib::ustring::format(l.size()) + "_" + doc_title + title + id] = std::make_pair(doc_title,cast(r)); } else { - l[Glib::ustring(_("notitle_")) + id] = std::make_pair(doc_title,cast(r)); + l[Glib::ustring::format(l.size()) + "_" + Glib::ustring(_("notitle_")) + id] = std::make_pair(doc_title,cast(r)); } g_free(title); } @@ -952,47 +1001,6 @@ SymbolsDialog::symbolsInDoc( SPDocument* symbol_document, Glib::ustring doc_titl return l; } -void SymbolsDialog::useInDoc (SPObject *r, std::vector &l) -{ - - if (is(r) ) { - l.push_back(cast(r)); - } - - for (auto& child: r->children) { - useInDoc( &child, l ); - } -} - -std::vector SymbolsDialog::useInDoc( SPDocument* useDocument) { - std::vector l; - useInDoc (useDocument->getRoot(), l); - return l; -} - -// Returns style from first element found that references id. -// This is a last ditch effort to find a style. -gchar const* SymbolsDialog::styleFromUse( gchar const* id, SPDocument* document) { - - gchar const* style = nullptr; - std::vector l = useInDoc( document ); - for( auto use:l ) { - if ( use ) { - gchar const *href = Inkscape::getHrefAttribute(*use->getRepr()).second; - if( href ) { - Glib::ustring href2(href); - Glib::ustring id2(id); - id2 = "#" + id2; - if( !href2.compare(id2) ) { - style = use->getRepr()->attribute("style"); - break; - } - } - } - } - return style; -} - void SymbolsDialog::clearSearch() { if(search->get_text().empty() && sensitive) { @@ -1039,9 +1047,9 @@ void SymbolsDialog::searchsymbols() icons_found = false; addSymbolsInDoc(symbol_document); } else { + search->set_text(_("Loading all symbols...")); idleconn.disconnect(); idleconn = Glib::signal_idle().connect(sigc::mem_fun(*this, &SymbolsDialog::callbackAllSymbols)); - search->set_text(_("Loading all symbols...")); } } @@ -1052,54 +1060,51 @@ void SymbolsDialog::unsensitive(GdkEventKey* evt) bool SymbolsDialog::callbackSymbols(){ icon_view->hide(); - if (l.size()) { - showOverlay(); - for (auto symbol_data = l.begin(); symbol_data != l.end();) { - Glib::ustring doc_title = symbol_data->second.first; - SPSymbol * symbol = symbol_data->second.second; - counter_symbols ++; - gchar *symbol_title_char = symbol->title(); - gchar *symbol_desc_char = symbol->description(); - bool found = false; - if (symbol_title_char) { - Glib::ustring symbol_title = Glib::ustring(symbol_title_char).lowercase(); - auto pos = symbol_title.rfind(search_str); - auto pos_translated = Glib::ustring(g_dpgettext2(nullptr, "Symbol", symbol_title_char)).lowercase().rfind(search_str); + showOverlay(); + for (auto symbol_data = l.begin(); symbol_data != l.end();) { + Glib::ustring doc_title = symbol_data->second.first; + SPSymbol * symbol = symbol_data->second.second; + counter_symbols ++; + gchar *symbol_title_char = symbol->title(); + gchar *symbol_desc_char = symbol->description(); + bool found = false; + if (symbol_title_char) { + Glib::ustring symbol_title = Glib::ustring(symbol_title_char).lowercase(); + auto pos = symbol_title.rfind(search_str); + auto pos_translated = Glib::ustring(g_dpgettext2(nullptr, "Symbol", symbol_title_char)).lowercase().rfind(search_str); + if ((pos != std::string::npos) || (pos_translated != std::string::npos)) { + found = true; + } + if (!found && symbol_desc_char) { + Glib::ustring symbol_desc = Glib::ustring(symbol_desc_char).lowercase(); + auto pos = symbol_desc.rfind(search_str); + auto pos_translated = Glib::ustring(g_dpgettext2(nullptr, "Symbol", symbol_desc_char)).lowercase().rfind(search_str); if ((pos != std::string::npos) || (pos_translated != std::string::npos)) { found = true; } - if (!found && symbol_desc_char) { - Glib::ustring symbol_desc = Glib::ustring(symbol_desc_char).lowercase(); - auto pos = symbol_desc.rfind(search_str); - auto pos_translated = Glib::ustring(g_dpgettext2(nullptr, "Symbol", symbol_desc_char)).lowercase().rfind(search_str); - if ((pos != std::string::npos) || (pos_translated != std::string::npos)) { - found = true; - } - } - } - if (symbol && (search_str.empty() || found)) { - addSymbol( symbol, doc_title); - icons_found = true; } - symbol_data = l.erase(l.begin()); - //to get more items and best performance - g_free(symbol_title_char); - g_free(symbol_desc_char); } - if (!icons_found && !search_str.empty()) { - showOverlay(); - } else { - hideOverlay(); + if (symbol && (search_str.empty() || found)) { + addSymbol( symbol, doc_title); + icons_found = true; } - sensitive = false; - search->set_text(search_str); - sensitive = true; - enableWidgets(true); - icon_view->show(); - return false; + symbol_data = l.erase(l.begin()); + //to get more items and best performance + g_free(symbol_title_char); + g_free(symbol_desc_char); + } + if (!icons_found && !search_str.empty()) { + showOverlay(); + } else { + hideOverlay(); } + sensitive = false; + search->set_text(search_str); + sensitive = true; + enableWidgets(true); icon_view->show(); - return true; + Glib::ustring doc_title = get_active_base_text(); + return false; } Glib::ustring SymbolsDialog::get_active_base_text(Glib::ustring title) @@ -1117,32 +1122,24 @@ Glib::ustring SymbolsDialog::get_active_base_text(Glib::ustring title) bool SymbolsDialog::callbackAllSymbols(){ icon_view->hide(); Glib::ustring current = get_active_base_text(); - if (current == ALLDOCS && search->get_text() == _("Loading all symbols...")) { - size_t counter = 0; - std::map > symbol_sets_tmp = symbol_sets; - for(auto const &symbol_document_map : symbol_sets_tmp) { - ++counter; - Glib::ustring current = get_active_base_text(); - if (current == CURRENTDOC) { - return true; - } - SPDocument* symbol_document = symbol_document_map.second.second; - if (symbol_document) { - continue; - } - symbol_document = getSymbolsSet(symbol_document_map.first).second; - symbol_set->set_active_text(ALLDOCS); + size_t counter = 0; + std::map > symbol_sets_tmp = symbol_sets; + for(auto const &symbol_document_map : symbol_sets_tmp) { + ++counter; + SPDocument* symbol_document = symbol_document_map.second.second; + if (symbol_document) { + continue; } - symbol_sets_tmp.clear(); - hideOverlay(); - all_docs_processed = true; - addSymbols(); - search->set_text(search_str); - icon_view->show(); - return false; + symbol_document = getSymbolsSet(symbol_document_map.first).second; + symbol_set->set_active_text(ALLDOCS); } + symbol_sets_tmp.clear(); + hideOverlay(); + all_docs_processed = true; + addSymbols(); + search->set_text(search_str); icon_view->show(); - return true; + return false; } void SymbolsDialog::addSymbolsInDoc(SPDocument* symbol_document) { @@ -1244,34 +1241,45 @@ void SymbolsDialog::addSymbol(SPSymbol *symbol, Glib::ustring doc_title) Glib::RefPtr SymbolsDialog::drawSymbol(SPObject *symbol) { + return draw_symbol(symbol, SYMBOL_ICON_SIZES[pack_size], scale_factor, fit_symbol != nullptr ? fit_symbol->get_active() : true); +} +// set scale factor = 0 from external calls +Glib::RefPtr +SymbolsDialog::draw_symbol(SPObject *symbol, unsigned size, double scale_factor, bool fit_symbol) +{ + Glib::RefPtr pixbuf(nullptr); + if (!symbol) { + return pixbuf; + } + SPDocument *preview_document = sp_symbols_preview_doc(); + Inkscape::Drawing renderDrawing; + unsigned key = SPItem::display_key_new(1); // Create a copy repr of the symbol with id="the_symbol" Inkscape::XML::Node *repr = symbol->getRepr()->duplicate(preview_document->getReprDoc()); repr->setAttribute("id", "the_symbol"); - - // First look for default style stored in - gchar const* style = repr->attribute("inkscape:symbol-style"); - if(!style) { - // If no default style in , look in documents. - if(symbol->document == getDocument()) { - gchar const *id = symbol->getRepr()->attribute("id"); - style = styleFromUse( id, symbol->document ); - } else { - style = symbol->document->getReprRoot()->attribute("style"); + // look for default style stored in + gchar const* style = symbol->getAttribute("inkscape:symbol-style"); + if (!style) { + style = styleFromUse(symbol->getId(), symbol->document); + if (!style) { + style = symbol->document->getReprRoot()->attribute("style"); } } - // This is for display in Symbols dialog only if( style ) repr->setAttribute( "style", style ); - - SPDocument::install_reference_document scoped(preview_document, getDocument()); - preview_document->getDefs()->getRepr()->appendChild(repr); + const gchar *stylewrap = symbol->document->getRoot()->getAttribute("style"); + if (stylewrap) { + symbol->document->getRoot()->setAttribute("style", nullptr); + } + SPDocument::install_reference_document scoped(preview_document, symbol->document); + preview_document->getDefs()->getRepr()->appendChild(repr); Inkscape::GC::release(repr); - + renderDrawing.setRoot(preview_document->getRoot()->invoke_show(renderDrawing, key, SP_ITEM_SHOW_DISPLAY )); // Uncomment this to get the preview_document documents saved (useful for debugging) // FILE *fp = fopen (g_strconcat(id, ".svg", NULL), "w"); // sp_repr_save_stream(preview_document->getReprDoc(), fp); // fclose (fp); - + // Make sure preview_document is up-to-date. preview_document->ensureUpToDate(); @@ -1280,9 +1288,8 @@ SymbolsDialog::drawSymbol(SPObject *symbol) auto item = cast(object_temp); g_assert(item != nullptr); - unsigned psize = SYMBOL_ICON_SIZES[pack_size]; - - Glib::RefPtr pixbuf(nullptr); + unsigned psize = size; + // We could use cache here, but it doesn't really work with the structure // of this user interface and we've already cached the pixbuf in the gtklist @@ -1299,38 +1306,27 @@ SymbolsDialog::drawSymbol(SPObject *symbol) if( width == 0.0 ) width = 1.0; if( height == 0.0 ) height = 1.0; - - if( fit_symbol->get_active() ) + if (fit_symbol) { scale = psize / ceil(std::max(width, height)); - else + } else { scale = pow( 2.0, scale_factor/2.0 ) * psize / 32.0; - + } pixbuf = Glib::wrap(render_pixbuf(renderDrawing, scale, *dbox, psize)); } - - preview_document->getObjectByRepr(repr)->deleteObject(false); + auto lpeitem = cast(preview_document->getObjectById("the_symbol")); + if (lpeitem) { + sp_lpe_item_enable_path_effects(lpeitem, false, true); + } + if (stylewrap) { + symbol->document->getRoot()->setAttribute("style", stylewrap); + } + Inkscape::GC::release(preview_document); + assert(preview_document->_anchored_refcount() == 0); + delete preview_document; return pixbuf; } -/* - * Return empty doc to render symbols in. - * Symbols are by default not rendered so a element is - * provided. - */ -SPDocument* SymbolsDialog::symbolsPreviewDoc() -{ - // BUG: must be inside - gchar const *buffer = -"" -" " -""; - return SPDocument::createNewDocFromMem( buffer, strlen(buffer), FALSE ); -} - /* * Update image widgets */ diff --git a/src/ui/dialog/symbols.h b/src/ui/dialog/symbols.h index abbd40b781..d38da2b51e 100644 --- a/src/ui/dialog/symbols.h +++ b/src/ui/dialog/symbols.h @@ -76,7 +76,7 @@ class SymbolsDialog : public DialogBase public: SymbolsDialog(char const *prefsPath = "/dialogs/symbols"); ~SymbolsDialog() override; - + static Glib::RefPtr draw_symbol(SPObject *symbol, unsigned size, double scale_factor = 0, bool fit_symbol = true); private: void documentReplaced() override; void selectionChanged(Inkscape::Selection *selection) override; @@ -101,16 +101,12 @@ private: Glib::ustring getSymbolDocTitle(std::optional const &path) const; Geom::Point getSymbolDimensions(std::optional const &path) const; void iconDragDataGet(const Glib::RefPtr& context, Gtk::SelectionData& selection_data, guint info, guint time); - void onDragStart(); void getSymbolsTitle(); Glib::ustring documentTitle(SPDocument* doc); std::pair getSymbolsSet(std::string title); void addSymbol(SPSymbol *symbol, Glib::ustring doc_title); - SPDocument* symbolsPreviewDoc(); void symbolsInDocRecursive (SPObject *r, std::map > &l, Glib::ustring doc_title); std::map > symbolsInDoc( SPDocument* document, Glib::ustring doc_title); - void useInDoc(SPObject *r, std::vector &l); - std::vector useInDoc( SPDocument* document); void beforeSearch(GdkEventKey* evt); void unsensitive(GdkEventKey* evt); void searchsymbols(); @@ -123,7 +119,6 @@ private: bool callbackAllSymbols(); Glib::ustring get_active_base_text(Glib::ustring title = "selectedcombo"); void enableWidgets(bool enable); - gchar const* styleFromUse( gchar const* id, SPDocument* document); Glib::RefPtr drawSymbol(SPObject *symbol); Glib::RefPtr getOverlay(gint width, gint height); /* Keep track of all symbol template documents */ @@ -161,24 +156,15 @@ private: Gtk::ScrolledWindow *scroller; Gtk::ToggleButton* fit_symbol; Gtk::IconSize iconsize; - - SPDocument* preview_document; /* Document to render single symbol */ - sigc::connection idleconn; - /* For rendering the template drawing */ - unsigned key; - Inkscape::Drawing renderDrawing; - std::vector gtk_connections; Inkscape::auto_connection defs_modified; }; - -} //namespace Dialogs +} //namespace Dialog } //namespace UI } //namespace Inkscape - #endif // INKSCAPE_UI_DIALOG_SYMBOLS_H /* diff --git a/src/ui/knot/knot-holder.cpp b/src/ui/knot/knot-holder.cpp index c2a54afdf6..d3908098c9 100644 --- a/src/ui/knot/knot-holder.cpp +++ b/src/ui/knot/knot-holder.cpp @@ -33,6 +33,7 @@ #include "object/sp-rect.h" #include "object/sp-shape.h" #include "object/sp-spiral.h" +#include "object/sp-symbol.h" #include "object/sp-star.h" #include "object/sp-marker.h" #include "object/filters/gaussian-blur.h" @@ -44,6 +45,7 @@ #include "ui/tools/node-tool.h" #include "ui/tools/rect-tool.h" #include "ui/tools/spiral-tool.h" +#include "ui/tools/symbol-tool.h" #include "ui/tools/tweak-tool.h" #include "display/control/snap-indicator.h" @@ -184,6 +186,8 @@ KnotHolder::knot_clicked_handler(SPKnot *knot, guint state) icon_name = INKSCAPE_ICON("draw-polygon-star"); } else if (is(saved_item)) { icon_name = INKSCAPE_ICON("draw-spiral"); + } else if (is(saved_item)) { + icon_name = INKSCAPE_ICON("draw-symbol"); } else if (is(saved_item)) { icon_name = INKSCAPE_ICON("tool-pointer"); } else { diff --git a/src/ui/tool-factory.cpp b/src/ui/tool-factory.cpp index 7c9232d4a9..149ae080b9 100644 --- a/src/ui/tool-factory.cpp +++ b/src/ui/tool-factory.cpp @@ -32,6 +32,7 @@ #include "ui/tools/spiral-tool.h" #include "ui/tools/spray-tool.h" #include "ui/tools/star-tool.h" +#include "ui/tools/symbol-tool.h" #include "ui/tools/text-tool.h" #include "ui/tools/tweak-tool.h" #include "ui/tools/zoom-tool.h" @@ -82,6 +83,8 @@ ToolBase *ToolFactory::createObject(SPDesktop *desktop, std::string const &id) tool = new SelectTool(desktop); else if (id == "/tools/shapes/spiral") tool = new SpiralTool(desktop); + else if (id == "/tools/shapes/symbol") + tool = new SymbolTool(desktop); else if (id == "/tools/spray") tool = new SprayTool(desktop); else if (id == "/tools/shapes/star") diff --git a/src/ui/toolbar/symbol-toolbar.cpp b/src/ui/toolbar/symbol-toolbar.cpp new file mode 100644 index 0000000000..8854e855f9 --- /dev/null +++ b/src/ui/toolbar/symbol-toolbar.cpp @@ -0,0 +1,241 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** + * @file + * Symbol toolbar + */ +/* Authors: + * jabiertxof + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "symbol-toolbar.h" + +#include "desktop.h" +#include "selection.h" + +#include "object/sp-symbol.h" +#include "widgets/toolbox.h" + +#include "ui/icon-names.h" +#include "ui/icon-loader.h" +#include "ui/dialog/symbols.h" +#include "ui/widget/canvas.h" +#include "ui/widget/label-tool-item.h" +#include "ui/widget/spin-button-tool-item.h" +#include "ui/dialog/dialog-container.h" + +#include + + +namespace Inkscape { +namespace UI { +namespace Toolbar { + +SymbolToolbar::SymbolToolbar(SPDesktop *desktop) : + Toolbar(desktop), + _freeze(false), + _repr(nullptr) +{ + auto prefs = Inkscape::Preferences::get(); + /* Mode */ + { + add_label(_("Mode: ")); + + Gtk::RadioToolButton::Group mode_group; + + auto obj_mode_btn = Gtk::manage(new Gtk::RadioToolButton(mode_group, _("Draw with objects"))); + obj_mode_btn->set_tooltip_text(_("Draw using normal object from selected symbol")); + obj_mode_btn->set_icon_name(INKSCAPE_ICON("shape-path")); + _mode_buttons.push_back(obj_mode_btn); + + auto clone_mode_btn = Gtk::manage(new Gtk::RadioToolButton(mode_group, _("Draw with clones"))); + clone_mode_btn->set_tooltip_text(_("Draw using clones from selected symbol in document")); + clone_mode_btn->set_icon_name(INKSCAPE_ICON("symbols")); + _mode_buttons.push_back(clone_mode_btn); + int btn_idx = 0; + _mode_buttons[0]->set_active(true); + mode_changed(false); + for (auto btn : _mode_buttons) { + btn->set_sensitive(true); + add(*btn); + btn->signal_clicked().connect(sigc::bind(sigc::mem_fun(*this, &SymbolToolbar::mode_changed), btn_idx == 1)); + btn_idx++; + } + } + add(*Gtk::manage(new Gtk::SeparatorToolItem())); + /* Colorize */ + { + _colorize = Gtk::manage(new Gtk::ToggleToolButton()); + auto *label = Gtk::manage(new Gtk::Label(_("Colorize"))); + label->set_valign(Gtk::ALIGN_CENTER); + auto *icon = Gtk::manage(sp_get_icon_image("color-palette", Gtk::ICON_SIZE_BUTTON)); + auto *box = Gtk::manage(new Gtk::Box()); + box->pack_start(*icon, false, false); + box->pack_start(*label, false, false); + _colorize->set_icon_widget(*box); + _colorize->set_active(prefs->getBool("/tools/shapes/symbol/usecurrent", false)); + _colorize->signal_toggled().connect(sigc::bind(sigc::mem_fun(*this, &SymbolToolbar::on_pref_toggled), + _colorize, + "/tools/shapes/symbol/usecurrent")); + add(*_colorize); + } + add(*Gtk::manage(new Gtk::SeparatorToolItem())); + show_all(); +} + +SymbolToolbar::~SymbolToolbar() +{ + if(_connection) { + _connection->disconnect(); + } +} + +gint +SymbolToolbar::get_active_symbol() { + gint index = 0; + for (auto btn : _symbols_buttons) { + if (btn && btn->get_active()) { + return index; + } + index ++; + } + return 0; +} + +void +SymbolToolbar::mode_changed(bool useuse) +{ + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + prefs->setBool("/tools/shapes/symbol/useuse", useuse); +} + +void +SymbolToolbar::set_mode(bool useuse) +{ + _mode_buttons[useuse]->set_active(); +} + +void +SymbolToolbar::set_active_symbol(gint pos) { + gint index = 0; + for (auto btn : _symbols_buttons) { + if (index == pos && btn) { + btn->set_active(); + return; + } + index ++; + } +} + +void +SymbolToolbar::reloadSymbols(SPDesktop *desktop, Inkscape::UI::Tools::SymbolTool *st) +{ + if (!desktop) { + return; + } + gint current = get_active_symbol(); + for (auto radiobutton : _symbols_buttons) { + delete radiobutton; + } + if (_separa) { + delete _separa; + } + if (_open_dialog) { + delete _open_dialog; + } + _symbols_buttons.clear(); + auto prefs = Inkscape::Preferences::get(); + + gint maxsymbols = prefs->getInt("/tools/shapes/symbol/ntoolbarsymbols", 7); + Gtk::RadioToolButton::Group _symbol_buttons; + if (st) { + for (int i = 0; i < maxsymbols; i++) { + auto doc = st->get_symbols_tool(i); + if (doc) { + auto symbols = doc->getObjectsByElement("symbol"); + if (!symbols.empty()) { + auto symbol = cast(symbols[0]); + if (symbol) { + auto btn = Gtk::manage(new Gtk::RadioToolButton(_symbol_buttons, "symbol->title()")); + if (current == i) { + _freeze = true; + btn->set_active(true); + _freeze = false; + } + btn->set_tooltip_text(_("Paint using this symbol")); + int s = prefs->getIntLimited(ToolboxFactory::ctrlbars_icon_size, ToolboxFactory::min_pixel_size, ToolboxFactory::min_pixel_size, ToolboxFactory::max_pixel_size); + auto widg = Gtk::manage(new Gtk::Image(Inkscape::UI::Dialog::SymbolsDialog::draw_symbol(symbol, s))); + btn->set_icon_widget(*widg); + btn->signal_size_allocate().connect([this,widg,symbol,s](Gtk::Allocation &allocation) { + widg->set(Inkscape::UI::Dialog::SymbolsDialog::draw_symbol(symbol, s)); + }); + _symbols_buttons.push_back(btn); + btn->get_style_context()->add_class("linked"); + add(*btn); + } + } + } + } + } + _separa = Gtk::manage(new Gtk::SeparatorToolItem()); + add(*_separa); + { + _open_dialog = Gtk::manage(new Gtk::ToolButton()); + auto *label = Gtk::manage(new Gtk::Label(_("Pick Symbols"))); + label->set_valign(Gtk::ALIGN_CENTER); + auto *icon = Gtk::manage(sp_get_icon_image("symbols", Gtk::ICON_SIZE_BUTTON)); + auto *box = Gtk::manage(new Gtk::Box()); + box->pack_start(*icon, false, false); + box->pack_start(*label, false, false); + _open_dialog->set_icon_widget(*box); + add(*_open_dialog); + _open_dialog->signal_clicked().connect([=]{ + Inkscape::UI::Dialog::DialogContainer* container = desktop->getContainer(); + container->new_dialog("Symbols"); + }); + } + show_all(); +} + +void +SymbolToolbar::on_pref_toggled(Gtk::ToggleToolButton *btn, + const Glib::ustring& path) +{ + auto prefs = Inkscape::Preferences::get(); + bool active = btn->get_active(); + prefs->setBool(path, active); +} + +GtkWidget * +SymbolToolbar::create(SPDesktop *desktop) +{ + auto toolbar = new SymbolToolbar(desktop); + return GTK_WIDGET(toolbar->gobj()); +} + +void +SymbolToolbar::defaults() +{ + if(_desktop->getCanvas()) _desktop->getCanvas()->grab_focus(); +} + +void +SymbolToolbar::selection_changed(Inkscape::Selection *selection) +{ +} + +} +} +} + +/* + 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 : diff --git a/src/ui/toolbar/symbol-toolbar.h b/src/ui/toolbar/symbol-toolbar.h new file mode 100644 index 0000000000..cccd19d875 --- /dev/null +++ b/src/ui/toolbar/symbol-toolbar.h @@ -0,0 +1,76 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#ifndef SEEN_SYMBOL_TOOLBAR_H +#define SEEN_SYMBOL_TOOLBAR_H + +/** + * @file + * Symbol toolbar + */ +/* Authors: + * jabiertxof + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "toolbar.h" +#include "ui/tools/symbol-tool.h" +#include + +class SPDesktop; + +namespace Gtk { +class ToogleToolButton; +class RadioToolButton; +class ToolButton; +class SeparatorToolItem; +} + + +namespace Inkscape { +class Selection; + +namespace XML { +class Node; +} + +namespace UI { +namespace Widget { +class LabelToolItem; +class SpinButtonToolItem; +} + +namespace Toolbar { +class SymbolToolbar : public Toolbar { +private: + bool _freeze; + + XML::Node *_repr; + /** + * Show the dialog + */ + void defaults(); + void selection_changed(Inkscape::Selection *selection); + std::unique_ptr _connection; + std::vector _mode_buttons; + std::vector _symbols_buttons; + Gtk::ToggleToolButton *_colorize = nullptr; + Gtk::ToolButton *_open_dialog = nullptr; + Gtk::SeparatorToolItem *_separa = nullptr; + Gtk::SeparatorToolItem *_separa2 = nullptr; + void mode_changed(bool useuse); + void set_mode(bool useuse); + void on_pref_toggled(Gtk::ToggleToolButton *btn, const Glib::ustring& path); +protected: + SymbolToolbar(SPDesktop *desktop); + ~SymbolToolbar() override; +public: + gint get_active_symbol(); + void set_active_symbol(gint pos); + void reloadSymbols(SPDesktop *desktop, Inkscape::UI::Tools::SymbolTool *st); + static GtkWidget * create(SPDesktop *desktop); +}; +} +} +} + +#endif /* !SEEN_SYMBOL_TOOLBAR_H */ diff --git a/src/ui/tools/symbol-tool.cpp b/src/ui/tools/symbol-tool.cpp new file mode 100644 index 0000000000..b79e5b2f1c --- /dev/null +++ b/src/ui/tools/symbol-tool.cpp @@ -0,0 +1,676 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** + * @file + * Symbol drawing context + */ +/* Authors: + * jabiertxof + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "symbol-tool.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "color-rgba.h" +#include "color/color-conv.h" +#include "context-fns.h" +#include "desktop-style.h" +#include "desktop.h" +#include "document-undo.h" +#include "document.h" +#include "extension/db.h" +#include "file.h" +#include "hsluv.h" +#include "id-clash.h" +#include "include/macros.h" +#include "inkscape-window.h" +#include "inkscape.h" +#include "io/resource.h" +#include "layer-manager.h" +#include "message-context.h" +#include "object/sp-defs.h" +#include "object/sp-namedview.h" +#include "object/sp-root.h" +#include "object/sp-symbol.h" +#include "selection.h" +#include "style.h" +#include "svg/svg.h" +#include "ui/clipboard.h" +#include "ui/icon-names.h" +#include "ui/modifiers.h" +#include "ui/toolbar/symbol-toolbar.h" + +using Inkscape::DocumentUndo; +using Inkscape::Modifiers::Modifier; + +namespace Inkscape { +namespace UI { +namespace Tools { + +static std::vector > _symbols_docs; +SymbolTool::SymbolTool(SPDesktop *desktop) + : ToolBase(desktop, "/tools/shapes/symbol", "symbol.svg") + , symbol(nullptr) +{ + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + if (prefs->getBool("/tools/shapes/selcue")) { + this->enableSelectionCue(); + } + + if (prefs->getBool("/tools/shapes/gradientdrag")) { + this->enableGrDrag(); + } + bool init = true; + Inkscape::UI::Toolbar::SymbolToolbar *sb = dynamic_cast(_desktop->get_toolbar_by_name("SymbolToolbar")); + if (sb && sb->get_n_items() > 6) { + init = false; + } + gint current = 0; + auto toolfavs = Inkscape::IO::Resource::get_path_ustring(Inkscape::IO::Resource::USER, Inkscape::IO::Resource::SYMBOLS, "symbol-tool-1.svg"); + bool exist = Glib::file_test(toolfavs, Glib::FILE_TEST_EXISTS); + std::list< SPDesktop* > listbuf; + // Get list of all available desktop + INKSCAPE.get_all_desktops(listbuf); + if (exist && get_symbols_tool(current) && init) { + gint maxsymbols = prefs->getInt("/tools/shapes/symbol/ntoolbarsymbols", 7); + std::vector docs; + for (int i = 1 ; i < 200; i++) { + auto toolfavs = Inkscape::IO::Resource::get_path_ustring(Inkscape::IO::Resource::USER, Inkscape::IO::Resource::SYMBOLS, Glib::ustring("symbol-tool-" + Glib::ustring::format(i) + ".svg").c_str()); + bool exist = Glib::file_test(toolfavs, Glib::FILE_TEST_EXISTS); + if (exist) { + if (i <= maxsymbols) { + std::ifstream input(toolfavs); + std::stringstream sstr; + while(input >> sstr.rdbuf()); + std::shared_ptr doc(SPDocument::createNewDocFromMem( sstr.str().c_str(), strlen( sstr.str().c_str()), false )); + if (doc) { + set_symbols_tool(doc); + } + } else { + remove(toolfavs.c_str()); + } + } else { + break; + } + } + } + for(SPDesktop *desktop: listbuf) { + auto *sb = dynamic_cast(desktop->get_toolbar_by_name("SymbolToolbar")); + if (sb) { + sb->reloadSymbols(desktop, this); + } + } + _shutdown_connexion = INKSCAPE.signal_shut_down.connect([this]() { + if (_symbols_docs.size()) { + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + gint maxsymbols = prefs->getInt("/tools/shapes/symbol/ntoolbarsymbols", 7); + std::reverse(_symbols_docs.begin(),_symbols_docs.end()); + for (int i = 1 ; i < 200; i++) { + auto toolfavs = Inkscape::IO::Resource::get_path_ustring(Inkscape::IO::Resource::USER, Inkscape::IO::Resource::SYMBOLS, Glib::ustring("symbol-tool-" + Glib::ustring::format(i) + ".svg").c_str()); + bool exist = Glib::file_test(toolfavs, Glib::FILE_TEST_EXISTS); + if (exist) { + remove(toolfavs.c_str()); + } + if (_symbols_docs.size() > i-1) { + if (i <= maxsymbols) { + _symbols_docs[i-1]->setDocumentName("symbol-tool.svg"); + try { + Inkscape::Extension::save(Inkscape::Extension::db.get("svg"), _symbols_docs[i-1].get(), toolfavs.c_str(), + false, false, + Inkscape::Extension::FILE_SAVE_METHOD_SAVE_AS); + } catch (...) { + g_warning("Couldent store symbols tool symbols"); + } + } + } if (!exist) { + break; + } + } + _symbols_docs.clear(); + } + }); +} + +SymbolTool::~SymbolTool() { + ungrabCanvasEvents(); +} + +/** + * Set list of symbols tool. + */ +void +SymbolTool::set_symbols_tool(std::shared_ptr symbol_doc) +{ + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + gint maxsymbols = prefs->getInt("/tools/shapes/symbol/ntoolbarsymbols", 7); + gint pos = 0; + Inkscape::UI::Toolbar::SymbolToolbar *sb = dynamic_cast(_desktop->get_toolbar_by_name("SymbolToolbar")); + if (sb) { + auto syms = symbol_doc->getObjectsByElement("symbol"); + if (!syms.empty()) { + auto symbol = cast(syms[0]); + if (symbol) { + for (auto doc : _symbols_docs) { + auto syms2 = doc->getObjectsByElement("symbol"); + if (!syms2.empty()) { + auto smb = cast(syms2[0]); + if (!g_strcmp0(smb->title(), symbol->title()) && !g_strcmp0(smb->getId(), symbol->getId())) { + _symbols_docs.erase(_symbols_docs.begin() + pos); + _symbols_docs.insert(_symbols_docs.begin() + pos, symbol_doc); + sb->set_active_symbol(pos); + return; + } + } + pos ++; + } + _symbols_docs.insert(_symbols_docs.begin(), symbol_doc); + if (maxsymbols < _symbols_docs.size()) { + _symbols_docs.pop_back(); + } + } + } + sb->set_active_symbol(0); + } +}; + +/** + * Get a symbol from symbols tool + */ +std::shared_ptr +SymbolTool::get_symbols_tool(gint index) +{ + if (_symbols_docs.size() > index) { + return _symbols_docs[index]; + }; + return nullptr; +} + + +bool SymbolTool::root_handler(GdkEvent* event) { + + static gboolean dragging; + + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + this->tolerance = prefs->getIntLimited("/options/dragtolerance/value", 0, 0, 100) * 3; // bigger tolerance to prevent small resizings on clisk + auto confine_mod = Modifiers::Modifier::get(Modifiers::Type::TRANS_CONFINE)->get_label(); + auto center_mod = Modifiers::Modifier::get(Modifiers::Type::TRANS_OFF_CENTER)->get_label(); + auto tip = Glib::ustring::compose(_("Click to add; press and drag add with custom scale; with %1 draw around the starting point; with %2 to scale nonuniformly."), center_mod, confine_mod); + auto tip_confine = Glib::ustring::compose(_("%1 scale nonuniformly."), confine_mod); + auto tip_center = Glib::ustring::compose(_("%1 draw around the starting point."), center_mod); + gint ret = FALSE; + switch (event->type) { + case GDK_BUTTON_PRESS: + if (event->button.button == 1) { + confine = Modifiers::Modifier::get(Modifiers::Type::TRANS_CONFINE)->active(event->button.state); + off_center = Modifiers::Modifier::get(Modifiers::Type::TRANS_OFF_CENTER)->active(event->button.state); + dragging = TRUE; + this->origin = this->setup_for_drag_start(event); + useuse = prefs->getBool("/tools/shapes/symbol/useuse", false); + if (!skipsnap) { + SnapManager &m = _desktop->namedview->snap_manager; + m.setup(_desktop); + m.freeSnapReturnByRef(this->origin, Inkscape::SNAPSOURCE_NODE_HANDLE); + m.unSetup(); + } + skipsnap = true; + grabCanvasEvents(); + colorize = prefs->getBool("/tools/shapes/symbol/usecurrent", false); + init = true; + ret = TRUE; + } + this->defaultMessageContext()->set(Inkscape::NORMAL_MESSAGE, tip.c_str()); + break; + + case GDK_MOTION_NOTIFY: + if (dragging && (event->motion.state & GDK_BUTTON1_MASK)) { + if ( this->within_tolerance + && ( abs( (gint) event->motion.x - this->xp ) < this->tolerance ) + && ( abs( (gint) event->motion.y - this->yp ) < this->tolerance ) ) { + this->defaultMessageContext()->set(Inkscape::NORMAL_MESSAGE, tip.c_str()); + break; // do not drag if we're within tolerance from origin + } + skipsnap = false; + confine = Modifiers::Modifier::get(Modifiers::Type::TRANS_CONFINE)->active(event->button.state); + off_center = Modifiers::Modifier::get(Modifiers::Type::TRANS_OFF_CENTER)->active(event->button.state); + // Once the user has moved farther than tolerance from the original location + // (indicating they intend to draw, not click), then always process the + // motion notify coordinates as given (no snapping back to origin) + this->within_tolerance = false; + Geom::Point const motion_w(event->motion.x, event->motion.y); + Geom::Point motion_dt(_desktop->w2d(motion_w)); + SnapManager &m = _desktop->namedview->snap_manager; + m.setup(_desktop, true, this->symbol); + m.freeSnapReturnByRef(motion_dt, Inkscape::SNAPSOURCE_NODE_HANDLE); + m.unSetup(); + this->item_to_select = nullptr; + + this->drag(motion_dt, event->motion.state); + + gobble_motion_events(GDK_BUTTON1_MASK); + + ret = TRUE; + } else if (!dragging && !this->sp_event_context_knot_mouseover()) { + SnapManager &m = _desktop->namedview->snap_manager; + m.setup(_desktop); + Geom::Point const motion_w(event->motion.x, event->motion.y); + Geom::Point motion_dt(_desktop->w2d(motion_w)); + m.preSnap(Inkscape::SnapCandidatePoint(motion_dt, Inkscape::SNAPSOURCE_NODE_HANDLE)); + m.unSetup(); + ret = TRUE; + } + this->defaultMessageContext()->set(Inkscape::NORMAL_MESSAGE, tip.c_str()); + break; + + case GDK_BUTTON_RELEASE: + if (event->button.button == 1) { + + dragging = FALSE; + this->discard_delayed_snap_event(); + + if (!this->within_tolerance) { + // we've been dragging, finish + this->finishItem(); + } else { + this->drag(this->origin, event->button.state, true); + this->finishItem(); + } + this->item_to_select = nullptr; + ungrabCanvasEvents(); + ret = TRUE; + } + this->defaultMessageContext()->set(Inkscape::NORMAL_MESSAGE, tip.c_str()); + this->xp = this->yp = 0; + break; + + case GDK_KEY_PRESS: + switch (get_latin_keyval(&event->key)) { + case GDK_KEY_Shift_L: + case GDK_KEY_Shift_R: + case GDK_KEY_Control_L: + case GDK_KEY_Control_R: + sp_event_show_modifier_tip (this->defaultMessageContext(), event, + tip_confine.c_str(), + tip_center.c_str(), + nullptr); + break; + case GDK_KEY_Escape: + if (dragging) { + dragging = false; + this->discard_delayed_snap_event(); + // if drawing, cancel, otherwise pass it up for deselecting + this->cancel(); + ret = TRUE; + } + break; + + case GDK_KEY_space: + if (dragging) { + ungrabCanvasEvents(); + dragging = false; + this->discard_delayed_snap_event(); + + if (!this->within_tolerance) { + // we've been dragging, finish the spiral + finishItem(); + } + // do not return true, so that space would work switching to selector + } + break; + + case GDK_KEY_Delete: + case GDK_KEY_KP_Delete: + case GDK_KEY_BackSpace: + ret = this->deleteSelectedDrag(MOD__CTRL_ONLY(event)); + break; + + default: + break; + } + break; + default: + break; + } + + if (!ret) { + ret = ToolBase::root_handler(event); + } + + return ret; +} + +SPObject * SymbolTool::findSymbol(SPObject * symbol) { + auto symbols = _desktop->getDocument()->getObjectsByElement("symbol"); + for (auto symb : symbols) { + if (!g_strcmp0(symb->getId(), symbol->getId())) { + if (!g_strcmp0(symb->title(),symbol->title())) { + return symb; + } + } + } + return nullptr; +} +/* // we only override one single color in the symbol +std::vector sp_get_colors(SPObject *obj) { + std::vector out; + if (!obj) { + return out; + } + for (SPObject *child = obj->firstChild(); child ; child = child->getNext()) { + std::vector tmp = sp_get_colors(child); + out.insert(out.end(), tmp.begin(), tmp.end()); + } + if (obj->getAttribute("style")) { + SPCSSAttr *css = sp_repr_css_attr(obj->getRepr(), "style"); + if (sp_repr_css_property(css, "opacity", "0.02") < "0.01" && + sp_repr_css_property(css, "display", "block") == "none") { + return out; + } + if (const gchar * fill = sp_repr_css_property(css, "fill", nullptr)) { + if (!strncmp(fill, "#", 1) && + sp_repr_css_property(css, "fill-opacity", "0.02") > "0.01") + { + out.push_back(fill); + } + } else if (is(obj) && + sp_repr_css_property(css, "fill-opacity", "0.02") > "0.01") + { + sp_repr_css_attr_inherited(obj->getRepr(), "fill"); + if (!sp_repr_css_property(css, "fill", nullptr)) { + out.push_back("#000000"); + } + } + if (const gchar * stroke = sp_repr_css_property(css, "stroke", nullptr)) { + if (!strncmp(stroke, "#", 1) && + sp_repr_css_property(css, "fill-opacity", "0.02") > "0.01" && + sp_repr_css_property(css, "stroke-width", "0.001") > "0.01") + { + out.push_back(stroke); + } + } + sp_repr_css_attr_unref(css); + } else if (is(obj)) { + out.push_back("#000000"); + } + return out; +} +// Compares two intervals according to starting times. +bool compareluminosity(Glib::ustring col1, Glib::ustring col2) +{ + + col1 += "ff"; + col2 += "ff"; + ColorRGBA colora((unsigned int)*Inkscape::Util::string_to_rgba_color(col1.c_str())); + ColorRGBA colorb((unsigned int)*Inkscape::Util::string_to_rgba_color(col2.c_str())); + std::array coltrip1 = {colora[0], colora[1], colora[2]}; + std::array coltrip2= {colorb[0], colorb[1], colorb[2]}; + float l1 = Hsluv::rgb_to_perceptual_lightness(coltrip1); + float l2 = Hsluv::rgb_to_perceptual_lightness(coltrip2); + return (l1 < l2); +} +void replace_color(SPObject *obj, Glib::ustring origincolor, Glib::ustring newcolor, bool root = true); +void replace_color(SPObject *obj, Glib::ustring origincolor, Glib::ustring newcolor, bool root) { + if (!obj) { + return; + } + for (SPObject *child = obj->firstChild(); child ; child = child->getNext()) { + replace_color(child, origincolor, newcolor); + } + if (root) { + return; + } + SPCSSAttr *css = sp_repr_css_attr(obj->getRepr(), "style"); + if (const gchar * fill = sp_repr_css_property(css, "fill", nullptr)) { + if (!strcmp(fill, origincolor.c_str())) { + sp_repr_css_set_property(css, "fill", newcolor.c_str()); + } + } + if (const gchar * stroke = sp_repr_css_property(css, "stroke", nullptr)) { + if (!strcmp(stroke, origincolor.c_str())) { + sp_repr_css_set_property(css, "stroke", newcolor.c_str()); + } + } + obj->setCSS(css,"style"); + sp_repr_css_attr_unref(css); +} + +// we only override one single (first) fill color to allow multiple fills coloring, thinks is not necesary for strokes +void SymbolTool::change_fillstroke(SPDesktop * desktop, SPObject *obj) { + SPCSSAttr *css_current = sp_desktop_get_style(desktop, false); + const gchar *color_a = sp_repr_css_property(css_current, "fill", nullptr); + const gchar *color_b = sp_repr_css_property(css_current, "stroke", nullptr); + if (!g_strcmp0(color_a, "none")) { + color_a = nullptr; + } + if (!g_strcmp0(color_b, "none")) { + color_b = nullptr; + } + std::vector colors = sp_get_colors(obj); + std::sort(colors.begin(), colors.end(), compareluminosity); + if (!colors.empty() && (color_a || color_b)) { + // this is the most simple case covered, others relay in symbol definition + if (colors.front() == colors.back()) { + replace_color(obj, colors.back(), Glib::ustring(color_b == nullptr ? color_a : color_b), true); + } + } +} */ + +void SymbolTool::drag(Geom::Point const &p, guint state, bool no_scale) { + + static double scale = 1.0; + static double scale_center = 1.0; + static double factor = 1.0; + static Geom::Affine gap = Geom::identity(); + static Geom::Affine gap_center = Geom::identity(); + + double layerscale = currentLayer()->i2doc_affine().inverse().descrim(); + Geom::Point p2 = _desktop->dt2doc(this->origin); + p2 *= Geom::Scale(layerscale); + SPSymbol *csymbol = nullptr; + if (init) { + init = false; + Inkscape::Selection *selection = _desktop->getSelection(); + Inkscape::XML::Document *xml_doc = _desktop->getDocument()->getReprDoc(); + gint current = 0; + Inkscape::UI::Toolbar::SymbolToolbar *sb = dynamic_cast(_desktop->get_toolbar_by_name("SymbolToolbar")); + if (sb) { + current = sb->get_active_symbol(); + } + auto doc = get_symbols_tool(current); + if (doc) { + auto symbols = doc->getObjectsByElement("symbol"); + if (!symbols.empty()) { + csymbol = cast(symbols[0]); + csymbol->setAttribute("display", "none"); + if (csymbol) { + gchar const* style = csymbol->getAttribute("inkscape:symbol-style"); + SPSymbol *symboldef = cast(findSymbol(csymbol)); + if (!symboldef) { + prevent_id_clashes(doc.get(), _desktop->getDocument(), true); + sp_import_document(_desktop, doc.get(), true, false); + symboldef = cast(findSymbol(csymbol)); + } + if (symboldef) { + auto href = Glib::ustring("#") + symboldef->getAttribute("id"); + Inkscape::XML::Node *use_repr = xml_doc->createElement("svg:use"); + use_repr->setAttribute("xlink:href", href); + /** + * If the symbol has a viewBox but no width or height, then take width and + * height from the viewBox and set them on the use element. Otherwise, the + * use element will have 100% document width and height! + */ + { + auto widthAttr = symboldef->getAttribute("width"); + auto heightAttr = symboldef->getAttribute("height"); + auto viewBoxAttr = symboldef->getAttribute("viewBox"); + + if (viewBoxAttr && !(heightAttr || widthAttr)) { + SPViewBox vb; + vb.set_viewBox(viewBoxAttr); + if (vb.viewBox_set) { + use_repr->setAttributeSvgDouble("width", vb.viewBox.width()); + use_repr->setAttributeSvgDouble("height", vb.viewBox.height()); + } + } + } + // Set a default style in rather than so it can be changed. + use_repr->setAttribute("style", style); + use_repr->setAttribute("transform", "translate(999999,999999)"); + use_repr->setAttribute("display", "none"); + this->symbol = cast(_desktop->layerManager().currentLayer()->appendChildRepr(use_repr)); + Inkscape::GC::release(use_repr); + selection->clear(); + selection->add(use_repr); + if (this->symbol) { + if (colorize) { + SPCSSAttr *css = sp_repr_css_attr_new(); + SPCSSAttr *css_current = sp_desktop_get_style(_desktop, false); + if (const gchar * fill = sp_repr_css_property(css_current, "fill", nullptr)) { + sp_repr_css_set_property(css, "fill", fill); + } + if (const gchar * stroke = sp_repr_css_property(css_current, "stroke", nullptr)) { + sp_repr_css_set_property(css, "stroke", stroke); + } + this->symbol->setCSS(css,"style"); + sp_repr_css_attr_unref(css); + sp_repr_css_attr_unref(css_current); + } + //this fix a issue with BBOX on stroked symbols + auto app = Gio::Application::get_default(); + auto variant = Glib::Variant::create("top left page"); + app->activate_action("object-align", variant); + Geom::OptRect dbox = selection->bounds(SPItem::VISUAL_BBOX); + if (dbox) { + /* Scale symbols to fit */ + double width = dbox->width(); + double height = dbox->height(); + if( width == 0.0 ) width = 1.0; + if( height == 0.0 ) height = 1.0; + factor = width/height; + scale = 1 / std::max(width, height); // 1 px object + gap = this->symbol->transform; + width = dbox->width(); + height = dbox->height(); + if( width == 0.0 ) width = 1.0; + if( height == 0.0 ) height = 1.0; + scale_center = 1 / std::max(width, height); // 1 px object + gap_center = Geom::Translate(-dbox->midpoint() * layerscale); + gap_center *= (no_scale ? Geom::identity() : Geom::Scale(2)); + } + } + } + } + } + } + } + if (!init && this->symbol) { + SnapManager &m = _desktop->namedview->snap_manager; + m.setup(_desktop, true, this->symbol); + Geom::Point pt2g = p; + if (confine && off_center) { + m.freeSnapReturnByRef(pt2g, Inkscape::SNAPSOURCE_NODE_HANDLE); + m.unSetup(); + } + Geom::Point p1 = _desktop->dt2doc(pt2g); + p1 *= Geom::Scale(layerscale); + Geom::Point const delta = p2 - p1; + gdouble sclx = (p1[Geom::X]-p2[Geom::X]) * (factor < 1 ? 1/factor : 1); + gdouble scly = (p1[Geom::Y]-p2[Geom::Y]) * (factor > 1 ? factor : 1); + if (no_scale) { + sclx = px == Geom::infinity() ? 1 : px * (factor < 1 ? 1/factor : 1); + scly = py == Geom::infinity() ? 1 : py * (factor > 1 ? factor : 1); + } else { + px = (p1[Geom::X]-p2[Geom::X]); + py = (p1[Geom::Y]-p2[Geom::Y]); + } + if (!confine) { + scly = sclx; + } + if (px == Geom::infinity()) { + scale_center = 1; + } + Geom::Affine trans = Geom::identity(); + if (off_center) { + trans = gap_center * (Geom::Scale(sclx * scale_center, scly * scale_center)); + } else { + trans = gap * (Geom::Scale(sclx * scale_center, scly * scale_center)); + } + this->symbol->updateRepr(); + trans *= currentLayer()->i2doc_affine(); + trans *= Geom::Translate(p2[Geom::X], p2[Geom::Y]); + this->symbol->transform = trans; + if (!confine || !off_center) { + Geom::OptRect dbox = this->symbol->documentGeometricBounds(); + if (dbox) { + Geom::Point corner0 = dbox->corner(0); + Geom::Point corner2 = dbox->corner(2); + if (corner0 == pt2g) { + corner0 = corner2; + } + m.freeSnapReturnByRef(corner2, Inkscape::SNAPSOURCE_NODE_HANDLE); + m.unSetup(); + } + } + if (csymbol) { + csymbol->removeAttribute("display"); + this->symbol->removeAttribute("display"); + cast(this->symbol)->trueOriginal()->removeAttribute("display"); + } + this->message_context->setF(Inkscape::IMMEDIATE_MESSAGE,_("Symbol:")); + } +} + + +void SymbolTool::finishItem() { + this->message_context->clear(); + + if (this->symbol != nullptr) { + auto * selection = _desktop->getSelection(); + if (!useuse) { + selection->set(cast(this->symbol)->unlink()); // keep selection + } + DocumentUndo::done(_desktop->getDocument(), _("Create from symbol tool"), INKSCAPE_ICON("draw-symbol")); + this->symbol = nullptr; + } +} + +void SymbolTool::cancel() { + _desktop->getSelection()->clear(); + ungrabCanvasEvents(); + + if (this->symbol != nullptr) { + this->symbol->deleteObject(); + this->symbol = nullptr; + } + + this->within_tolerance = false; + this->xp = 0; + this->yp = 0; + this->item_to_select = nullptr; + + DocumentUndo::cancel(_desktop->getDocument()); +} + +} +} +} + +/* + 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 : diff --git a/src/ui/tools/symbol-tool.h b/src/ui/tools/symbol-tool.h new file mode 100644 index 0000000000..886cf1a241 --- /dev/null +++ b/src/ui/tools/symbol-tool.h @@ -0,0 +1,57 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#ifndef __SP_SYMBOL_CONTEXT_H__ +#define __SP_SYMBOL_CONTEXT_H__ +/** + * @file + * Symbol drawing context + */ +/* Authors: + * jabiertxof + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include +#include <2geom/point.h> +#include +#include "ui/tools/tool-base.h" + +namespace Inkscape { + +class Selection; + +namespace UI { +namespace Tools { + +class SymbolTool : public ToolBase { +public: + SymbolTool(SPDesktop *desktop); + ~SymbolTool() override; + + bool root_handler(GdkEvent* event) override; + void set_symbols_tool(std::shared_ptr symbol_doc); + std::shared_ptr get_symbols_tool(gint index); +private: + SPItem* symbol; + Geom::Point origin; + void finishItem(); + SPObject * findSymbol(SPObject * symbol); + void drag(Geom::Point const &p, guint state, bool no_scale = false); + void cancel(); + bool colorize = false; + bool confine = false; + bool off_center = false; + bool hassymbol = false; + bool init = false; + bool skipsnap = false; + Geom::Coord px = Geom::infinity(); + Geom::Coord py = Geom::infinity(); + int useuse = 0; + sigc::connection _shutdown_connexion; +}; + +} +} +} + +#endif diff --git a/src/widgets/mappings.xml b/src/widgets/mappings.xml index c3993614d7..86151a3437 100644 --- a/src/widgets/mappings.xml +++ b/src/widgets/mappings.xml @@ -124,6 +124,7 @@ + diff --git a/src/widgets/toolbox.cpp b/src/widgets/toolbox.cpp index 0e8d315c80..4a7ef7c70e 100644 --- a/src/widgets/toolbox.cpp +++ b/src/widgets/toolbox.cpp @@ -61,12 +61,14 @@ #include "ui/toolbar/select-toolbar.h" #include "ui/toolbar/spray-toolbar.h" #include "ui/toolbar/spiral-toolbar.h" +#include "ui/toolbar/symbol-toolbar.h" #include "ui/toolbar/star-toolbar.h" #include "ui/toolbar/tweak-toolbar.h" #include "ui/toolbar/text-toolbar.h" #include "ui/toolbar/zoom-toolbar.h" #include "ui/tools/tool-base.h" +#include "inkscape.h" //#define DEBUG_TEXT @@ -131,6 +133,7 @@ static struct { { "/tools/shapes/star", "Star", Inkscape::UI::Toolbar::StarToolbar::create, N_("Style of new stars")}, { "/tools/shapes/3dbox", "3DBox", Inkscape::UI::Toolbar::Box3DToolbar::create, N_("Style of new 3D boxes")}, { "/tools/shapes/spiral", "Spiral", Inkscape::UI::Toolbar::SpiralToolbar::create, N_("Style of new spirals")}, + { "/tools/shapes/symbol", "Symbol", Inkscape::UI::Toolbar::SymbolToolbar::create, N_("Style of new symbols")}, { "/tools/freehand/pencil", "Pencil", Inkscape::UI::Toolbar::PencilToolbar::create_pencil, N_("Style of new paths created by Pencil")}, { "/tools/freehand/pen", "Pen", Inkscape::UI::Toolbar::PencilToolbar::create_pen, N_("Style of new paths created by Pen")}, { "/tools/calligraphic", "Calligraphic", Inkscape::UI::Toolbar::CalligraphyToolbar::create, N_("Style of new calligraphic strokes")}, @@ -565,6 +568,7 @@ void setup_aux_toolbox(GtkWidget *toolbox, SPDesktop *desktop) int pixel_size = ToolboxFactory::prefToPixelSize(ToolboxFactory::ctrlbars_icon_size); ToolboxFactory::set_icon_size(sub_toolbox, pixel_size); + gtk_widget_set_hexpand(sub_toolbox, TRUE); // Add a swatch widget if swatch tooltip is defined. @@ -589,7 +593,15 @@ void setup_aux_toolbox(GtkWidget *toolbox, SPDesktop *desktop) gtk_container_add(GTK_CONTAINER(toolbox), holder); Glib::ustring ui_name = aux_toolboxes[i].tool_name + "Toolbar"; // If you change "Toolbar" here, change it also in desktop-widget.cpp. gtk_widget_set_name( holder, ui_name.c_str() ); - + if (SP_ACTIVE_DESKTOP && aux_toolboxes[i].tool_name == "SymbolToolbar") { + Inkscape::UI::Tools::SymbolTool *st = dynamic_cast(SP_ACTIVE_DESKTOP->event_context); + if (st) { + Inkscape::UI::Toolbar::SymbolToolbar *sb = dynamic_cast(SP_ACTIVE_DESKTOP->get_toolbar_by_name("SymbolToolbar")); + if (sb) { + sb->reloadSymbols(SP_ACTIVE_DESKTOP, st); + } + } + } // TODO: We could make the toolbox a custom subclass of GtkEventBox // so that we can store a list of toolbars, rather than using // GObject data -- GitLab