1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /** @file
3  * @brief A dialog for CSS styles
4  */
5 /* Authors:
6  *   Kamalpreet Kaur Grewal
7  *   Tavmjong Bah
8  *   Jabiertxof
9  *
10  * Copyright (C) Kamalpreet Kaur Grewal 2016 <grewalkamal005@gmail.com>
11  * Copyright (C) Tavmjong Bah 2017 <tavmjong@free.fr>
12  *
13  * Released under GNU GPL v2+, read the file 'COPYING' for more information.
14  */
15 
16 #include "styledialog.h"
17 #include "attribute-rel-svg.h"
18 #include "attributes.h"
19 #include "document-undo.h"
20 #include "inkscape.h"
21 #include "io/resource.h"
22 #include "selection.h"
23 #include "style-internal.h"
24 #include "style.h"
25 #include "svg/svg-color.h"
26 #include "ui/icon-loader.h"
27 #include "ui/widget/iconrenderer.h"
28 #include "verbs.h"
29 #include "xml/attribute-record.h"
30 #include "xml/node-observer.h"
31 #include "xml/sp-css-attr.h"
32 
33 #include <map>
34 #include <regex>
35 #include <utility>
36 
37 #include <gdk/gdkkeysyms.h>
38 #include <glibmm/i18n.h>
39 
40 // G_MESSAGES_DEBUG=DEBUG_STYLEDIALOG  gdb ./inkscape
41 // #define DEBUG_STYLEDIALOG
42 // #define G_LOG_DOMAIN "STYLEDIALOG"
43 
44 using Inkscape::DocumentUndo;
45 
46 /**
47  * This macro is used to remove spaces around selectors or any strings when
48  * parsing is done to update XML style element or row labels in this dialog.
49  */
50 #define REMOVE_SPACES(x)                                                                                               \
51     x.erase(0, x.find_first_not_of(' '));                                                                              \
52     x.erase(x.find_last_not_of(' ') + 1);
53 
54 namespace Inkscape {
55 
56 /**
57  * Get the first <style> element's first text node. If no such node exists and
58  * `create_if_missing` is false, then return NULL.
59  *
60  * Only finds <style> elements in root or in root-level <defs>.
61  */
get_first_style_text_node(XML::Node * root,bool create_if_missing)62 XML::Node *get_first_style_text_node(XML::Node *root, bool create_if_missing)
63 {
64     static GQuark const CODE_svg_style = g_quark_from_static_string("svg:style");
65     static GQuark const CODE_svg_defs = g_quark_from_static_string("svg:defs");
66 
67     XML::Node *styleNode = nullptr;
68     XML::Node *textNode = nullptr;
69 
70     if (!root) {
71         return nullptr;
72     }
73 
74     for (auto *node = root->firstChild(); node; node = node->next()) {
75         if (node->code() == CODE_svg_defs) {
76             textNode = get_first_style_text_node(node, false);
77             if (textNode != nullptr) {
78                 return textNode;
79             }
80         }
81 
82         if (node->code() == CODE_svg_style) {
83             styleNode = node;
84             break;
85         }
86     }
87 
88     if (styleNode == nullptr) {
89         if (!create_if_missing)
90             return nullptr;
91 
92         styleNode = root->document()->createElement("svg:style");
93         root->addChild(styleNode, nullptr);
94         Inkscape::GC::release(styleNode);
95     }
96 
97     for (auto *node = styleNode->firstChild(); node; node = node->next()) {
98         if (node->type() == XML::NodeType::TEXT_NODE) {
99             textNode = node;
100             break;
101         }
102     }
103 
104     if (textNode == nullptr) {
105         if (!create_if_missing)
106             return nullptr;
107 
108         textNode = root->document()->createTextNode("");
109         styleNode->appendChild(textNode);
110         Inkscape::GC::release(textNode);
111     }
112 
113     return textNode;
114 }
115 
116 namespace UI {
117 namespace Dialog {
118 
119 // Keeps a watch on style element
120 class StyleDialog::NodeObserver : public Inkscape::XML::NodeObserver {
121   public:
NodeObserver(StyleDialog * styledialog)122     NodeObserver(StyleDialog *styledialog)
123         : _styledialog(styledialog)
124     {
125         g_debug("StyleDialog::NodeObserver: Constructor");
126     };
127 
128     void notifyContentChanged(Inkscape::XML::Node &node, Inkscape::Util::ptr_shared old_content,
129                               Inkscape::Util::ptr_shared new_content) override;
130 
131     StyleDialog *_styledialog;
132 };
133 
134 
notifyContentChanged(Inkscape::XML::Node &,Inkscape::Util::ptr_shared,Inkscape::Util::ptr_shared)135 void StyleDialog::NodeObserver::notifyContentChanged(Inkscape::XML::Node & /*node*/,
136                                                      Inkscape::Util::ptr_shared /*old_content*/,
137                                                      Inkscape::Util::ptr_shared /*new_content*/)
138 {
139 
140     g_debug("StyleDialog::NodeObserver::notifyContentChanged");
141     _styledialog->_updating = false;
142     _styledialog->readStyleElement();
143 }
144 
145 
146 // Keeps a watch for new/removed/changed nodes
147 // (Must update objects that selectors match.)
148 class StyleDialog::NodeWatcher : public Inkscape::XML::NodeObserver {
149   public:
NodeWatcher(StyleDialog * styledialog)150     NodeWatcher(StyleDialog *styledialog)
151         : _styledialog(styledialog)
152     {
153         g_debug("StyleDialog::NodeWatcher: Constructor");
154     };
155 
notifyChildAdded(Inkscape::XML::Node &,Inkscape::XML::Node & child,Inkscape::XML::Node *)156     void notifyChildAdded(Inkscape::XML::Node & /*node*/, Inkscape::XML::Node &child,
157                           Inkscape::XML::Node * /*prev*/) override
158     {
159             _styledialog->_nodeAdded(child);
160     }
161 
notifyChildRemoved(Inkscape::XML::Node &,Inkscape::XML::Node & child,Inkscape::XML::Node *)162     void notifyChildRemoved(Inkscape::XML::Node & /*node*/, Inkscape::XML::Node &child,
163                             Inkscape::XML::Node * /*prev*/) override
164     {
165             _styledialog->_nodeRemoved(child);
166     }
167     /*     void notifyContentChanged(Inkscape::XML::Node &node,
168                                           Inkscape::Util::ptr_shared old_content,
169                                           Inkscape::Util::ptr_shared new_content) override{
170             if ( _styledialog && _repr && _textNode == node) {
171                 _styledialog->_stylesheetChanged( node );
172             }
173         };
174      */
notifyAttributeChanged(Inkscape::XML::Node & node,GQuark qname,Util::ptr_shared,Util::ptr_shared)175     void notifyAttributeChanged(Inkscape::XML::Node &node, GQuark qname, Util::ptr_shared /*old_value*/,
176                                 Util::ptr_shared /*new_value*/) override
177     {
178         static GQuark const CODE_id = g_quark_from_static_string("id");
179         static GQuark const CODE_class = g_quark_from_static_string("class");
180         static GQuark const CODE_style = g_quark_from_static_string("style");
181 
182         if (qname == CODE_id || qname == CODE_class || qname == CODE_style) {
183             _styledialog->_nodeChanged(node);
184         }
185     }
186 
187     StyleDialog *_styledialog;
188 };
189 
_nodeAdded(Inkscape::XML::Node & node)190 void StyleDialog::_nodeAdded(Inkscape::XML::Node &node)
191 {
192     readStyleElement();
193 }
194 
_nodeRemoved(Inkscape::XML::Node & repr)195 void StyleDialog::_nodeRemoved(Inkscape::XML::Node &repr)
196 {
197     if (_textNode == &repr) {
198         _textNode = nullptr;
199     }
200 
201     readStyleElement();
202 }
203 
_nodeChanged(Inkscape::XML::Node & object)204 void StyleDialog::_nodeChanged(Inkscape::XML::Node &object)
205 {
206     g_debug("StyleDialog::_nodeChanged");
207     readStyleElement();
208 }
209 
210 /* void
211 StyleDialog::_stylesheetChanged( Inkscape::XML::Node &repr ) {
212     std::cout << "Style tag modified" << std::endl;
213     readStyleElement();
214 } */
215 
216 /**
217  * Constructor
218  * A treeview and a set of two buttons are added to the dialog. _addSelector
219  * adds selectors to treeview. _delSelector deletes the selector from the dialog.
220  * Any addition/deletion of the selectors updates XML style element accordingly.
221  */
StyleDialog()222 StyleDialog::StyleDialog()
223     : DialogBase("/dialogs/style", SP_VERB_DIALOG_STYLE)
224     , _updating(false)
225     , _textNode(nullptr)
226     , _scroolpos(0)
227     , _deleted_pos(0)
228     , _deletion(false)
229 {
230     g_debug("StyleDialog::StyleDialog");
231 
232     m_nodewatcher.reset(new StyleDialog::NodeWatcher(this));
233     m_styletextwatcher.reset(new StyleDialog::NodeObserver(this));
234 
235     // Pack widgets
236     _mainBox.pack_start(_scrolledWindow, Gtk::PACK_EXPAND_WIDGET);
237     _scrolledWindow.set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC);
238     _styleBox.set_orientation(Gtk::ORIENTATION_VERTICAL);
239     _styleBox.set_valign(Gtk::ALIGN_START);
240     _scrolledWindow.add(_styleBox);
241     _vadj = _scrolledWindow.get_vadjustment();
242     _vadj->signal_value_changed().connect(sigc::mem_fun(*this, &StyleDialog::_vscrool));
243     _mainBox.set_orientation(Gtk::ORIENTATION_VERTICAL);
244 
245     pack_start(_mainBox, Gtk::PACK_EXPAND_WIDGET);
246 }
247 
_vscrool()248 void StyleDialog::_vscrool()
249 {
250     if (!_scroollock) {
251         _scroolpos = _vadj->get_value();
252     } else {
253         _vadj->set_value(_scroolpos);
254         _scroollock = false;
255     }
256 }
257 
fixCSSSelectors(Glib::ustring selector)258 Glib::ustring StyleDialog::fixCSSSelectors(Glib::ustring selector)
259 {
260     g_debug("SelectorsDialog::fixCSSSelectors");
261     REMOVE_SPACES(selector);
262     std::vector<Glib::ustring> tokens = Glib::Regex::split_simple("[,]+", selector);
263     Glib::ustring my_selector = selector + " {"; // Parsing fails sometimes without '{'. Fix me
264     CRSelector *cr_selector = cr_selector_parse_from_buf((guchar *)my_selector.c_str(), CR_UTF_8);
265     for (auto token : tokens) {
266         REMOVE_SPACES(token);
267         std::vector<Glib::ustring> subtokens = Glib::Regex::split_simple("[ ]+", token);
268         for (auto subtoken : subtokens) {
269             REMOVE_SPACES(subtoken);
270             Glib::ustring my_selector = subtoken + " {"; // Parsing fails sometimes without '{'. Fix me
271             CRSelector *cr_selector = cr_selector_parse_from_buf((guchar *)my_selector.c_str(), CR_UTF_8);
272             gchar *selectorchar = reinterpret_cast<gchar *>(cr_selector_to_string(cr_selector));
273             if (selectorchar) {
274                 Glib::ustring toadd = Glib::ustring(selectorchar);
275                 g_free(selectorchar);
276                 if (toadd[0] != '.' && toadd[0] != '#' && toadd.size() > 1) {
277                     auto i = std::min(toadd.find("#"), toadd.find("."));
278                     Glib::ustring tag = toadd;
279                     if (i != std::string::npos) {
280                         tag = tag.substr(0, i);
281                     }
282                     if (!SPAttributeRelSVG::isSVGElement(tag)) {
283                         if (tokens.size() == 1) {
284                             tag = "." + tag;
285                             return tag;
286                         } else {
287                             return "";
288                         }
289                     }
290                 }
291             }
292         }
293     }
294     if (cr_selector) {
295         return selector;
296     }
297     return "";
298 }
299 
300 /**
301  * Class destructor
302  */
~StyleDialog()303 StyleDialog::~StyleDialog()
304 {
305     g_debug("StyleDialog::~StyleDialog");
306     _document_replaced_connection.disconnect();
307     _selection_changed_connection.disconnect();
308 }
309 
_reload()310 void StyleDialog::_reload() { readStyleElement(); }
311 
312 /**
313  * @return Inkscape::XML::Node* pointing to a style element's text node.
314  * Returns the style element's text node. If there is no style element, one is created.
315  * Ditto for text node.
316  */
_getStyleTextNode(bool create_if_missing)317 Inkscape::XML::Node *StyleDialog::_getStyleTextNode(bool create_if_missing)
318 {
319     g_debug("StyleDialog::_getStyleTextNoded");
320 
321     auto textNode = Inkscape::get_first_style_text_node(m_root, create_if_missing);
322 
323     if (_textNode != textNode) {
324         if (_textNode) {
325             _textNode->removeObserver(*m_styletextwatcher);
326         }
327 
328         _textNode = textNode;
329 
330         if (_textNode) {
331             _textNode->addObserver(*m_styletextwatcher);
332         }
333     }
334 
335     return textNode;
336 }
337 
338 
_selectTree(Glib::ustring selector)339 Glib::RefPtr<Gtk::TreeModel> StyleDialog::_selectTree(Glib::ustring selector)
340 {
341     g_debug("StyleDialog::_selectTree");
342 
343     Gtk::Label *selectorlabel;
344     Glib::RefPtr<Gtk::TreeModel> model;
345     for (auto fullstyle : _styleBox.get_children()) {
346         Gtk::Box *style = dynamic_cast<Gtk::Box *>(fullstyle);
347         for (auto stylepart : style->get_children()) {
348             switch (style->child_property_position(*stylepart)) {
349                 case 0: {
350                     Gtk::Box *selectorbox = dynamic_cast<Gtk::Box *>(stylepart);
351                     for (auto styleheader : selectorbox->get_children()) {
352                         if (!selectorbox->child_property_position(*styleheader)) {
353                             selectorlabel = dynamic_cast<Gtk::Label *>(styleheader);
354                         }
355                     }
356                     break;
357                 }
358                 case 1: {
359                     Glib::ustring wdg_selector = selectorlabel->get_text();
360                     if (wdg_selector == selector) {
361                         Gtk::TreeView *treeview = dynamic_cast<Gtk::TreeView *>(stylepart);
362                         if (treeview) {
363                             return treeview->get_model();
364                         }
365                     }
366                     break;
367                 }
368                 default:
369                     break;
370             }
371         }
372     }
373     return model;
374 }
375 
setCurrentSelector(Glib::ustring current_selector)376 void StyleDialog::setCurrentSelector(Glib::ustring current_selector)
377 {
378     g_debug("StyleDialog::setCurrentSelector");
379     _current_selector = current_selector;
380     readStyleElement();
381 }
382 
383 // copied from style.cpp:1499
is_url(char const * p)384 static bool is_url(char const *p)
385 {
386     if (p == nullptr)
387         return false;
388     /** \todo
389      * FIXME: I'm not sure if this applies to SVG as well, but CSS2 says any URIs
390      * in property values must start with 'url('.
391      */
392     return (g_ascii_strncasecmp(p, "url(", 4) == 0);
393 }
394 
395 /**
396  * Fill the Gtk::TreeStore from the svg:style element.
397  */
readStyleElement()398 void StyleDialog::readStyleElement()
399 {
400     g_debug("StyleDialog::readStyleElement");
401 
402     if (_updating)
403         return; // Don't read if we wrote style element.
404     _updating = true;
405     _scroollock = true;
406     Inkscape::XML::Node *textNode = _getStyleTextNode();
407     SPDocument *document = SP_ACTIVE_DOCUMENT;
408 
409     // Get content from style text node.
410     std::string content = (textNode && textNode->content()) ? textNode->content() : "";
411 
412     // Remove end-of-lines (check it works on Windoze).
413     content.erase(std::remove(content.begin(), content.end(), '\n'), content.end());
414 
415     // Remove comments (/* xxx */)
416 
417     bool breakme = false;
418     size_t start = content.find("/*");
419     size_t open = content.find("{", start + 1);
420     size_t close = content.find("}", start + 1);
421     size_t end = content.find("*/", close + 1);
422     while (!breakme) {
423         if (open == std::string::npos || close == std::string::npos || end == std::string::npos) {
424             breakme = true;
425             break;
426         }
427         while (open < close) {
428             open = content.find("{", close + 1);
429             close = content.find("}", close + 1);
430             end = content.find("*/", close + 1);
431             size_t reopen = content.find("{", close + 1);
432             if (open == std::string::npos || end == std::string::npos || end < reopen) {
433                 if (end < reopen) {
434                     content = content.erase(start, end - start + 2);
435                 } else {
436                     breakme = true;
437                 }
438                 break;
439             }
440         }
441         start = content.find("/*", start + 1);
442         open = content.find("{", start + 1);
443         close = content.find("}", start + 1);
444         end = content.find("*/", close + 1);
445     }
446 
447     // First split into selector/value chunks.
448     // An attempt to use Glib::Regex failed. A C++11 version worked but
449     // reportedly has problems on Windows. Using split_simple() is simpler
450     // and probably faster.
451     //
452     // Glib::RefPtr<Glib::Regex> regex1 =
453     //   Glib::Regex::create("([^\\{]+)\\{([^\\{]+)\\}");
454     //
455     // Glib::MatchInfo minfo;
456     // regex1->match(content, minfo);
457 
458     // Split on curly brackets. Even tokens are selectors, odd are values.
459     std::vector<Glib::ustring> tokens = Glib::Regex::split_simple("[}{]", content);
460     _owner_style.clear();
461     // If text node is empty, return (avoids problem with negative below).
462 
463     for (auto child : _styleBox.get_children()) {
464         _styleBox.remove(*child);
465         delete child;
466     }
467     Inkscape::Selection *selection = _desktop->getSelection();
468     SPObject *obj = nullptr;
469     if (selection->objects().size() == 1) {
470         obj = selection->objects().back();
471     }
472     if (!obj) {
473         obj = _desktop->getDocument()->getXMLDialogSelectedObject();
474         if (obj && !obj->getRepr()) {
475             obj = nullptr; // treat detached object as no selection
476         }
477     }
478 
479     auto gladefile = get_filename_string(Inkscape::IO::Resource::UIS, "dialog-css.glade");
480     Glib::RefPtr<Gtk::Builder> _builder;
481     try {
482         _builder = Gtk::Builder::create_from_file(gladefile);
483     } catch (const Glib::Error &ex) {
484         g_warning("Glade file loading failed for filter effect dialog");
485         return;
486     }
487     gint selectorpos = 0;
488     Gtk::Box *css_selector_container;
489     _builder->get_widget("CSSSelectorContainer", css_selector_container);
490     Gtk::Label *css_selector;
491     _builder->get_widget("CSSSelector", css_selector);
492     Gtk::EventBox *css_selector_event_add;
493     _builder->get_widget("CSSSelectorEventAdd", css_selector_event_add);
494     css_selector_event_add->add_events(Gdk::BUTTON_RELEASE_MASK);
495     css_selector->set_text("element");
496     Gtk::TreeView *css_tree;
497     _builder->get_widget("CSSTree", css_tree);
498     css_tree->get_style_context()->add_class("style_element");
499     Glib::RefPtr<Gtk::TreeStore> store = Gtk::TreeStore::create(_mColumns);
500     css_tree->set_model(store);
501     css_selector_event_add->signal_button_release_event().connect(
502         sigc::bind<Glib::RefPtr<Gtk::TreeStore>, Gtk::TreeView *, Glib::ustring, gint>(
503             sigc::mem_fun(*this, &StyleDialog::_addRow), store, css_tree, "style_properties", selectorpos));
504     Inkscape::UI::Widget::IconRenderer *addRenderer = manage(new Inkscape::UI::Widget::IconRenderer());
505     addRenderer->add_icon("edit-delete");
506     int addCol = css_tree->append_column(" ", *addRenderer) - 1;
507     Gtk::TreeViewColumn *col = css_tree->get_column(addCol);
508     if (col) {
509         addRenderer->signal_activated().connect(
510             sigc::bind<Glib::RefPtr<Gtk::TreeStore>>(sigc::mem_fun(*this, &StyleDialog::_onPropDelete), store));
511     }
512     Gtk::CellRendererText *label = Gtk::manage(new Gtk::CellRendererText());
513     label->property_placeholder_text() = _("property");
514     label->property_editable() = true;
515     label->signal_edited().connect(sigc::bind<Glib::RefPtr<Gtk::TreeStore>, Gtk::TreeView *>(
516         sigc::mem_fun(*this, &StyleDialog::_nameEdited), store, css_tree));
517     label->signal_editing_started().connect(sigc::mem_fun(*this, &StyleDialog::_startNameEdit));
518     addCol = css_tree->append_column(" ", *label) - 1;
519     col = css_tree->get_column(addCol);
520     if (col) {
521         col->set_resizable(true);
522         col->add_attribute(label->property_text(), _mColumns._colName);
523     }
524     Gtk::CellRendererText *value = Gtk::manage(new Gtk::CellRendererText());
525     value->property_placeholder_text() = _("value");
526     value->property_editable() = true;
527     value->signal_edited().connect(
528         sigc::bind<Glib::RefPtr<Gtk::TreeStore>>(sigc::mem_fun(*this, &StyleDialog::_valueEdited), store));
529     value->signal_editing_started().connect(
530         sigc::bind<Glib::RefPtr<Gtk::TreeStore>>(sigc::mem_fun(*this, &StyleDialog::_startValueEdit), store));
531     addCol = css_tree->append_column(" ", *value) - 1;
532     col = css_tree->get_column(addCol);
533     if (col) {
534         col->add_attribute(value->property_text(), _mColumns._colValue);
535         col->set_expand(true);
536         col->add_attribute(value->property_strikethrough(), _mColumns._colStrike);
537     }
538     Inkscape::UI::Widget::IconRenderer *urlRenderer = manage(new Inkscape::UI::Widget::IconRenderer());
539     urlRenderer->add_icon("empty-icon");
540     urlRenderer->add_icon("edit-redo");
541     int urlCol = css_tree->append_column(" ", *urlRenderer) - 1;
542     Gtk::TreeViewColumn *urlcol = css_tree->get_column(urlCol);
543     if (urlcol) {
544         urlcol->set_min_width(40);
545         urlcol->set_max_width(40);
546         urlRenderer->signal_activated().connect(sigc::bind(sigc::mem_fun(*this, &StyleDialog::_onLinkObj), store));
547         urlcol->add_attribute(urlRenderer->property_icon(), _mColumns._colLinked);
548     }
549     std::map<Glib::ustring, Glib::ustring> attr_prop;
550     Gtk::TreeModel::Path path;
551     bool empty = true;
552     if (obj && obj->getRepr()->attribute("style")) {
553         Glib::ustring style = obj->getRepr()->attribute("style");
554         attr_prop = parseStyle(style);
555         for (auto iter : obj->style->properties()) {
556             if (attr_prop.count(iter->name())) {
557                 auto value = attr_prop[iter->name()];
558                 empty = false;
559                 Gtk::TreeModel::Row row = *(store->prepend());
560                 row[_mColumns._colSelector] = "style_properties";
561                 row[_mColumns._colSelectorPos] = 0;
562                 row[_mColumns._colActive] = true;
563                 row[_mColumns._colName] = iter->name();
564                 row[_mColumns._colValue] = value;
565                 row[_mColumns._colStrike] = false;
566                 row[_mColumns._colOwner] = Glib::ustring("Current value");
567                 row[_mColumns._colHref] = nullptr;
568                 row[_mColumns._colLinked] = false;
569                 if (is_url(value.c_str())) {
570                     auto id = value.substr(5, value.size() - 6);
571                     SPObject *elemref = nullptr;
572                     if ((elemref = document->getObjectById(id.c_str()))) {
573                         row[_mColumns._colHref] = elemref;
574                         row[_mColumns._colLinked] = true;
575                     }
576                 }
577                 _addOwnerStyle(iter->name(), "style attribute");
578             }
579         }
580         // this is to fix a bug on cairo win:
581         // https://gitlab.freedesktop.org/cairo/cairo/issues/338
582         // TODO: check if inkscape min cairo version has applied the patch proposed and remove (3 times)
583         if (empty) {
584             css_tree->hide();
585         }
586         _styleBox.pack_start(*css_selector_container, Gtk::PACK_EXPAND_WIDGET);
587     }
588     selectorpos++;
589     if (tokens.size() == 0) {
590         _updating = false;
591         return;
592     }
593     for (unsigned i = 0; i < tokens.size() - 1; i += 2) {
594         Glib::ustring selector = tokens[i];
595         REMOVE_SPACES(selector); // Remove leading/trailing spaces
596         // Get list of objects selector matches
597         std::vector<Glib::ustring> selectordata = Glib::Regex::split_simple(";", selector);
598         Glib::ustring selector_orig = selector;
599         if (!selectordata.empty()) {
600             selector = selectordata.back();
601         }
602         std::vector<SPObject *> objVec = _getObjVec(selector);
603         if (obj) {
604             bool stop = true;
605             for (auto objel : objVec) {
606                 if (objel == obj) {
607                     stop = false;
608                 }
609             }
610             if (stop) {
611                 _updating = false;
612                 selectorpos++;
613                 continue;
614             }
615         }
616         if (!obj && _current_selector != "" && _current_selector != selector) {
617             _updating = false;
618             selectorpos++;
619             continue;
620         }
621         if (!obj) {
622             bool present = false;
623             for (auto objv : objVec) {
624                 for (auto objsel : selection->objects()) {
625                     if (objv == objsel) {
626                         present = true;
627                         break;
628                     }
629                 }
630                 if (present) {
631                     break;
632                 }
633             }
634             if (!present) {
635                 _updating = false;
636                 selectorpos++;
637                 continue;
638             }
639         }
640         Glib::ustring properties;
641         // Check to make sure we do have a value to match selector.
642         if ((i + 1) < tokens.size()) {
643             properties = tokens[i + 1];
644         } else {
645             std::cerr << "StyleDialog::readStyleElement: Missing values "
646                          "for last selector!"
647                       << std::endl;
648         }
649         Glib::RefPtr<Gtk::Builder> _builder;
650         try {
651             _builder = Gtk::Builder::create_from_file(gladefile);
652         } catch (const Glib::Error &ex) {
653             g_warning("Glade file loading failed for filter effect dialog");
654             return;
655         }
656         Gtk::Box *css_selector_container;
657         _builder->get_widget("CSSSelectorContainer", css_selector_container);
658         Gtk::Label *css_selector;
659         _builder->get_widget("CSSSelector", css_selector);
660         Gtk::EventBox *css_selector_event_box;
661         _builder->get_widget("CSSSelectorEventBox", css_selector_event_box);
662         Gtk::Entry *css_edit_selector;
663         _builder->get_widget("CSSEditSelector", css_edit_selector);
664         Gtk::EventBox *css_selector_event_add;
665         _builder->get_widget("CSSSelectorEventAdd", css_selector_event_add);
666         css_selector_event_add->add_events(Gdk::BUTTON_RELEASE_MASK);
667         css_selector->set_text(selector);
668         Gtk::TreeView *css_tree;
669         _builder->get_widget("CSSTree", css_tree);
670         css_tree->get_style_context()->add_class("style_sheet");
671         Glib::RefPtr<Gtk::TreeStore> store = Gtk::TreeStore::create(_mColumns);
672         css_tree->set_model(store);
673         // I comment this feature, is working but seems obscure to undertand
674         // the user can edit selector name in current implementation
675         /* css_selector_event_box->signal_button_release_event().connect(
676             sigc::bind(sigc::mem_fun(*this, &StyleDialog::_selectorStartEdit), css_selector, css_edit_selector));
677         css_edit_selector->signal_key_press_event().connect(sigc::bind(
678             sigc::mem_fun(*this, &StyleDialog::_selectorEditKeyPress), store, css_selector, css_edit_selector));
679         css_edit_selector->signal_activate().connect(
680             sigc::bind(sigc::mem_fun(*this, &StyleDialog::_selectorActivate), store, css_selector, css_edit_selector));
681          */
682         Inkscape::UI::Widget::IconRenderer *addRenderer = manage(new Inkscape::UI::Widget::IconRenderer());
683         addRenderer->add_icon("edit-delete");
684         int addCol = css_tree->append_column(" ", *addRenderer) - 1;
685         Gtk::TreeViewColumn *col = css_tree->get_column(addCol);
686         if (col) {
687             addRenderer->signal_activated().connect(
688                 sigc::bind<Glib::RefPtr<Gtk::TreeStore>>(sigc::mem_fun(*this, &StyleDialog::_onPropDelete), store));
689         }
690         Gtk::CellRendererToggle *isactive = Gtk::manage(new Gtk::CellRendererToggle());
691         isactive->property_activatable() = true;
692         addCol = css_tree->append_column(" ", *isactive) - 1;
693         col = css_tree->get_column(addCol);
694         if (col) {
695             col->add_attribute(isactive->property_active(), _mColumns._colActive);
696             isactive->signal_toggled().connect(
697                 sigc::bind<Glib::RefPtr<Gtk::TreeStore>>(sigc::mem_fun(*this, &StyleDialog::_activeToggled), store));
698         }
699         Gtk::CellRendererText *label = Gtk::manage(new Gtk::CellRendererText());
700         label->property_placeholder_text() = _("property");
701         label->property_editable() = true;
702         label->signal_edited().connect(sigc::bind<Glib::RefPtr<Gtk::TreeStore>, Gtk::TreeView *>(
703             sigc::mem_fun(*this, &StyleDialog::_nameEdited), store, css_tree));
704         label->signal_editing_started().connect(sigc::mem_fun(*this, &StyleDialog::_startNameEdit));
705         addCol = css_tree->append_column(" ", *label) - 1;
706         col = css_tree->get_column(addCol);
707         if (col) {
708             col->set_resizable(true);
709             col->add_attribute(label->property_text(), _mColumns._colName);
710         }
711         Gtk::CellRendererText *value = Gtk::manage(new Gtk::CellRendererText());
712         value->property_editable() = true;
713         value->property_placeholder_text() = _("value");
714         value->signal_edited().connect(
715             sigc::bind<Glib::RefPtr<Gtk::TreeStore>>(sigc::mem_fun(*this, &StyleDialog::_valueEdited), store));
716         value->signal_editing_started().connect(
717             sigc::bind<Glib::RefPtr<Gtk::TreeStore>>(sigc::mem_fun(*this, &StyleDialog::_startValueEdit), store));
718         addCol = css_tree->append_column(" ", *value) - 1;
719         col = css_tree->get_column(addCol);
720         if (col) {
721             col->add_attribute(value->property_text(), _mColumns._colValue);
722             col->add_attribute(value->property_strikethrough(), _mColumns._colStrike);
723         }
724         Glib::ustring style = properties;
725         Glib::ustring comments = "";
726         while (style.find("/*") != std::string::npos) {
727             size_t beg = style.find("/*");
728             size_t end = style.find("*/");
729             if (end != std::string::npos && beg != std::string::npos) {
730                 comments = comments.append(style, beg + 2, end - beg - 2);
731                 style = style.erase(beg, end - beg + 2);
732             }
733         }
734         std::map<Glib::ustring, Glib::ustring> attr_prop_styleshet = parseStyle(style);
735         std::map<Glib::ustring, Glib::ustring> attr_prop_styleshet_comments = parseStyle(comments);
736         std::map<Glib::ustring, std::pair<Glib::ustring, bool>> result_props;
737         for (auto styled : attr_prop_styleshet) {
738             result_props[styled.first] = std::make_pair(styled.second, true);
739         }
740         for (auto styled : attr_prop_styleshet_comments) {
741             result_props[styled.first] = std::make_pair(styled.second, false);
742         }
743         empty = true;
744         css_selector_event_add->signal_button_release_event().connect(
745             sigc::bind<Glib::RefPtr<Gtk::TreeStore>, Gtk::TreeView *, Glib::ustring, gint>(
746                 sigc::mem_fun(*this, &StyleDialog::_addRow), store, css_tree, selector_orig, selectorpos));
747         if (obj) {
748             for (auto iter : result_props) {
749                 empty = false;
750                 Gtk::TreeIter iterstore = store->append();
751                 Gtk::TreeModel::Path path = (Gtk::TreeModel::Path)iterstore;
752                 Gtk::TreeModel::Row row = *(iterstore);
753                 row[_mColumns._colSelector] = selector_orig;
754                 row[_mColumns._colSelectorPos] = selectorpos;
755                 row[_mColumns._colActive] = iter.second.second;
756                 row[_mColumns._colName] = iter.first;
757                 row[_mColumns._colValue] = iter.second.first;
758                 const Glib::ustring value = row[_mColumns._colValue];
759                 if (iter.second.second) {
760                     Glib::ustring val = "";
761                     for (auto iterprop : obj->style->properties()) {
762                         if (iterprop->style_src != SPStyleSrc::UNSET && iterprop->name() == iter.first) {
763                             val = iterprop->get_value();
764                             break;
765                         }
766                     }
767                     guint32 r1 = 0; // if there's no color, return black
768                     r1 = sp_svg_read_color(value.c_str(), r1);
769                     guint32 r2 = 0; // if there's no color, return black
770                     r2 = sp_svg_read_color(val.c_str(), r2);
771                     if (attr_prop.count(iter.first) || (value != val && (r1 == 0 || r1 != r2))) {
772                         row[_mColumns._colStrike] = true;
773                         row[_mColumns._colOwner] = Glib::ustring("");
774                     } else {
775                         row[_mColumns._colStrike] = false;
776                         row[_mColumns._colOwner] = Glib::ustring("Current value");
777                         _addOwnerStyle(iter.first, selector);
778                     }
779                 } else {
780                     row[_mColumns._colStrike] = true;
781                     Glib::ustring tooltiptext = _("This value is commented out.");
782                     row[_mColumns._colOwner] = tooltiptext;
783                 }
784             }
785         } else {
786             for (auto iter : result_props) {
787                 empty = false;
788                 Gtk::TreeModel::Row row = *(store->prepend());
789                 row[_mColumns._colSelector] = selector_orig;
790                 row[_mColumns._colSelectorPos] = selectorpos;
791                 row[_mColumns._colActive] = iter.second.second;
792                 row[_mColumns._colName] = iter.first;
793                 row[_mColumns._colValue] = iter.second.first;
794                 row[_mColumns._colStrike] = false;
795                 row[_mColumns._colOwner] = Glib::ustring("Stylesheet value");
796             }
797         }
798         if (empty) {
799             css_tree->hide();
800         }
801         _styleBox.pack_start(*css_selector_container, Gtk::PACK_EXPAND_WIDGET);
802         selectorpos++;
803     }
804     try {
805         _builder = Gtk::Builder::create_from_file(gladefile);
806     } catch (const Glib::Error &ex) {
807         g_warning("Glade file loading failed for filter effect dialog.");
808         return;
809     }
810     _builder->get_widget("CSSSelector", css_selector);
811     css_selector->set_text("element.attributes");
812     _builder->get_widget("CSSSelectorContainer", css_selector_container);
813     _builder->get_widget("CSSSelectorEventAdd", css_selector_event_add);
814     css_selector_event_add->add_events(Gdk::BUTTON_RELEASE_MASK);
815     store = Gtk::TreeStore::create(_mColumns);
816     _builder->get_widget("CSSTree", css_tree);
817     css_tree->get_style_context()->add_class("style_attribute");
818     css_tree->set_model(store);
819     css_selector_event_add->signal_button_release_event().connect(
820         sigc::bind<Glib::RefPtr<Gtk::TreeStore>, Gtk::TreeView *, Glib::ustring, gint>(
821             sigc::mem_fun(*this, &StyleDialog::_addRow), store, css_tree, "attributes", selectorpos));
822     bool hasattributes = false;
823     empty = true;
824     if (obj) {
825         for (auto iter : obj->style->properties()) {
826             if (iter->style_src != SPStyleSrc::UNSET) {
827                 auto key = iter->id();
828                 if (key != SPAttr::FONT && key != SPAttr::D && key != SPAttr::MARKER) {
829                     const gchar *attr = obj->getRepr()->attribute(iter->name().c_str());
830                     if (attr) {
831                         if (!hasattributes) {
832                             Inkscape::UI::Widget::IconRenderer *addRenderer =
833                                 manage(new Inkscape::UI::Widget::IconRenderer());
834                             addRenderer->add_icon("edit-delete");
835                             int addCol = css_tree->append_column(" ", *addRenderer) - 1;
836                             Gtk::TreeViewColumn *col = css_tree->get_column(addCol);
837                             if (col) {
838                                 addRenderer->signal_activated().connect(sigc::bind<Glib::RefPtr<Gtk::TreeStore>>(
839                                     sigc::mem_fun(*this, &StyleDialog::_onPropDelete), store));
840                             }
841                             Gtk::CellRendererText *label = Gtk::manage(new Gtk::CellRendererText());
842                             label->property_placeholder_text() = _("property");
843                             label->property_editable() = true;
844                             label->signal_edited().connect(sigc::bind<Glib::RefPtr<Gtk::TreeStore>, Gtk::TreeView *>(
845                                 sigc::mem_fun(*this, &StyleDialog::_nameEdited), store, css_tree));
846                             label->signal_editing_started().connect(sigc::mem_fun(*this, &StyleDialog::_startNameEdit));
847                             addCol = css_tree->append_column(" ", *label) - 1;
848                             col = css_tree->get_column(addCol);
849                             if (col) {
850                                 col->set_resizable(true);
851                                 col->add_attribute(label->property_text(), _mColumns._colName);
852                             }
853                             Gtk::CellRendererText *value = Gtk::manage(new Gtk::CellRendererText());
854                             value->property_placeholder_text() = _("value");
855                             value->property_editable() = true;
856                             value->signal_edited().connect(sigc::bind<Glib::RefPtr<Gtk::TreeStore>>(
857                                 sigc::mem_fun(*this, &StyleDialog::_valueEdited), store));
858                             value->signal_editing_started().connect(sigc::bind<Glib::RefPtr<Gtk::TreeStore>>(
859                                 sigc::mem_fun(*this, &StyleDialog::_startValueEdit), store));
860 
861                             addCol = css_tree->append_column(" ", *value) - 1;
862                             col = css_tree->get_column(addCol);
863                             if (col) {
864                                 col->add_attribute(value->property_text(), _mColumns._colValue);
865                                 col->add_attribute(value->property_strikethrough(), _mColumns._colStrike);
866                             }
867                         }
868                         empty = false;
869                         Gtk::TreeIter iterstore = store->prepend();
870                         Gtk::TreeModel::Path path = (Gtk::TreeModel::Path)iterstore;
871                         Gtk::TreeModel::Row row = *(iterstore);
872                         row[_mColumns._colSelector] = "attributes";
873                         row[_mColumns._colSelectorPos] = selectorpos;
874                         row[_mColumns._colActive] = true;
875                         row[_mColumns._colName] = iter->name();
876                         row[_mColumns._colValue] = attr;
877                         if (_owner_style.find(iter->name()) != _owner_style.end()) {
878                             row[_mColumns._colStrike] = true;
879                             Glib::ustring tooltiptext = Glib::ustring("");
880                             row[_mColumns._colOwner] = tooltiptext;
881                         } else {
882                             row[_mColumns._colStrike] = false;
883                             row[_mColumns._colOwner] = Glib::ustring("Current value");
884                             _addOwnerStyle(iter->name(), "inline attributes");
885                         }
886                         hasattributes = true;
887                     }
888                 }
889             }
890         }
891         if (empty) {
892             css_tree->hide();
893         }
894         if (!hasattributes) {
895             for (auto widg : css_selector_container->get_children()) {
896                 delete widg;
897             }
898         }
899         _styleBox.pack_start(*css_selector_container, Gtk::PACK_EXPAND_WIDGET);
900     }
901     for (auto selector : _styleBox.get_children()) {
902         Gtk::Box *box = dynamic_cast<Gtk::Box *>(&selector[0]);
903         if (box) {
904             std::vector<Gtk::Widget *> childs = box->get_children();
905             if (childs.size() > 1) {
906                 Gtk::TreeView *css_tree = dynamic_cast<Gtk::TreeView *>(childs[1]);
907                 if (css_tree) {
908                     Glib::RefPtr<Gtk::TreeModel> model = css_tree->get_model();
909                     if (model) {
910                         model->foreach_iter(sigc::mem_fun(*this, &StyleDialog::_on_foreach_iter));
911                     }
912                 }
913             }
914         }
915     }
916     if (obj) {
917         obj->style->readFromObject(obj);
918         obj->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG);
919     }
920     _mainBox.show_all_children();
921     _updating = false;
922 }
923 
_selectorStartEdit(GdkEventButton * event,Gtk::Label * selector,Gtk::Entry * selector_edit)924 bool StyleDialog::_selectorStartEdit(GdkEventButton *event, Gtk::Label *selector, Gtk::Entry *selector_edit)
925 {
926     g_debug("StyleDialog::_selectorStartEdit");
927     if (event->type == GDK_BUTTON_RELEASE && event->button == 1) {
928         selector->hide();
929         selector_edit->set_text(selector->get_text());
930         selector_edit->show();
931     }
932     return false;
933 }
934 
935 /* void StyleDialog::_selectorActivate(Glib::RefPtr<Gtk::TreeStore> store, Gtk::Label *selector, Gtk::Entry
936 *selector_edit)
937 {
938     g_debug("StyleDialog::_selectorEditKeyPress");
939     Glib::ustring newselector = fixCSSSelectors(selector_edit->get_text());
940     if (newselector.empty()) {
941         selector_edit->get_style_context()->add_class("system_error_color");
942         return;
943     }
944     _writeStyleElement(store, selector->get_text(), selector_edit->get_text());
945 } */
946 
_selectorEditKeyPress(GdkEventKey * event,Glib::RefPtr<Gtk::TreeStore> store,Gtk::Label * selector,Gtk::Entry * selector_edit)947 bool StyleDialog::_selectorEditKeyPress(GdkEventKey *event, Glib::RefPtr<Gtk::TreeStore> store, Gtk::Label *selector,
948                                         Gtk::Entry *selector_edit)
949 {
950     g_debug("StyleDialog::_selectorEditKeyPress");
951     switch (event->keyval) {
952         case GDK_KEY_Escape:
953             selector->show();
954             selector_edit->hide();
955             selector_edit->get_style_context()->remove_class("system_error_color");
956             break;
957     }
958     return false;
959 }
960 
_on_foreach_iter(const Gtk::TreeModel::iterator & iter)961 bool StyleDialog::_on_foreach_iter(const Gtk::TreeModel::iterator &iter)
962 {
963     g_debug("StyleDialog::_on_foreach_iter");
964 
965     Gtk::TreeModel::Row row = *(iter);
966     Glib::ustring owner = row[_mColumns._colOwner];
967     if (owner.empty()) {
968         Glib::ustring value = _owner_style[row[_mColumns._colName]];
969         Glib::ustring tooltiptext = Glib::ustring(_("Current value"));
970         if (!value.empty()) {
971             tooltiptext = Glib::ustring::compose(_("Used in %1"), _owner_style[row[_mColumns._colName]]);
972             row[_mColumns._colStrike] = true;
973         } else {
974             row[_mColumns._colStrike] = false;
975         }
976         row[_mColumns._colOwner] = tooltiptext;
977     }
978     return false;
979 }
980 
_onLinkObj(Glib::ustring path,Glib::RefPtr<Gtk::TreeStore> store)981 void StyleDialog::_onLinkObj(Glib::ustring path, Glib::RefPtr<Gtk::TreeStore> store)
982 {
983     g_debug("StyleDialog::_onLinkObj");
984 
985     Gtk::TreeModel::Row row = *store->get_iter(path);
986     if (row && row[_mColumns._colLinked]) {
987         SPObject *linked = row[_mColumns._colHref];
988         if (linked) {
989             Inkscape::Selection *selection = _desktop->getSelection();
990             _desktop->getDocument()->setXMLDialogSelectedObject(linked);
991             selection->clear();
992             selection->set(linked);
993         }
994     }
995 }
996 
997 /**
998  * @brief StyleDialog::_onPropDelete
999  * @param event
1000  * @return true
1001  * Delete the attribute from the style
1002  */
_onPropDelete(Glib::ustring path,Glib::RefPtr<Gtk::TreeStore> store)1003 void StyleDialog::_onPropDelete(Glib::ustring path, Glib::RefPtr<Gtk::TreeStore> store)
1004 {
1005     g_debug("StyleDialog::_onPropDelete");
1006     Gtk::TreeModel::Row row = *store->get_iter(path);
1007     if (row) {
1008         Glib::ustring selector = row[_mColumns._colSelector];
1009         row[_mColumns._colName] = "";
1010         _deleted_pos = row[_mColumns._colSelectorPos];
1011         store->erase(row);
1012         _deletion = true;
1013         _writeStyleElement(store, selector);
1014     }
1015 }
1016 
_addOwnerStyle(Glib::ustring name,Glib::ustring selector)1017 void StyleDialog::_addOwnerStyle(Glib::ustring name, Glib::ustring selector)
1018 {
1019     g_debug("StyleDialog::_addOwnerStyle");
1020 
1021     if (_owner_style.find(name) == _owner_style.end()) {
1022         _owner_style[name] = selector;
1023     }
1024 }
1025 
1026 
1027 /**
1028  * @brief StyleDialog::parseStyle
1029  *
1030  * Convert a style string into a vector map. This should be moved to style.cpp
1031  *
1032  */
parseStyle(Glib::ustring style_string)1033 std::map<Glib::ustring, Glib::ustring> StyleDialog::parseStyle(Glib::ustring style_string)
1034 {
1035     g_debug("StyleDialog::parseStyle");
1036 
1037     std::map<Glib::ustring, Glib::ustring> ret;
1038 
1039     REMOVE_SPACES(style_string); // We'd use const, but we need to trip spaces
1040     std::vector<Glib::ustring> props = r_props->split(style_string);
1041 
1042     for (auto token : props) {
1043         REMOVE_SPACES(token);
1044 
1045         if (token.empty())
1046             break;
1047         std::vector<Glib::ustring> pair = r_pair->split(token);
1048 
1049         if (pair.size() > 1) {
1050             ret[pair[0]] = pair[1];
1051         }
1052     }
1053     return ret;
1054 }
1055 
1056 
1057 /**
1058  * Update the content of the style element as selectors (or objects) are added/removed.
1059  */
_writeStyleElement(Glib::RefPtr<Gtk::TreeStore> store,Glib::ustring selector,Glib::ustring new_selector)1060 void StyleDialog::_writeStyleElement(Glib::RefPtr<Gtk::TreeStore> store, Glib::ustring selector,
1061                                      Glib::ustring new_selector)
1062 {
1063     g_debug("StyleDialog::_writeStyleElemen");
1064     if (_updating) {
1065         return;
1066     }
1067     _scroollock = true;
1068     Inkscape::Selection *selection = _desktop->getSelection();
1069     SPObject *obj = nullptr;
1070     if (selection->objects().size() == 1) {
1071         obj = selection->objects().back();
1072     }
1073     if (!obj) {
1074         obj = _desktop->getDocument()->getXMLDialogSelectedObject();
1075     }
1076     if (selection->objects().size() < 2 && !obj) {
1077         readStyleElement();
1078         return;
1079     }
1080     _updating = true;
1081     gint selectorpos = 0;
1082     std::string styleContent = "";
1083     if (selector != "style_properties" && selector != "attributes") {
1084         if (!new_selector.empty()) {
1085             selector = new_selector;
1086         }
1087         std::vector<Glib::ustring> selectordata = Glib::Regex::split_simple(";", selector);
1088         for (auto selectoritem : selectordata) {
1089             if (selectordata[selectordata.size() - 1] == selectoritem) {
1090                 selector = selectoritem;
1091             } else {
1092                 styleContent = styleContent + selectoritem + ";\n";
1093             }
1094         }
1095         styleContent.append("\n").append(selector.raw()).append(" { \n");
1096     }
1097     selectorpos = _deleted_pos;
1098     for (auto &row : store->children()) {
1099         selector = row[_mColumns._colSelector];
1100         selectorpos = row[_mColumns._colSelectorPos];
1101         const char *opencomment = "";
1102         const char *closecomment = "";
1103         if (selector != "style_properties" && selector != "attributes") {
1104             opencomment = row[_mColumns._colActive] ? "    " : "  /*";
1105             closecomment = row[_mColumns._colActive] ? "\n" : "*/\n";
1106         }
1107         Glib::ustring const &name = row[_mColumns._colName];
1108         Glib::ustring const &value = row[_mColumns._colValue];
1109         if (!(name.empty() && value.empty())) {
1110             styleContent = styleContent + opencomment + name.raw() + ":" + value.raw() + ";" + closecomment;
1111         }
1112     }
1113     if (selector != "style_properties" && selector != "attributes") {
1114         styleContent = styleContent + "}";
1115     }
1116     if (selector == "style_properties") {
1117         _updating = true;
1118         obj->getRepr()->setAttribute("style", styleContent);
1119         _updating = false;
1120     } else if (selector == "attributes") {
1121         for (auto iter : obj->style->properties()) {
1122             auto key = iter->id();
1123             if (key != SPAttr::FONT && key != SPAttr::D && key != SPAttr::MARKER) {
1124                 const gchar *attr = obj->getRepr()->attribute(iter->name().c_str());
1125                 if (attr) {
1126                     _updating = true;
1127                     obj->getRepr()->removeAttribute(iter->name());
1128                     _updating = false;
1129                 }
1130             }
1131         }
1132         for (auto &row : store->children()) {
1133             Glib::ustring const &name = row[_mColumns._colName];
1134             Glib::ustring const &value = row[_mColumns._colValue];
1135             if (!(name.empty() && value.empty())) {
1136                 _updating = true;
1137                 obj->getRepr()->setAttribute(name, value);
1138                 _updating = false;
1139             }
1140         }
1141     } else if (!selector.empty()) { // styleshet
1142         // We could test if styleContent is empty and then delete the style node here but there is no
1143         // harm in keeping it around ...
1144 
1145         std::string pos = std::to_string(selectorpos);
1146         std::string selectormatch = "(";
1147         for (; selectorpos > 1; selectorpos--) {
1148             selectormatch = selectormatch + "[^}]*?}";
1149         }
1150         selectormatch = selectormatch + ")([^}]*?})((.|\n)*)";
1151 
1152         Inkscape::XML::Node *textNode = _getStyleTextNode(true);
1153         std::regex e(selectormatch.c_str());
1154         std::string content = (textNode->content() ? textNode->content() : "");
1155         std::string result;
1156         std::regex_replace(std::back_inserter(result), content.begin(), content.end(), e, "$1" + styleContent + "$3");
1157         bool empty = false;
1158         if (result.empty()) {
1159             empty = true;
1160             result = "* > .inkscapehacktmp{}";
1161         }
1162         textNode->setContent(result.c_str());
1163         if (empty) {
1164             textNode->setContent("");
1165         }
1166     }
1167     _updating = false;
1168     readStyleElement();
1169     SPDocument *document = SP_ACTIVE_DOCUMENT;
1170     for (auto iter : document->getObjectsBySelector(selector)) {
1171         iter->style->readFromObject(iter);
1172         iter->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG);
1173     }
1174     DocumentUndo::done(SP_ACTIVE_DOCUMENT, SP_VERB_DIALOG_STYLE, _("Edited style element."));
1175 
1176     g_debug("StyleDialog::_writeStyleElement(): | %s |", styleContent.c_str());
1177 }
1178 
_addRow(GdkEventButton * evt,Glib::RefPtr<Gtk::TreeStore> store,Gtk::TreeView * css_tree,Glib::ustring selector,gint pos)1179 bool StyleDialog::_addRow(GdkEventButton *evt, Glib::RefPtr<Gtk::TreeStore> store, Gtk::TreeView *css_tree,
1180                           Glib::ustring selector, gint pos)
1181 {
1182     g_debug("StyleDialog::_addRow");
1183 
1184     if (evt->type == GDK_BUTTON_RELEASE && evt->button == 1) {
1185         Gtk::TreeIter iter = store->prepend();
1186         Gtk::TreeModel::Path path = (Gtk::TreeModel::Path)iter;
1187         Gtk::TreeModel::Row row = *(iter);
1188         row[_mColumns._colSelector] = selector;
1189         row[_mColumns._colSelectorPos] = pos;
1190         row[_mColumns._colActive] = true;
1191         row[_mColumns._colName] = "";
1192         row[_mColumns._colValue] = "";
1193         row[_mColumns._colStrike] = false;
1194         gint col = 2;
1195         if (pos < 1) {
1196             col = 1;
1197         }
1198         css_tree->show();
1199         css_tree->set_cursor(path, *(css_tree->get_column(col)), true);
1200         grab_focus();
1201         return true;
1202     }
1203     return false;
1204 }
1205 
_setAutocompletion(Gtk::Entry * entry,SPStyleEnum const cssenum[])1206 void StyleDialog::_setAutocompletion(Gtk::Entry *entry, SPStyleEnum const cssenum[])
1207 {
1208     g_debug("StyleDialog::_setAutocompletion");
1209 
1210     Glib::RefPtr<Gtk::ListStore> completionModel = Gtk::ListStore::create(_mCSSData);
1211     Glib::RefPtr<Gtk::EntryCompletion> entry_completion = Gtk::EntryCompletion::create();
1212     entry_completion->set_model(completionModel);
1213     entry_completion->set_text_column (_mCSSData._colCSSData);
1214     entry_completion->set_minimum_key_length(0);
1215     entry_completion->set_popup_completion(true);
1216     gint counter = 0;
1217     const char * key = cssenum[counter].key;
1218     while (key) {
1219         Gtk::TreeModel::Row row = *(completionModel->prepend());
1220         row[_mCSSData._colCSSData] = Glib::ustring(key);
1221         counter++;
1222         key = cssenum[counter].key;
1223     }
1224     entry->set_completion(entry_completion);
1225 }
1226 /*Harcode values non in enum*/
_setAutocompletion(Gtk::Entry * entry,Glib::ustring name)1227 void StyleDialog::_setAutocompletion(Gtk::Entry *entry, Glib::ustring name)
1228 {
1229     g_debug("StyleDialog::_setAutocompletion");
1230 
1231     Glib::RefPtr<Gtk::ListStore> completionModel = Gtk::ListStore::create(_mCSSData);
1232     Glib::RefPtr<Gtk::EntryCompletion> entry_completion = Gtk::EntryCompletion::create();
1233     entry_completion->set_model(completionModel);
1234     entry_completion->set_text_column(_mCSSData._colCSSData);
1235     entry_completion->set_minimum_key_length(0);
1236     entry_completion->set_popup_completion(true);
1237     if (name == "paint-order") {
1238         Gtk::TreeModel::Row row = *(completionModel->append());
1239         row[_mCSSData._colCSSData] = Glib::ustring("fill markers stroke");
1240         row = *(completionModel->append());
1241         row[_mCSSData._colCSSData] = Glib::ustring("fill stroke markers");
1242         row = *(completionModel->append());
1243         row[_mCSSData._colCSSData] = Glib::ustring("stroke markers fill");
1244         row = *(completionModel->append());
1245         row[_mCSSData._colCSSData] = Glib::ustring("stroke fill markers");
1246         row = *(completionModel->append());
1247         row[_mCSSData._colCSSData] = Glib::ustring("markers fill stroke");
1248         row = *(completionModel->append());
1249         row[_mCSSData._colCSSData] = Glib::ustring("markers stroke fill");
1250     }
1251     entry->set_completion(entry_completion);
1252 }
1253 
1254 void
_startValueEdit(Gtk::CellEditable * cell,const Glib::ustring & path,Glib::RefPtr<Gtk::TreeStore> store)1255 StyleDialog::_startValueEdit(Gtk::CellEditable* cell, const Glib::ustring& path, Glib::RefPtr<Gtk::TreeStore> store)
1256 {
1257     g_debug("StyleDialog::_startValueEdit");
1258     _deletion = false;
1259     _scroollock = true;
1260     Gtk::TreeModel::Row row = *store->get_iter(path);
1261     if (row) {
1262         Gtk::Entry *entry = dynamic_cast<Gtk::Entry *>(cell);
1263         Glib::ustring name = row[_mColumns._colName];
1264         if (name == "paint-order") {
1265             _setAutocompletion(entry, name);
1266         } else if (name == "fill-rule") {
1267             _setAutocompletion(entry, enum_fill_rule);
1268         } else if (name == "stroke-linecap") {
1269             _setAutocompletion(entry, enum_stroke_linecap);
1270         } else if (name == "stroke-linejoin") {
1271             _setAutocompletion(entry, enum_stroke_linejoin);
1272         } else if (name == "font-style") {
1273             _setAutocompletion(entry, enum_font_style);
1274         } else if (name == "font-variant") {
1275             _setAutocompletion(entry, enum_font_variant);
1276         } else if (name == "font-weight") {
1277             _setAutocompletion(entry, enum_font_weight);
1278         } else if (name == "font-stretch") {
1279             _setAutocompletion(entry, enum_font_stretch);
1280         } else if (name == "font-variant-position") {
1281             _setAutocompletion(entry, enum_font_variant_position);
1282         } else if (name == "text-align") {
1283             _setAutocompletion(entry, enum_text_align);
1284         } else if (name == "text-transform") {
1285             _setAutocompletion(entry, enum_text_transform);
1286         } else if (name == "text-anchor") {
1287             _setAutocompletion(entry, enum_text_anchor);
1288         } else if (name == "white-space") {
1289             _setAutocompletion(entry, enum_white_space);
1290         } else if (name == "direction") {
1291             _setAutocompletion(entry, enum_direction);
1292         } else if (name == "baseline-shift") {
1293             _setAutocompletion(entry, enum_baseline_shift);
1294         } else if (name == "visibility") {
1295             _setAutocompletion(entry, enum_visibility);
1296         } else if (name == "overflow") {
1297             _setAutocompletion(entry, enum_overflow);
1298         } else if (name == "display") {
1299             _setAutocompletion(entry, enum_display);
1300         } else if (name == "shape-rendering") {
1301             _setAutocompletion(entry, enum_shape_rendering);
1302         } else if (name == "color-rendering") {
1303             _setAutocompletion(entry, enum_color_rendering);
1304         } else if (name == "overflow") {
1305             _setAutocompletion(entry, enum_overflow);
1306         } else if (name == "clip-rule") {
1307             _setAutocompletion(entry, enum_clip_rule);
1308         } else if (name == "color-interpolation") {
1309             _setAutocompletion(entry, enum_color_interpolation);
1310         }
1311         entry->signal_key_release_event().connect(
1312             sigc::bind(sigc::mem_fun(*this, &StyleDialog::_onValueKeyReleased), entry));
1313         entry->signal_key_press_event().connect(
1314             sigc::bind(sigc::mem_fun(*this, &StyleDialog::_onValueKeyPressed), entry));
1315     }
1316 }
1317 
_startNameEdit(Gtk::CellEditable * cell,const Glib::ustring & path)1318 void StyleDialog::_startNameEdit(Gtk::CellEditable *cell, const Glib::ustring &path)
1319 {
1320     _deletion = false;
1321     g_debug("StyleDialog::_startNameEdit");
1322     _scroollock = true;
1323     Glib::RefPtr<Gtk::ListStore> completionModel = Gtk::ListStore::create(_mCSSData);
1324     Glib::RefPtr<Gtk::EntryCompletion> entry_completion = Gtk::EntryCompletion::create();
1325     entry_completion->set_model(completionModel);
1326     entry_completion->set_text_column(_mCSSData._colCSSData);
1327     entry_completion->set_minimum_key_length(1);
1328     entry_completion->set_popup_completion(true);
1329     for (auto prop : sp_attribute_name_list(true)) {
1330         Gtk::TreeModel::Row row = *(completionModel->append());
1331         row[_mCSSData._colCSSData] = prop;
1332     }
1333     Gtk::Entry *entry = dynamic_cast<Gtk::Entry *>(cell);
1334     entry->set_completion(entry_completion);
1335     entry->signal_key_release_event().connect(
1336         sigc::bind(sigc::mem_fun(*this, &StyleDialog::_onNameKeyReleased), entry));
1337     entry->signal_key_press_event().connect(sigc::bind(sigc::mem_fun(*this, &StyleDialog::_onNameKeyPressed), entry));
1338 }
1339 
1340 
sp_styledialog_store_move_to_next(gpointer data)1341 gboolean sp_styledialog_store_move_to_next(gpointer data)
1342 {
1343     StyleDialog *styledialog = reinterpret_cast<StyleDialog *>(data);
1344     if (!styledialog->_deletion) {
1345         auto selection = styledialog->_current_css_tree->get_selection();
1346         Gtk::TreeIter iter = *(selection->get_selected());
1347         Gtk::TreeModel::Path model = (Gtk::TreeModel::Path)iter;
1348         if (model == styledialog->_current_path) {
1349             styledialog->_current_css_tree->set_cursor(styledialog->_current_path, *styledialog->_current_value_col,
1350                                                        true);
1351         }
1352     }
1353     return FALSE;
1354 }
1355 
1356 /**
1357  * @brief StyleDialog::nameEdited
1358  * @param event
1359  * @return
1360  * Called when the name is edited in the TreeView editable column
1361  */
_nameEdited(const Glib::ustring & path,const Glib::ustring & name,Glib::RefPtr<Gtk::TreeStore> store,Gtk::TreeView * css_tree)1362 void StyleDialog::_nameEdited(const Glib::ustring &path, const Glib::ustring &name, Glib::RefPtr<Gtk::TreeStore> store,
1363                               Gtk::TreeView *css_tree)
1364 {
1365     g_debug("StyleDialog::_nameEdited");
1366 
1367     _scroollock = true;
1368     Gtk::TreeModel::Row row = *store->get_iter(path);
1369     _current_path = (Gtk::TreeModel::Path)*store->get_iter(path);
1370 
1371     if (row) {
1372         _current_css_tree = css_tree;
1373         Glib::ustring finalname = name;
1374         auto i = finalname.find_first_of(";:=");
1375         if (i != std::string::npos) {
1376             finalname.erase(i, name.size() - i);
1377         }
1378         gint pos = row[_mColumns._colSelectorPos];
1379         bool write = false;
1380         if (row[_mColumns._colName] != finalname && row[_mColumns._colValue] != "") {
1381             write = true;
1382         }
1383         Glib::ustring selector = row[_mColumns._colSelector];
1384         Glib::ustring value = row[_mColumns._colValue];
1385         bool is_attr = selector == "attributes";
1386         Glib::ustring old_name = row[_mColumns._colName];
1387         row[_mColumns._colName] = finalname;
1388         if (finalname.empty() && value.empty()) {
1389             _deleted_pos = row[_mColumns._colSelectorPos];
1390             store->erase(row);
1391         }
1392         gint col = 3;
1393         if (pos < 1 || is_attr) {
1394             col = 2;
1395         }
1396         _current_value_col = css_tree->get_column(col);
1397         if (write && old_name != name) {
1398             _writeStyleElement(store, selector);
1399             /*
1400             I think is better comment this, is enoght update on value change
1401             if (selector != "style_properties" && selector != "attributes") {
1402             std::vector<SPObject *> objs = _getObjVec(selector);
1403             for (auto obj : objs){
1404                 Glib::ustring css_str = "";
1405                 SPCSSAttr *css = sp_repr_css_attr_new();
1406                 sp_repr_css_attr_add_from_string(css, obj->getRepr()->attribute("style"));
1407                 css->removeAttribute(name);
1408                 sp_repr_css_write_string(css, css_str);
1409                 obj->getRepr()->setAttributeOrRemoveIfEmpty("style", css_str);
1410                 obj->style->readFromObject(obj);
1411                 obj->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG);
1412             }
1413             } */
1414         } else {
1415             g_timeout_add(50, &sp_styledialog_store_move_to_next, this);
1416             grab_focus();
1417         }
1418     }
1419 }
1420 
1421 /**
1422  * @brief StyleDialog::valueEdited
1423  * @param event
1424  * @return
1425  * Called when the value is edited in the TreeView editable column
1426  */
_valueEdited(const Glib::ustring & path,const Glib::ustring & value,Glib::RefPtr<Gtk::TreeStore> store)1427 void StyleDialog::_valueEdited(const Glib::ustring &path, const Glib::ustring &value,
1428                                Glib::RefPtr<Gtk::TreeStore> store)
1429 {
1430     g_debug("StyleDialog::_valueEdited");
1431 
1432     _scroollock = true;
1433 
1434     Gtk::TreeModel::Row row = *store->get_iter(path);
1435     if (row) {
1436         Glib::ustring finalvalue = value;
1437         auto i = std::min(finalvalue.find(";"), finalvalue.find(":"));
1438         if (i != std::string::npos) {
1439             finalvalue.erase(i, finalvalue.size() - i);
1440         }
1441         Glib::ustring old_value = row[_mColumns._colValue];
1442         if (old_value == finalvalue) {
1443             return;
1444         }
1445         row[_mColumns._colValue] = finalvalue;
1446         Glib::ustring selector = row[_mColumns._colSelector];
1447         Glib::ustring name = row[_mColumns._colName];
1448         if (name.empty() && finalvalue.empty()) {
1449             _deleted_pos = row[_mColumns._colSelectorPos];
1450             store->erase(row);
1451         }
1452         _writeStyleElement(store, selector);
1453         if (selector != "style_properties" && selector != "attributes") {
1454             std::vector<SPObject *> objs = _getObjVec(selector);
1455             for (auto obj : objs) {
1456                 Glib::ustring css_str = "";
1457                 SPCSSAttr *css = sp_repr_css_attr_new();
1458                 sp_repr_css_attr_add_from_string(css, obj->getRepr()->attribute("style"));
1459                 css->removeAttribute(name);
1460                 sp_repr_css_write_string(css, css_str);
1461                 obj->getRepr()->setAttribute("style", css_str);
1462                 obj->style->readFromObject(obj);
1463                 obj->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG);
1464             }
1465         }
1466     }
1467 }
1468 
_activeToggled(const Glib::ustring & path,Glib::RefPtr<Gtk::TreeStore> store)1469 void StyleDialog::_activeToggled(const Glib::ustring &path, Glib::RefPtr<Gtk::TreeStore> store)
1470 {
1471     g_debug("StyleDialog::_activeToggled");
1472 
1473     _scroollock = true;
1474     Gtk::TreeModel::Row row = *store->get_iter(path);
1475     if (row) {
1476         row[_mColumns._colActive] = !row[_mColumns._colActive];
1477         Glib::ustring selector = row[_mColumns._colSelector];
1478         _writeStyleElement(store, selector);
1479     }
1480 }
1481 
_onNameKeyPressed(GdkEventKey * event,Gtk::Entry * entry)1482 bool StyleDialog::_onNameKeyPressed(GdkEventKey *event, Gtk::Entry *entry)
1483 {
1484     g_debug("StyleDialog::_onNameKeyReleased");
1485     bool ret = false;
1486     switch (event->keyval) {
1487         case GDK_KEY_Tab:
1488         case GDK_KEY_KP_Tab:
1489             entry->editing_done();
1490             ret = true;
1491             break;
1492     }
1493     return ret;
1494 }
1495 
_onNameKeyReleased(GdkEventKey * event,Gtk::Entry * entry)1496 bool StyleDialog::_onNameKeyReleased(GdkEventKey *event, Gtk::Entry *entry)
1497 {
1498     g_debug("StyleDialog::_onNameKeyReleased");
1499     bool ret = false;
1500     switch (event->keyval) {
1501         case GDK_KEY_equal:
1502         case GDK_KEY_colon:
1503             entry->editing_done();
1504             ret = true;
1505             break;
1506         case GDK_KEY_Shift_L:
1507         case GDK_KEY_Shift_R:
1508         case GDK_KEY_semicolon: {
1509             Glib::ustring text = entry->get_text();
1510             auto i = std::min(text.find(";"), text.find(":"));
1511             if (i != std::string::npos) {
1512                 entry->editing_done();
1513                 ret = true;
1514             }
1515             break;
1516         }
1517     }
1518     return ret;
1519 }
1520 
_onValueKeyPressed(GdkEventKey * event,Gtk::Entry * entry)1521 bool StyleDialog::_onValueKeyPressed(GdkEventKey *event, Gtk::Entry *entry)
1522 {
1523     g_debug("StyleDialog::_onValueKeyReleased");
1524     bool ret = false;
1525     switch (event->keyval) {
1526         case GDK_KEY_Tab:
1527         case GDK_KEY_KP_Tab:
1528             entry->editing_done();
1529             ret = true;
1530             break;
1531     }
1532     return ret;
1533 }
1534 
_onValueKeyReleased(GdkEventKey * event,Gtk::Entry * entry)1535 bool StyleDialog::_onValueKeyReleased(GdkEventKey *event, Gtk::Entry *entry)
1536 {
1537     g_debug("StyleDialog::_onValueKeyReleased");
1538     bool ret = false;
1539     switch (event->keyval) {
1540         case GDK_KEY_semicolon:
1541             entry->editing_done();
1542             ret = true;
1543             break;
1544         case GDK_KEY_Shift_L:
1545         case GDK_KEY_Shift_R:
1546         case GDK_KEY_colon: {
1547             Glib::ustring text = entry->get_text();
1548             auto i = std::min(text.find(";"), text.find(":"));
1549             if (i != std::string::npos) {
1550                 entry->editing_done();
1551                 ret = true;
1552             }
1553             break;
1554         }
1555     }
1556     return ret;
1557 }
1558 
1559 /**
1560  * Update the watchers on objects.
1561  */
_updateWatchers(SPDesktop * desktop)1562 void StyleDialog::_updateWatchers(SPDesktop *desktop)
1563 
1564 {
1565     g_debug("StyleDialog::_updateWatchers");
1566 
1567     if (_textNode) {
1568         _textNode->removeObserver(*m_styletextwatcher);
1569         _textNode = nullptr;
1570     }
1571 
1572     if (m_root) {
1573         m_root->removeSubtreeObserver(*m_nodewatcher);
1574         m_root = nullptr;
1575     }
1576 
1577     if (desktop) {
1578         m_root = desktop->getDocument()->getReprRoot();
1579         m_root->addSubtreeObserver(*m_nodewatcher);
1580     }
1581 }
1582 
1583 
1584 /**
1585  * @param selector: a valid CSS selector string.
1586  * @return objVec: a vector of pointers to SPObject's the selector matches.
1587  * Return a vector of all objects that selector matches.
1588  */
_getObjVec(Glib::ustring selector)1589 std::vector<SPObject *> StyleDialog::_getObjVec(Glib::ustring selector)
1590 {
1591     g_debug("StyleDialog::_getObjVec");
1592 
1593     g_assert(selector.find(";") == Glib::ustring::npos);
1594 
1595     return _desktop->getDocument()->getObjectsBySelector(selector);
1596 }
1597 
_closeDialog(Gtk::Dialog * textDialogPtr)1598 void StyleDialog::_closeDialog(Gtk::Dialog *textDialogPtr) { textDialogPtr->response(Gtk::RESPONSE_OK); }
1599 
1600 
1601 /**
1602  * Handle document replaced. (Happens when a default document is immediately replaced by another
1603  * document in a new window.)
1604  */
_handleDocumentReplaced(SPDesktop * desktop,SPDocument *)1605 void StyleDialog::_handleDocumentReplaced(SPDesktop *desktop, SPDocument * /* document */)
1606 {
1607     g_debug("StyleDialog::handleDocumentReplaced()");
1608 
1609     _selection_changed_connection.disconnect();
1610     _updateWatchers(desktop);
1611 
1612     if (!desktop)
1613         return;
1614 
1615     _selection_changed_connection =
1616         desktop->getSelection()->connectChanged(sigc::hide(sigc::mem_fun(this, &StyleDialog::_handleSelectionChanged)));
1617 
1618     readStyleElement();
1619 }
1620 
1621 
1622 /*
1623  * When a dialog is floating, it is connected to the active desktop.
1624  */
update()1625 void StyleDialog::update()
1626 {
1627     if (!_app) {
1628         std::cerr << "UndoHistory::update(): _app is null" << std::endl;
1629         return;
1630     }
1631 
1632     SPDesktop *desktop = getDesktop();
1633     if (!desktop || _desktop == desktop) {
1634         return;
1635     }
1636 
1637     _desktop = desktop;
1638     _document_replaced_connection.disconnect();
1639 
1640     if (desktop) {
1641         _document_replaced_connection =
1642         desktop->connectDocumentReplaced(sigc::mem_fun(this, &StyleDialog::_handleDocumentReplaced));
1643     }
1644 
1645     _handleDocumentReplaced(desktop, nullptr);
1646 }
1647 
1648 /*
1649  * Handle a change in which objects are selected in a document.
1650  */
_handleSelectionChanged()1651 void StyleDialog::_handleSelectionChanged()
1652 {
1653     g_debug("StyleDialog::_handleSelectionChanged()");
1654     _scroolpos = 0;
1655     _vadj->set_value(0);
1656     // Sometimes the selection changes because inkscape is closing.
1657     if (getDesktop()) {
1658         readStyleElement();
1659     }
1660 }
1661 
1662 } // namespace Dialog
1663 } // namespace UI
1664 } // namespace Inkscape
1665 
1666 /*
1667   Local Variables:
1668   mode:c++
1669   c-file-style:"stroustrup"
1670   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1671   indent-tabs-mode:nil
1672   fill-column:99
1673   End:
1674 */
1675 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
1676