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