Inkscape
Vector Graphics Editor
Loading...
Searching...
No Matches
styledialog.cpp
Go to the documentation of this file.
1// SPDX-License-Identifier: GPL-2.0-or-later
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
18#include <algorithm>
19#include <cstddef>
20#include <iostream>
21#include <regex>
22#include <string>
23#include <utility>
24
25#include <glibmm/i18n.h>
26#include <glibmm/regex.h>
27#include <gdk/gdkkeysyms.h>
28#include <gtkmm/adjustment.h>
29#include <gtkmm/box.h>
30#include <gtkmm/button.h>
31#include <gtkmm/cellrenderertext.h>
32#include <gtkmm/cellrenderertoggle.h>
33#include <gtkmm/dialog.h>
34#include <gtkmm/entry.h>
35#include <gtkmm/eventcontrollerfocus.h>
36#include <gtkmm/eventcontrollerkey.h>
37#include <gtkmm/label.h>
38#include <gtkmm/liststore.h>
39#include <gtkmm/treemodel.h>
40#include <gtkmm/treestore.h>
41#include <gtkmm/treeviewcolumn.h>
42#include <gtkmm/treeview.h>
43#include <sigc++/adaptors/bind.h>
44#include <sigc++/functors/mem_fun.h>
45
46#include "colors/manager.h"
47#include "colors/color.h"
48#include "attribute-rel-svg.h"
49#include "attributes.h"
50#include "document.h"
51#include "document-undo.h"
52#include "inkscape.h"
53#include "selection.h"
54#include "style.h"
55#include "style-enums.h"
56#include "style-internal.h"
57
58#include "io/resource.h"
59#include "object/sp-object.h"
60#include "ui/builder-utils.h"
61#include "ui/controller.h"
62#include "ui/icon-loader.h"
63#include "ui/pack.h"
64#include "ui/util.h"
66#include "util/trim.h"
68#include "xml/node-observer.h"
69#include "xml/sp-css-attr.h"
70
71// G_MESSAGES_DEBUG=DEBUG_STYLEDIALOG gdb ./inkscape
72// #define DEBUG_STYLEDIALOG
73// #define G_LOG_DOMAIN "STYLEDIALOG"
74
75using std::size_t;
77
78namespace Inkscape::UI::Dialog {
79
87{
88 static GQuark const CODE_svg_style = g_quark_from_static_string("svg:style");
89 static GQuark const CODE_svg_defs = g_quark_from_static_string("svg:defs");
90
91 XML::Node *styleNode = nullptr;
92 XML::Node *textNode = nullptr;
93
94 if (!root) {
95 return nullptr;
96 }
97
98 for (auto *node = root->firstChild(); node; node = node->next()) {
99 if (node->code() == CODE_svg_defs) {
100 textNode = get_first_style_text_node(node, false);
101 if (textNode != nullptr) {
102 return textNode;
103 }
104 }
105
106 if (node->code() == CODE_svg_style) {
107 styleNode = node;
108 break;
109 }
110 }
111
112 if (styleNode == nullptr) {
113 if (!create_if_missing)
114 return nullptr;
115
116 styleNode = root->document()->createElement("svg:style");
117 root->addChild(styleNode, nullptr);
118 Inkscape::GC::release(styleNode);
119 }
120
121 for (auto *node = styleNode->firstChild(); node; node = node->next()) {
123 textNode = node;
124 break;
125 }
126 }
127
128 if (textNode == nullptr) {
129 if (!create_if_missing)
130 return nullptr;
131
132 textNode = root->document()->createTextNode("");
133 styleNode->appendChild(textNode);
134 Inkscape::GC::release(textNode);
135 }
136
137 return textNode;
138}
139
140// Keeps a watch on style element
141class StyleDialog::NodeObserver : public Inkscape::XML::NodeObserver {
142 public:
143 NodeObserver(StyleDialog *styledialog)
144 : _styledialog(styledialog)
145 {
146 g_debug("StyleDialog::NodeObserver: Constructor");
147 };
148
149 void notifyContentChanged(Inkscape::XML::Node &node, Inkscape::Util::ptr_shared old_content,
150 Inkscape::Util::ptr_shared new_content) override;
151
152 StyleDialog *_styledialog;
153};
154
155
156void StyleDialog::NodeObserver::notifyContentChanged(Inkscape::XML::Node & /*node*/,
157 Inkscape::Util::ptr_shared /*old_content*/,
158 Inkscape::Util::ptr_shared /*new_content*/)
159{
160
161 g_debug("StyleDialog::NodeObserver::notifyContentChanged");
162 _styledialog->_updating = false;
163 _styledialog->readStyleElement();
164}
165
166// Keeps a watch for new/removed/changed nodes
167// (Must update objects that selectors match.)
168class StyleDialog::NodeWatcher : public Inkscape::XML::NodeObserver {
169 StyleDialog *_styledialog;
170
171public:
172 NodeWatcher(StyleDialog *styledialog)
173 : _styledialog(styledialog)
174 {
175 g_debug("StyleDialog::NodeWatcher: Constructor");
176 };
177
178 void notifyChildAdded(Inkscape::XML::Node & /*node*/, Inkscape::XML::Node &child,
179 Inkscape::XML::Node * /*prev*/) override
180 {
181 _styledialog->_nodeAdded(child);
182 }
183
184 void notifyChildRemoved(Inkscape::XML::Node & /*node*/, Inkscape::XML::Node &child,
185 Inkscape::XML::Node * /*prev*/) override
186 {
187 _styledialog->_nodeRemoved(child);
188 }
189
190 void notifyAttributeChanged(Inkscape::XML::Node &node, GQuark qname, Util::ptr_shared /*old_value*/,
191 Util::ptr_shared /*new_value*/) override
192 {
193 static GQuark const CODE_id = g_quark_from_static_string("id");
194 static GQuark const CODE_class = g_quark_from_static_string("class");
195 static GQuark const CODE_style = g_quark_from_static_string("style");
196
197 if (qname == CODE_id || qname == CODE_class || qname == CODE_style) {
198 _styledialog->_nodeChanged(node);
199 }
200 }
201};
202
204{
205 if (!getShowing()) {
206 return;
207 }
209}
210
212{
213 if (!getShowing()) {
214 return;
215 }
216 if (_textNode == &repr) {
217 _textNode = nullptr;
218 }
219
221}
222
224{
225 if (!getShowing()) {
226 return;
227 }
228 g_debug("StyleDialog::_nodeChanged");
230}
231
239 : DialogBase("/dialogs/style", "Style")
240 , m_nodewatcher {std::make_unique<StyleDialog::NodeWatcher >(this)}
241 , m_styletextwatcher{std::make_unique<StyleDialog::NodeObserver>(this)}
242{
243 g_debug("StyleDialog::StyleDialog");
244
246 _scrolledWindow.set_policy(Gtk::PolicyType::AUTOMATIC, Gtk::PolicyType::AUTOMATIC);
247
248 _styleBox.set_name("StyleBox");
249 _styleBox.set_orientation(Gtk::Orientation::VERTICAL);
250 _styleBox.set_valign(Gtk::Align::START);
251
252 _scrolledWindow.set_child(_styleBox);
253 _scrolledWindow.set_overlay_scrolling(false);
254 _vadj = _scrolledWindow.get_vadjustment();
255 _vadj->signal_value_changed().connect(sigc::mem_fun(*this, &StyleDialog::_vscroll));
256
257 _mainBox.set_orientation(Gtk::Orientation::VERTICAL);
258
260}
261
266
268{
269 if (!_scrollock) {
270 _scrollpos = _vadj->get_value();
271 } else {
272 _vadj->set_value(_scrollpos);
273 _scrollock = false;
274 }
275}
276
278
285{
286 g_debug("StyleDialog::_getStyleTextNoded");
287
288 auto const textNode = get_first_style_text_node(m_root, create_if_missing);
289
290 if (_textNode != textNode) {
291 if (_textNode) {
293 }
294
295 _textNode = textNode;
296
297 if (_textNode) {
299 }
300 }
301
302 return textNode;
303}
304
305void StyleDialog::setCurrentSelector(Glib::ustring current_selector)
306{
307 g_debug("StyleDialog::setCurrentSelector");
308 _current_selector = std::move(current_selector);
310}
311
312// copied from style.cpp:1499
313static bool is_url(char const *p)
314{
315 if (p == nullptr)
316 return false;
321 return (g_ascii_strncasecmp(p, "url(", 4) == 0);
322}
323
328{
329 g_debug("StyleDialog::readStyleElement");
330
331 auto document = getDocument();
332 if (_updating || !document || _deletion)
333 return; // Don't read if we wrote style element.
334 _updating = true;
335 _scrollock = true;
337
338 // Get content from style text node.
339 std::string content = (textNode && textNode->content()) ? textNode->content() : "";
340
341 // Remove end-of-lines (check it works on Windoze).
342 content.erase(std::remove(content.begin(), content.end(), '\n'), content.end());
343
344 // Remove comments (/* xxx */)
345
346 bool breakme = false;
347 size_t start = content.find("/*");
348 size_t open = content.find("{", start + 1);
349 size_t close = content.find("}", start + 1);
350 size_t end = content.find("*/", close + 1);
351 while (!breakme) {
352 if (open == std::string::npos || close == std::string::npos || end == std::string::npos) {
353 breakme = true;
354 break;
355 }
356 while (open < close) {
357 open = content.find("{", close + 1);
358 close = content.find("}", close + 1);
359 end = content.find("*/", close + 1);
360 size_t reopen = content.find("{", close + 1);
361 if (open == std::string::npos || end == std::string::npos || end < reopen) {
362 if (end < reopen) {
363 content = content.erase(start, end - start + 2);
364 } else {
365 breakme = true;
366 }
367 break;
368 }
369 }
370 start = content.find("/*", start + 1);
371 open = content.find("{", start + 1);
372 close = content.find("}", start + 1);
373 end = content.find("*/", close + 1);
374 }
375
376 // First split into selector/value chunks.
377 // An attempt to use Glib::Regex failed. A C++11 version worked but
378 // reportedly has problems on Windows. Using split_simple() is simpler
379 // and probably faster.
380 //
381 // Glib::RefPtr<Glib::Regex> regex1 =
382 // Glib::Regex::create("([^\\{]+)\\{([^\\{]+)\\}");
383 //
384 // Glib::MatchInfo minfo;
385 // regex1->match(content, minfo);
386
387 // Split on curly brackets. Even tokens are selectors, odd are values.
388 std::vector<Glib::ustring> tokens = Glib::Regex::split_simple("[}{]", content.c_str()); // Must use c_str() as "content" is a std::string which cannot
389 // be converted directly to a Glib::UStringView in Gtk4.
390 _owner_style.clear();
391 // If text node is empty, return (avoids problem with negative below).
392
394
396 SPObject *obj = nullptr;
397 if (selection->objects().size() == 1) {
398 obj = selection->objects().back();
399 }
400 if (!obj) {
402 if (obj && !obj->getRepr()) {
403 obj = nullptr; // treat detached object as no selection
404 }
405 }
406
407 // Currently selected object's properties set via style element.
408 auto builder = create_builder("dialog-css.glade");
409 auto css_selector_container = &get_widget<Gtk::Box> (builder, "CSSSelectorContainer");
410 auto css_selector = &get_widget<Gtk::Label> (builder, "CSSSelector");
411 auto css_tree = &get_widget<Gtk::TreeView>(builder, "CSSTree");
412 auto css_button = &get_widget<Gtk::Button> (builder, "CSSSelectorAddButton");
413
414 css_selector->set_text("element");
415
416 css_tree->add_css_class("style_element");
417 Glib::RefPtr<Gtk::TreeStore> store = Gtk::TreeStore::create(_mColumns);
418 css_tree->set_model(store);
419 _addTreeViewHandlers(*css_tree); // TODO: GTK4: Just add one on self as weʼll get events there?
420
421 unsigned selectorpos = 0;
422 css_button->signal_clicked().connect(
423 sigc::bind(
424 sigc::mem_fun(*this, &StyleDialog::_addRow), store, css_tree, "style_properties", selectorpos));
425
426 auto const addRenderer = Gtk::make_managed<UI::Widget::IconRenderer>();
427 addRenderer->add_icon("edit-delete");
428 int addCol = css_tree->append_column(" ", *addRenderer) - 1;
429 Gtk::TreeViewColumn *col = css_tree->get_column(addCol);
430 if (col) {
431 addRenderer->signal_activated().connect(
432 sigc::bind(sigc::mem_fun(*this, &StyleDialog::_onPropDelete), store));
433 }
434
435 auto const label = Gtk::make_managed<Gtk::CellRendererText>();
436 label->property_placeholder_text() = _("property");
437 label->property_editable() = true;
438 label->signal_edited().connect(sigc::bind(
439 sigc::mem_fun(*this, &StyleDialog::_nameEdited), store, css_tree));
440 label->signal_editing_started().connect(sigc::mem_fun(*this, &StyleDialog::_startNameEdit));
441 addCol = css_tree->append_column(" ", *label) - 1;
442 col = css_tree->get_column(addCol);
443 if (col) {
444 col->set_resizable(true);
445 col->add_attribute(label->property_text(), _mColumns._colName);
446 }
447
448 auto const value = Gtk::make_managed<Gtk::CellRendererText>();
449 value->property_placeholder_text() = _("value");
450 value->property_editable() = true;
451 value->signal_edited().connect(
452 sigc::bind(sigc::mem_fun(*this, &StyleDialog::_valueEdited), store));
453 value->signal_editing_started().connect(
454 sigc::bind(sigc::mem_fun(*this, &StyleDialog::_startValueEdit), store));
455 addCol = css_tree->append_column(" ", *value) - 1;
456 col = css_tree->get_column(addCol);
457 if (col) {
458 col->add_attribute(value->property_text(), _mColumns._colValue);
459 col->set_expand(true);
460 col->add_attribute(value->property_strikethrough(), _mColumns._colStrike);
461 }
462
463 auto const urlRenderer = Gtk::make_managed<UI::Widget::IconRenderer>();
464 urlRenderer->add_icon("empty-icon");
465 urlRenderer->add_icon("edit-redo");
466
467 int urlCol = css_tree->append_column(" ", *urlRenderer) - 1;
468 Gtk::TreeViewColumn *urlcol = css_tree->get_column(urlCol);
469 if (urlcol) {
470 urlcol->set_min_width(40);
471 urlcol->set_max_width(40);
472 urlRenderer->signal_activated().connect(sigc::bind(sigc::mem_fun(*this, &StyleDialog::_onLinkObj), store));
473 urlcol->add_attribute(urlRenderer->property_icon(), _mColumns._colLinked);
474 }
475
476 AttrProp attr_prop;
477 Gtk::TreeModel::Path path;
478 bool empty = true;
479 if (obj && obj->getRepr()->attribute("style")) {
480 Glib::ustring style = obj->getRepr()->attribute("style");
481 attr_prop = parseStyle(std::move(style));
482
483 for (auto iter : obj->style->properties()) {
484 auto const &name = iter->name();
485 auto const found = attr_prop.find(name);
486 if (found == attr_prop.end()) continue;
487
488 auto const &value = found->second;
489 empty = false;
490 Gtk::TreeModel::Row row = *(store->prepend());
491 row[_mColumns._colSelector] = "style_properties";
492 row[_mColumns._colSelectorPos] = 0;
493 row[_mColumns._colActive] = true;
494 row[_mColumns._colName] = name;
495 row[_mColumns._colValue] = value;
496 row[_mColumns._colStrike] = false;
497 row[_mColumns._colOwner] = _("Current value");
498 row[_mColumns._colHref] = nullptr;
499 row[_mColumns._colLinked] = false;
500 if (is_url(value.c_str())) {
501 auto id = value.substr(5, value.size() - 6);
502 SPObject *elemref = nullptr;
503 if ((elemref = document->getObjectById(id.c_str()))) {
504 row[_mColumns._colHref] = elemref;
505 row[_mColumns._colLinked] = true;
506 }
507 }
508 _addOwnerStyle(name, _("Used in style attribute"));
509 }
510
511 // this is to fix a bug on cairo win:
512 // https://gitlab.freedesktop.org/cairo/cairo/issues/338
513 // TODO: check if inkscape min cairo version has applied the patch proposed and remove (3 times)
514 if (empty) {
515 css_tree->set_visible(false);
516 }
518 }
519
520 selectorpos++;
521
522 if (tokens.size() == 0) {
523 _updating = false;
524 return;
525 }
526
527 // Loop over selectors.
528 for (size_t i = 0; i < tokens.size() - 1; i += 2) {
529 auto selector = std::move(tokens[i]);
530 Util::trim(selector); // Remove leading/trailing spaces
531 // Get list of objects selector matches
532 std::vector<Glib::ustring> selectordata = Glib::Regex::split_simple(";", selector);
533 Glib::ustring selector_orig = selector;
534 if (!selectordata.empty()) {
535 selector = selectordata.back();
536 }
537 std::vector<SPObject *> objVec = _getObjVec(selector);
538 if (obj) {
539 bool stop = true;
540 for (auto objel : objVec) {
541 if (objel == obj) {
542 stop = false;
543 }
544 }
545 if (stop) {
546 _updating = false;
547 selectorpos++;
548 continue;
549 }
550 }
551 if (!obj && !_current_selector.empty() && _current_selector != selector) {
552 _updating = false;
553 selectorpos++;
554 continue;
555 }
556 if (!obj) {
557 bool present = false;
558 for (auto objv : objVec) {
559 for (auto objsel : selection->objects()) {
560 if (objv == objsel) {
561 present = true;
562 break;
563 }
564 }
565 if (present) {
566 break;
567 }
568 }
569 if (!present) {
570 _updating = false;
571 selectorpos++;
572 continue;
573 }
574 }
575
576 Glib::ustring properties;
577 // Check to make sure we do have a value to match selector.
578 if ((i + 1) < tokens.size()) {
579 properties = std::move(tokens[i + 1]);
580 } else {
581 std::cerr << "StyleDialog::readStyleElement: Missing values "
582 "for last selector!"
583 << std::endl;
584 }
585
586 // Create new builder each loop.
587 auto builder = create_builder("dialog-css.glade");
588 auto css_selector_container = &get_widget<Gtk::Box> (builder, "CSSSelectorContainer");
589 auto css_selector = &get_widget<Gtk::Label> (builder, "CSSSelector");
590 auto css_tree = &get_widget<Gtk::TreeView>(builder, "CSSTree");
591 auto css_button = &get_widget<Gtk::Button> (builder, "CSSSelectorAddButton");
592
593 css_selector->set_text(selector);
594
595 css_tree->add_css_class("style_sheet");
596 Glib::RefPtr<Gtk::TreeStore> store = Gtk::TreeStore::create(_mColumns);
597 css_tree->set_model(store);
598 _addTreeViewHandlers(*css_tree); // TODO: GTK4: Just add one on self as weʼll get events there?
599
600 css_button->signal_clicked().connect(
601 sigc::bind(
602 sigc::mem_fun(*this, &StyleDialog::_addRow), store, css_tree, selector_orig, selectorpos));
603
604 auto const addRenderer = Gtk::make_managed<UI::Widget::IconRenderer>();
605 addRenderer->add_icon("edit-delete");
606 int addCol = css_tree->append_column(" ", *addRenderer) - 1;
607 Gtk::TreeViewColumn *col = css_tree->get_column(addCol);
608 if (col) {
609 addRenderer->signal_activated().connect(
610 sigc::bind(sigc::mem_fun(*this, &StyleDialog::_onPropDelete), store));
611 }
612
613 auto const isactive = Gtk::make_managed<Gtk::CellRendererToggle>();
614 isactive->property_activatable() = true;
615 addCol = css_tree->append_column(" ", *isactive) - 1;
616 col = css_tree->get_column(addCol);
617 if (col) {
618 col->add_attribute(isactive->property_active(), _mColumns._colActive);
619 isactive->signal_toggled().connect(
620 sigc::bind(sigc::mem_fun(*this, &StyleDialog::_activeToggled), store));
621 }
622
623 auto const label = Gtk::make_managed<Gtk::CellRendererText>();
624 label->property_placeholder_text() = _("property");
625 label->property_editable() = true;
626 label->signal_edited().connect(sigc::bind(
627 sigc::mem_fun(*this, &StyleDialog::_nameEdited), store, css_tree));
628 label->signal_editing_started().connect(sigc::mem_fun(*this, &StyleDialog::_startNameEdit));
629 addCol = css_tree->append_column(" ", *label) - 1;
630 col = css_tree->get_column(addCol);
631 if (col) {
632 col->set_resizable(true);
633 col->add_attribute(label->property_text(), _mColumns._colName);
634 }
635
636 auto const value = Gtk::make_managed<Gtk::CellRendererText>();
637 value->property_editable() = true;
638 value->property_placeholder_text() = _("value");
639 value->signal_edited().connect(
640 sigc::bind(sigc::mem_fun(*this, &StyleDialog::_valueEdited), store));
641 value->signal_editing_started().connect(
642 sigc::bind(sigc::mem_fun(*this, &StyleDialog::_startValueEdit), store));
643 addCol = css_tree->append_column(" ", *value) - 1;
644 col = css_tree->get_column(addCol);
645 if (col) {
646 col->add_attribute(value->property_text(), _mColumns._colValue);
647 col->add_attribute(value->property_strikethrough(), _mColumns._colStrike);
648 }
649
650 Glib::ustring comments;
651 for (size_t beg = 0, end = 0;
652 (beg = properties.find("/*", beg )) != properties.npos &&
653 (end = properties.find("*/", beg + 2)) != properties.npos;)
654 {
655 comments.append(properties, beg + 2, end - beg - 2);
656 properties.erase(beg, end - beg + 2);
657 }
658
659 std::map<Glib::ustring, std::pair<Glib::ustring, bool>> result_props;
660 auto const move_to_result = [&](AttrProp &&src_props, bool const active)
661 {
662 while (!src_props.empty()) {
663 auto &&node = src_props.extract(src_props.begin());
664 result_props[std::move(node.key())] = {std::move(node.mapped()), active};
665 }
666 };
667 move_to_result(parseStyle(std::move(properties)), true );
668 move_to_result(parseStyle(std::move(comments )), false);
669 empty = result_props.empty();
670
671
672 for (auto const &[name, pair] : result_props) {
673 auto const &[value, active] = pair;
674
675 Gtk::TreeModel::iterator iterstore = obj ? store->append() : store->prepend();
676 Gtk::TreeModel::Row row = *(iterstore);
677 row[_mColumns._colSelector] = selector_orig;
678 row[_mColumns._colSelectorPos] = selectorpos;
679 row[_mColumns._colActive] = active;
680 row[_mColumns._colName] = name;
681 row[_mColumns._colValue] = value;
682
683 if (!obj) {
684 row[_mColumns._colOwner] = _("Stylesheet value");
685 continue;
686 }
687
688 if (!active) {
689 row[_mColumns._colStrike] = true;
690 row[_mColumns._colOwner] = _("This value is commented out.");
691 continue;
692 }
693
694 auto val = Glib::ustring{};
695 for (auto iterprop : obj->style->properties()) {
696 if (iterprop->style_src != SPStyleSrc::UNSET && iterprop->name() == name) {
697 val = iterprop->get_value();
698 break;
699 }
700 }
701
702 auto r1 = Colors::Color::parse(value);
703 auto r2 = Colors::Color::parse(val);
704 if (((!r1 || *r1 != *r2) && value != val) || attr_prop.count(name)) {
705 row[_mColumns._colStrike] = true;
706 } else {
707 row[_mColumns._colOwner] = _("Current value");
708 // TRANSLATORS: %1 is a CSS selector.
709 _addOwnerStyle(name, Glib::ustring::compose(_("Used in %1"), selector));
710 }
711 }
712
713 if (empty) {
714 css_tree->set_visible(false);
715 }
716
718
719 selectorpos++;
720 }
721
722 // Currently selected object's properties set via attributes.
723 // Create new builder and get a new set of widgets.
724 builder = create_builder("dialog-css.glade");
725 css_selector_container = &get_widget<Gtk::Box> (builder, "CSSSelectorContainer");
726 css_selector = &get_widget<Gtk::Label> (builder, "CSSSelector");
727 css_tree = &get_widget<Gtk::TreeView>(builder, "CSSTree");
728 css_button = &get_widget<Gtk::Button> (builder, "CSSSelectorAddButton");
729
730 css_selector->set_text("element.attributes");
731
732 css_tree->add_css_class("style_attribute");
733 store = Gtk::TreeStore::create(_mColumns);
734 css_tree->set_model(store);
735 _addTreeViewHandlers(*css_tree); // TODO: GTK4: Just add one on self as weʼll get events there?
736
737 css_button->signal_clicked().connect(
738 sigc::bind(
739 sigc::mem_fun(*this, &StyleDialog::_addRow), store, css_tree, "attributes", selectorpos));
740
741 bool hasattributes = false;
742 empty = true;
743 if (obj) {
744 for (auto iter : obj->style->properties()) {
745 if (iter->style_src != SPStyleSrc::UNSET) {
746 auto key = iter->id();
747 if (key != SPAttr::FONT && key != SPAttr::D && key != SPAttr::MARKER) {
748 const gchar *attr = obj->getRepr()->attribute(iter->name().c_str());
749 if (attr) {
750 if (!hasattributes) {
751 auto const addRenderer = Gtk::make_managed<UI::Widget::IconRenderer>();
752 addRenderer->add_icon("edit-delete");
753 int addCol = css_tree->append_column(" ", *addRenderer) - 1;
754 Gtk::TreeViewColumn *col = css_tree->get_column(addCol);
755 if (col) {
756 addRenderer->signal_activated().connect(sigc::bind(
757 sigc::mem_fun(*this, &StyleDialog::_onPropDelete), store));
758 }
759
760 auto const label = Gtk::make_managed<Gtk::CellRendererText>();
761 label->property_placeholder_text() = _("property");
762 label->property_editable() = true;
763 label->signal_edited().connect(sigc::bind(
764 sigc::mem_fun(*this, &StyleDialog::_nameEdited), store, css_tree));
765 label->signal_editing_started().connect(sigc::mem_fun(*this, &StyleDialog::_startNameEdit));
766 addCol = css_tree->append_column(" ", *label) - 1;
767 col = css_tree->get_column(addCol);
768 if (col) {
769 col->set_resizable(true);
770 col->add_attribute(label->property_text(), _mColumns._colName);
771 }
772
773 auto const value = Gtk::make_managed<Gtk::CellRendererText>();
774 value->property_placeholder_text() = _("value");
775 value->property_editable() = true;
776 value->signal_edited().connect(sigc::bind(
777 sigc::mem_fun(*this, &StyleDialog::_valueEdited), store));
778 value->signal_editing_started().connect(sigc::bind(
779 sigc::mem_fun(*this, &StyleDialog::_startValueEdit), store));
780 addCol = css_tree->append_column(" ", *value) - 1;
781 col = css_tree->get_column(addCol);
782 if (col) {
783 col->add_attribute(value->property_text(), _mColumns._colValue);
784 col->add_attribute(value->property_strikethrough(), _mColumns._colStrike);
785 }
786 }
787
788 empty = false;
789 Gtk::TreeModel::iterator iterstore = store->prepend();
790 Gtk::TreeModel::Path path = (Gtk::TreeModel::Path)iterstore;
791 Gtk::TreeModel::Row row = *(iterstore);
792 row[_mColumns._colSelector] = "attributes";
793 row[_mColumns._colSelectorPos] = selectorpos;
794 row[_mColumns._colActive] = true;
795 row[_mColumns._colName] = iter->name();
796 row[_mColumns._colValue] = attr;
797 if (_owner_style.find(iter->name()) != _owner_style.end()) {
798 row[_mColumns._colStrike] = true;
799 row[_mColumns._colOwner] = Glib::ustring{};
800 } else {
801 row[_mColumns._colStrike] = false;
802 row[_mColumns._colOwner] = _("Current value");
803 _addOwnerStyle(iter->name(), _("Used in inline attributes"));
804 }
805 hasattributes = true;
806 }
807 }
808 }
809 }
810
811 if (empty) {
812 css_tree->set_visible(false);
813 }
814
815 if (!hasattributes) {
816 UI::remove_all_children(*css_selector_container);
817 }
818
820 }
821
822 for (auto const selector : UI::get_children(_styleBox)) {
823 if (auto const box = dynamic_cast<Gtk::Box *>(selector)) {
824 auto const childs = UI::get_children(*box);
825 if (childs.size() > 1) {
826 if (auto const css_tree = dynamic_cast<Gtk::TreeView *>(childs[1])) {
827 if (auto const &model = css_tree->get_model()) {
828 model->foreach_iter(sigc::mem_fun(*this, &StyleDialog::_on_foreach_iter));
829 }
830 }
831 }
832 }
833 }
834
835 if (obj) {
836 obj->style->readFromObject(obj);
837 obj->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG);
838 }
839
840 _updating = false;
841}
842
843bool StyleDialog::_on_foreach_iter(const Gtk::TreeModel::iterator &iter)
844{
845 g_debug("StyleDialog::_on_foreach_iter");
846
847 Gtk::TreeModel::Row row = *(iter);
848 auto const &owner = row.get_value(_mColumns._colOwner);
849 if (!owner.empty()) return false;
850
851 auto const it = _owner_style.find(row.get_value(_mColumns._colName));
852 if (it != _owner_style.end()) {
853 row[_mColumns._colOwner] = it->second;
854 row[_mColumns._colStrike] = true;
855 } else {
856 row[_mColumns._colOwner] = _("Current value");
857 row[_mColumns._colStrike] = false;
858 }
859 return false;
860}
861
862void StyleDialog::_onLinkObj(Glib::ustring const &path, Glib::RefPtr<Gtk::TreeStore> const& store)
863{
864 g_debug("StyleDialog::_onLinkObj");
865
866 Gtk::TreeModel::Row row = *store->get_iter(path);
867 if (row && row[_mColumns._colLinked]) {
868 SPObject *linked = row[_mColumns._colHref];
869 if (linked) {
870 auto selection = getSelection();
872 selection->clear();
873 selection->set(linked);
874 }
875 }
876}
877
884void StyleDialog::_onPropDelete(Glib::ustring const &path, Glib::RefPtr<Gtk::TreeStore> const &store)
885{
886 g_debug("StyleDialog::_onPropDelete");
887 auto iter = store->get_iter(path);
888 auto row = *iter;
889 if (row) {
890 Glib::ustring selector = row[_mColumns._colSelector];
891 row[_mColumns._colName] = Glib::ustring{};
893 store->erase(iter);
894 _deletion = true;
895 _writeStyleElement(store, selector);
896 _deletion = false;
897 }
898}
899
900void StyleDialog::_addOwnerStyle(Glib::ustring name, Glib::ustring selector)
901{
902 g_debug("StyleDialog::_addOwnerStyle");
903
904 if (_owner_style.find(name) == _owner_style.end()) {
905 _owner_style.emplace(std::move(name), std::move(selector));
906 }
907}
908
909
917{
918 g_debug("StyleDialog::parseStyle");
919
920 Util::trim(style_string); // We'd use const, but we need to trip spaces
921
922 AttrProp ret;
923
924 static auto const r_props = Glib::Regex::create("\\s*;\\s*");
925 std::vector<Glib::ustring> props = r_props->split(style_string);
926
927 for (auto &&token : props) {
928 Util::trim(token);
929 if (token.empty())
930 break;
931
932 static auto const r_pair = Glib::Regex::create("\\s*:\\s*");
933 std::vector<Glib::ustring> pair = r_pair->split(token);
934
935 if (pair.size() > 1) {
936 ret[std::move(pair[0])] = std::move(pair[1]);
937 }
938 }
939 return ret;
940}
941
942
946void StyleDialog::_writeStyleElement(Glib::RefPtr<Gtk::TreeStore> const &store,
947 Glib::ustring selector, Glib::ustring const &new_selector)
948{
949 g_debug("StyleDialog::_writeStyleElemen");
950 auto selection = getSelection();
951 if (_updating && selection)
952 return;
953 _scrollock = true;
954 SPObject *obj = nullptr;
955 if (selection->objects().size() == 1) {
956 obj = selection->objects().back();
957 }
958 if (!obj) {
960 }
961 if (selection->objects().size() < 2 && !obj) {
963 return;
964 }
965 _updating = true;
966 int selectorpos = 0;
967 std::string styleContent;
968 if (selector != "style_properties" && selector != "attributes") {
969 if (!new_selector.empty()) {
970 selector = new_selector;
971 }
972 std::vector<Glib::ustring> selectordata = Glib::Regex::split_simple(";", selector);
973 for (auto selectoritem : selectordata) {
974 if (selectordata[selectordata.size() - 1] == selectoritem) {
975 selector = selectoritem;
976 } else {
977 styleContent = styleContent + selectoritem + ";\n";
978 }
979 }
980 styleContent.append("\n").append(selector.raw()).append(" { \n");
981 }
982 selectorpos = _deleted_pos;
983 for (auto &row : store->children()) {
984 selector = row[_mColumns._colSelector];
985 selectorpos = row[_mColumns._colSelectorPos];
986 const char *opencomment = "";
987 const char *closecomment = "";
988 if (selector != "style_properties" && selector != "attributes") {
989 opencomment = row[_mColumns._colActive] ? " " : " /*";
990 closecomment = row[_mColumns._colActive] ? "\n" : "*/\n";
991 }
992 Glib::ustring const &name = row[_mColumns._colName];
993 Glib::ustring const &value = row[_mColumns._colValue];
994 if (!(name.empty() && value.empty())) {
995 styleContent = styleContent + opencomment + name.raw() + ":" + value.raw() + ";" + closecomment;
996 }
997 }
998 if (selector != "style_properties" && selector != "attributes") {
999 styleContent = styleContent + "}";
1000 }
1001 if (selector == "style_properties") {
1002 _updating = true;
1003 obj->getRepr()->setAttribute("style", styleContent);
1004 _updating = false;
1005 } else if (selector == "attributes") {
1006 for (auto iter : obj->style->properties()) {
1007 auto key = iter->id();
1008 if (key != SPAttr::FONT && key != SPAttr::D && key != SPAttr::MARKER) {
1009 const gchar *attr = obj->getRepr()->attribute(iter->name().c_str());
1010 if (attr) {
1011 _updating = true;
1012 obj->getRepr()->removeAttribute(iter->name());
1013 _updating = false;
1014 }
1015 }
1016 }
1017 for (auto &row : store->children()) {
1018 Glib::ustring const &name = row[_mColumns._colName];
1019 Glib::ustring const &value = row[_mColumns._colValue];
1020 if (!(name.empty() && value.empty())) {
1021 _updating = true;
1022 obj->getRepr()->setAttribute(name, value);
1023 _updating = false;
1024 }
1025 }
1026 } else if (!selector.empty()) { // styleshet
1027 // We could test if styleContent is empty and then delete the style node here but there is no
1028 // harm in keeping it around ...
1029
1030 std::string pos = std::to_string(selectorpos);
1031 std::string selectormatch = "(";
1032 for (; selectorpos > 1; selectorpos--) {
1033 selectormatch = selectormatch + "[^\\}]*?\\}";
1034 }
1035 selectormatch = selectormatch + ")([^\\}]*?\\})((.|\n)*)";
1036
1037 Inkscape::XML::Node *textNode = _getStyleTextNode(true);
1038 std::regex e(selectormatch.c_str());
1039 std::string content = (textNode->content() ? textNode->content() : "");
1040 std::string result;
1041 std::regex_replace(std::back_inserter(result), content.begin(), content.end(), e, "$1" + styleContent + "$3");
1042 bool empty = false;
1043 if (result.empty()) {
1044 empty = true;
1045 result = "* > .inkscapehacktmp{}";
1046 }
1047 textNode->setContent(result.c_str());
1048 if (empty) {
1049 textNode->setContent("");
1050 }
1051 }
1052 _updating = false;
1054 for (auto iter : getDocument()->getObjectsBySelector(selector)) {
1055 iter->style->readFromObject(iter);
1056 iter->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG);
1057 }
1058 DocumentUndo::done(SP_ACTIVE_DOCUMENT, _("Edited style element."), "");
1059
1060 g_debug("StyleDialog::_writeStyleElement(): | %s |", styleContent.c_str());
1061}
1062
1063void StyleDialog::_addRow(Glib::RefPtr<Gtk::TreeStore> const &store, Gtk::TreeView *const css_tree,
1064 Glib::ustring const &selector, int const pos)
1065{
1066 g_debug("StyleDialog::_addRow");
1067
1068 Gtk::TreeModel::iterator iter = store->prepend();
1069 Gtk::TreeModel::Path path = (Gtk::TreeModel::Path)iter;
1070 Gtk::TreeModel::Row row = *(iter);
1071 row[_mColumns._colSelector] = selector;
1072 row[_mColumns._colSelectorPos] = pos;
1073 row[_mColumns._colActive] = true;
1074
1075 auto const col = pos < 1 ? 1 : 2;
1076 css_tree->set_visible(true);
1077 css_tree->set_cursor(path, *(css_tree->get_column(col)), true);
1078 grab_focus();
1079}
1080
1081void StyleDialog::_setAutocompletion(Gtk::Entry *entry, SPStyleEnum const cssenum[])
1082{
1083 g_debug("StyleDialog::_setAutocompletion");
1084
1085 Glib::RefPtr<Gtk::ListStore> completionModel = Gtk::ListStore::create(_mCSSData);
1086 Glib::RefPtr<Gtk::EntryCompletion> entry_completion = Gtk::EntryCompletion::create();
1087 entry_completion->set_model(completionModel);
1088 entry_completion->set_text_column (_mCSSData._colCSSData);
1089 entry_completion->set_minimum_key_length(0);
1090 entry_completion->set_popup_completion(true);
1091 int counter = 0;
1092 const char * key = cssenum[counter].key;
1093 while (key) {
1094 Gtk::TreeModel::Row row = *(completionModel->prepend());
1095 row[_mCSSData._colCSSData] = Glib::ustring(key);
1096 counter++;
1097 key = cssenum[counter].key;
1098 }
1099 entry->set_completion(entry_completion);
1100}
1101
1102/*Hardcode values non in enum*/
1103void StyleDialog::_setAutocompletion(Gtk::Entry *entry, Glib::ustring name)
1104{
1105 g_debug("StyleDialog::_setAutocompletion");
1106
1107 Glib::RefPtr<Gtk::ListStore> completionModel = Gtk::ListStore::create(_mCSSData);
1108 Glib::RefPtr<Gtk::EntryCompletion> entry_completion = Gtk::EntryCompletion::create();
1109 entry_completion->set_model(completionModel);
1110 entry_completion->set_text_column(_mCSSData._colCSSData);
1111 entry_completion->set_minimum_key_length(0);
1112 entry_completion->set_popup_completion(true);
1113 if (name == "paint-order") {
1114 Gtk::TreeModel::Row row = *(completionModel->append());
1115 row[_mCSSData._colCSSData] = Glib::ustring("fill markers stroke");
1116 row = *(completionModel->append());
1117 row[_mCSSData._colCSSData] = Glib::ustring("fill stroke markers");
1118 row = *(completionModel->append());
1119 row[_mCSSData._colCSSData] = Glib::ustring("stroke markers fill");
1120 row = *(completionModel->append());
1121 row[_mCSSData._colCSSData] = Glib::ustring("stroke fill markers");
1122 row = *(completionModel->append());
1123 row[_mCSSData._colCSSData] = Glib::ustring("markers fill stroke");
1124 row = *(completionModel->append());
1125 row[_mCSSData._colCSSData] = Glib::ustring("markers stroke fill");
1126 }
1127 entry->set_completion(entry_completion);
1128}
1129
1130void
1131StyleDialog::_startValueEdit(Gtk::CellEditable* cell, const Glib::ustring& path, Glib::RefPtr<Gtk::TreeStore> store)
1132{
1133 g_debug("StyleDialog::_startValueEdit");
1134
1135 _scrollock = true;
1136
1137 Gtk::TreeModel::Row row = *store->get_iter(path);
1138 if (row) {
1139 Gtk::Entry *entry = dynamic_cast<Gtk::Entry *>(cell);
1140
1141 Glib::ustring name = row[_mColumns._colName];
1142 if (name == "paint-order") {
1143 _setAutocompletion(entry, name);
1144 } else if (name == "fill-rule") {
1146 } else if (name == "stroke-linecap") {
1148 } else if (name == "stroke-linejoin") {
1150 } else if (name == "font-style") {
1152 } else if (name == "font-variant") {
1154 } else if (name == "font-weight") {
1156 } else if (name == "font-stretch") {
1158 } else if (name == "font-variant-position") {
1160 } else if (name == "text-align") {
1162 } else if (name == "text-transform") {
1164 } else if (name == "text-anchor") {
1166 } else if (name == "white-space") {
1168 } else if (name == "direction") {
1170 } else if (name == "baseline-shift") {
1172 } else if (name == "visibility") {
1174 } else if (name == "overflow") {
1176 } else if (name == "display") {
1178 } else if (name == "shape-rendering") {
1180 } else if (name == "color-rendering") {
1182 } else if (name == "clip-rule") {
1184 } else if (name == "color-interpolation") {
1186 }
1187
1188 _setEditingEntry(entry, ";");
1189 }
1190}
1191
1192void StyleDialog::_startNameEdit(Gtk::CellEditable *cell, const Glib::ustring &path)
1193{
1194 g_debug("StyleDialog::_startNameEdit");
1195
1196 _scrollock = true;
1197
1198 Glib::RefPtr<Gtk::ListStore> completionModel = Gtk::ListStore::create(_mCSSData);
1199 Glib::RefPtr<Gtk::EntryCompletion> entry_completion = Gtk::EntryCompletion::create();
1200 entry_completion->set_model(completionModel);
1201 entry_completion->set_text_column(_mCSSData._colCSSData);
1202 entry_completion->set_minimum_key_length(1);
1203 entry_completion->set_popup_completion(true);
1204
1205 for (auto prop : sp_attribute_name_list(true)) {
1206 Gtk::TreeModel::Row row = *(completionModel->append());
1207 row[_mCSSData._colCSSData] = prop;
1208 }
1209
1210 Gtk::Entry *entry = dynamic_cast<Gtk::Entry *>(cell);
1211 entry->set_completion(entry_completion);
1212 _setEditingEntry(entry, ":=");
1213}
1214
1216{
1217 StyleDialog *styledialog = reinterpret_cast<StyleDialog *>(data);
1218 auto selection = styledialog->_current_css_tree->get_selection();
1219 Gtk::TreeModel::iterator iter = selection->get_selected();
1220 if (!iter) {
1221 return false;
1222 }
1223 Gtk::TreeModel::Path model = (Gtk::TreeModel::Path)iter;
1224 if (model == styledialog->_current_path) {
1225 styledialog->_current_css_tree->set_cursor(styledialog->_current_path, *styledialog->_current_value_col,
1226 true);
1227 }
1228 return false;
1229}
1230
1236void StyleDialog::_nameEdited(const Glib::ustring &path, const Glib::ustring &name, Glib::RefPtr<Gtk::TreeStore> store,
1237 Gtk::TreeView *css_tree)
1238{
1239 g_debug("StyleDialog::_nameEdited");
1240
1241 _scrollock = true;
1242
1243 auto iter = store->get_iter(path);
1244 auto row = *iter;
1245 _current_path = store->get_path(iter);
1246
1247 if (!row) return;
1248
1249 _current_css_tree = css_tree;
1250
1251 Glib::ustring finalname = name;
1252 auto i = finalname.find_first_of(";:=");
1253 if (i != std::string::npos) {
1254 finalname.erase(i, name.size() - i);
1255 }
1256
1257 auto const pos = row.get_value(_mColumns._colSelectorPos);
1258 auto const write = row.get_value(_mColumns._colName ) != finalname &&
1259 !row.get_value(_mColumns._colValue).empty();
1260
1261 Glib::ustring selector = row[_mColumns._colSelector];
1262 Glib::ustring value = row[_mColumns._colValue];
1263 bool is_attr = selector == "attributes";
1264
1265 Glib::ustring old_name = row[_mColumns._colName];
1266 row[_mColumns._colName] = finalname;
1267
1268 if (finalname.empty() && value.empty()) {
1270 store->erase(iter);
1271 }
1272
1273 auto const col = pos < 1 || is_attr ? 2 : 3;
1274 _current_value_col = css_tree->get_column(col);
1275
1276 if (write && old_name != name) {
1277 _writeStyleElement(store, selector);
1278 } else {
1279 g_timeout_add(50, &sp_styledialog_store_move_to_next, this);
1280 grab_focus();
1281 }
1282}
1283
1290void StyleDialog::_valueEdited(const Glib::ustring &path, const Glib::ustring &value,
1291 Glib::RefPtr<Gtk::TreeStore> store)
1292{
1293 g_debug("StyleDialog::_valueEdited");
1294
1295 _scrollock = true;
1296
1297 auto iter = store->get_iter(path);
1298 auto row = *iter;
1299 if (row) {
1300 Glib::ustring finalvalue = value;
1301 auto i = std::min(finalvalue.find(";"), finalvalue.find(":"));
1302 if (i != std::string::npos) {
1303 finalvalue.erase(i, finalvalue.size() - i);
1304 }
1305 Glib::ustring old_value = row[_mColumns._colValue];
1306 if (old_value == finalvalue) {
1307 return;
1308 }
1309 row[_mColumns._colValue] = finalvalue;
1310 Glib::ustring selector = row[_mColumns._colSelector];
1311 Glib::ustring name = row[_mColumns._colName];
1312 if (name.empty() && finalvalue.empty()) {
1314 store->erase(iter);
1315 }
1316 _writeStyleElement(store, selector);
1317 if (selector != "style_properties" && selector != "attributes") {
1318 std::vector<SPObject *> objs = _getObjVec(selector);
1319 for (auto obj : objs) {
1320 auto css_str = Glib::ustring{};
1322 sp_repr_css_attr_add_from_string(css, obj->getRepr()->attribute("style"));
1323 css->removeAttribute(name);
1324 sp_repr_css_write_string(css, css_str);
1325 obj->getRepr()->setAttribute("style", css_str);
1326 obj->style->readFromObject(obj);
1327 obj->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG);
1328 }
1329 }
1330 }
1331}
1332
1333void StyleDialog::_activeToggled(const Glib::ustring &path, Glib::RefPtr<Gtk::TreeStore> const &store)
1334{
1335 g_debug("StyleDialog::_activeToggled");
1336
1337 _scrollock = true;
1338 Gtk::TreeModel::Row row = *store->get_iter(path);
1339 if (row) {
1341 Glib::ustring selector = row[_mColumns._colSelector];
1342 _writeStyleElement(store, selector);
1343 }
1344}
1345
1346void StyleDialog::_addTreeViewHandlers(Gtk::TreeView &treeview)
1347{
1348 // This seems needed to get Tab to work as it usually does OotB, in e.g. AttrDialog. Unsure why
1349 auto const key = Gtk::EventControllerKey::create();
1350 key->signal_key_released().connect(sigc::mem_fun(*this, &StyleDialog::_onTreeViewKeyReleased));
1351 treeview.add_controller(key);
1352
1353 // and since the above somehow doesnʼt fire on focus-out of final cell, we have to do this too…
1354 auto focus = Gtk::EventControllerFocus::create();
1355 focus->set_propagation_phase(Gtk::PropagationPhase::BUBBLE);
1356 treeview.add_controller(focus);
1357 focus->signal_leave().connect([this] { _onTreeViewFocusLeave(); });
1358
1359 // If TreeView can-focus, above arenʼt needed, BUT it causes CRITICALs… so just be Good Enough™
1360 // Doing that also means we need 2 presses of Tab, the 1st to dismiss the completion: not ideal
1361}
1362
1363void StyleDialog::_setEditingEntry(Gtk::Entry * const entry, Glib::ustring endChars)
1364{
1365 g_debug("StyleDialog::_setEditingEntry: _editingEntry = %p", static_cast<void *>(entry));
1366
1367 _editingEntry = entry;
1368
1369 if (entry == nullptr) return;
1370
1371 // Using entry, not _editingEntry, avoids lifetime confusion/crashes via signal emission order.
1372 entry->property_text().signal_changed().connect( [entry, endChars = std::move(endChars)]
1373 {
1374 g_debug("StyleDialog::_setEditingEntry: Entry:text changed");
1375
1376 auto text = entry->get_text();
1377 if (text.empty()) return;
1378 auto const endChar = text[text.size() - 1];
1379 if (endChars.find(endChar) == endChars.npos) return;
1380
1381 text.resize(text.size() - 1);
1382 entry->set_text(text);
1383 entry->editing_done();
1384 });
1385
1386 entry->signal_editing_done().connect([this]{ _setEditingEntry(nullptr, {}); });
1387}
1388
1389void StyleDialog::_onTreeViewKeyReleased(unsigned keyval, unsigned /*keycode*/, Gdk::ModifierType /*state*/)
1390{
1391 g_debug("StyleDialog::_onTreeViewKeyReleased");
1392
1393 if (_editingEntry != nullptr && (keyval == GDK_KEY_Tab or keyval == GDK_KEY_KP_Tab))
1394 {
1395 g_debug("StyleDialog::_onTreeViewKeyReleased: _editingEntry != nullptr && Tab");
1396
1397 _editingEntry->editing_done();
1398 }
1399}
1400
1402{
1403 g_debug("StyleDialog::_onTreeViewFocusLeave");
1404
1405 if (_editingEntry) {
1406 g_debug("StyleDialog::_onTreeViewFocus: _editingEntry != nullptr");
1407
1408 // If !change, entry stays visible after Tab, but remove_widget() crashes so… Donʼt Do That
1409 _editingEntry->editing_done();
1410 }
1411}
1412
1418std::vector<SPObject *> StyleDialog::_getObjVec(Glib::ustring selector)
1419{
1420 g_debug("StyleDialog::_getObjVec");
1421
1422 g_assert(selector.find(";") == Glib::ustring::npos);
1423
1424 return getDocument()->getObjectsBySelector(selector);
1425}
1426
1427void StyleDialog::_closeDialog(Gtk::Dialog *textDialogPtr) { textDialogPtr->response(Gtk::ResponseType::OK); }
1428
1429
1431{
1432 if (_textNode) {
1434 _textNode = nullptr;
1435 }
1436 if (m_root) {
1438 m_root = nullptr;
1439 }
1440}
1441
1455
1456/*
1457 * Handle a change in which objects are selected in a document.
1458 */
1460{
1461 _scrollpos = 0;
1462 _vadj->set_value(0);
1463 // Sometimes the selection changes because inkscape is closing.
1464 if (getDesktop()) {
1466 }
1467}
1468
1469} // namespace Inkscape::UI::Dialog
1470
1471/*
1472 Local Variables:
1473 mode:c++
1474 c-file-style:"stroustrup"
1475 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1476 indent-tabs-mode:nil
1477 fill-column:99
1478 End:
1479*/
1480// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
TODO: insert short description here.
TODO: insert short description here.
std::vector< Glib::ustring > sp_attribute_name_list(bool css_only)
Get sorted attribute name list.
static SPStyleProp const props[]
Lookup dictionary for attributes/properties.
Lookup dictionary for attributes/properties.
Gtk builder utilities.
Fragment store
Definition canvas.cpp:155
static void done(SPDocument *document, Glib::ustring const &event_description, Glib::ustring const &undo_icon, unsigned int object_modified_tag=0)
void clear()
Unselects all selected objects.
SPObjectRange objects()
Returns the list of selected objects.
The set of selected SPObjects for a given document and layer model.
Definition selection.h:80
void set(XML::Node *repr)
Set the selection to an XML node's SPObject.
Definition selection.h:118
DialogBase is the base class for the dialog system.
Definition dialog-base.h:42
Selection * getSelection() const
Definition dialog-base.h:84
SPDocument * getDocument() const
Definition dialog-base.h:83
SPDesktop * getDesktop() const
Definition dialog-base.h:79
Gtk::TreeModelColumn< Glib::ustring > _colCSSData
Gtk::TreeModelColumn< SPObject * > _colHref
Gtk::TreeModelColumn< Glib::ustring > _colOwner
Gtk::TreeModelColumn< int > _colSelectorPos
Gtk::TreeModelColumn< Glib::ustring > _colSelector
Gtk::TreeModelColumn< Glib::ustring > _colValue
Gtk::TreeModelColumn< Glib::ustring > _colName
The StyleDialog class A list of CSS selectors will show up in this dialog.
Definition styledialog.h:73
Inkscape::XML::Node * _textNode
Gtk::TreeModel::Path _current_path
Definition styledialog.h:84
Inkscape::XML::Node * _getStyleTextNode(bool create_if_missing=false)
void _startValueEdit(Gtk::CellEditable *cell, const Glib::ustring &path, Glib::RefPtr< Gtk::TreeStore > store)
void _startNameEdit(Gtk::CellEditable *cell, const Glib::ustring &path)
void _setEditingEntry(Gtk::Entry *entry, Glib::ustring endChars)
std::unique_ptr< XML::NodeObserver > const m_nodewatcher
void _onTreeViewKeyReleased(unsigned keyval, unsigned keycode, Gdk::ModifierType state)
void documentReplaced() final
Handle document replaced.
void _writeStyleElement(Glib::RefPtr< Gtk::TreeStore > const &store, Glib::ustring selector, Glib::ustring const &new_selector={})
Update the content of the style element as selectors (or objects) are added/removed.
void readStyleElement()
Fill the Gtk::TreeStore from the svg:style element.
void _activeToggled(const Glib::ustring &path, Glib::RefPtr< Gtk::TreeStore > const &store)
StyleDialog()
Constructor A treeview and a set of two buttons are added to the dialog.
void _nameEdited(const Glib::ustring &path, const Glib::ustring &name, Glib::RefPtr< Gtk::TreeStore > store, Gtk::TreeView *css_tree)
StyleDialog::nameEdited.
void _setAutocompletion(Gtk::Entry *entry, SPStyleEnum const cssenum[])
void _nodeChanged(Inkscape::XML::Node &repr)
AttrProp parseStyle(Glib::ustring style_string)
StyleDialog::parseStyle.
Inkscape::XML::Node * m_root
Glib::RefPtr< Gtk::Adjustment > _vadj
void _onPropDelete(Glib::ustring const &path, Glib::RefPtr< Gtk::TreeStore > const &store)
StyleDialog::_onPropDelete.
void _closeDialog(Gtk::Dialog *textDialogPtr)
void setCurrentSelector(Glib::ustring current_selector)
void selectionChanged(Selection *selection) final
std::map< Glib::ustring, Glib::ustring > AttrProp
Definition styledialog.h:89
std::vector< SPObject * > _getObjVec(Glib::ustring selector)
void _valueEdited(const Glib::ustring &path, const Glib::ustring &value, Glib::RefPtr< Gtk::TreeStore > store)
StyleDialog::valueEdited.
Gtk::TreeViewColumn * _current_value_col
Definition styledialog.h:83
std::unique_ptr< XML::NodeObserver > const m_styletextwatcher
Gtk::ScrolledWindow _scrolledWindow
void _nodeAdded(Inkscape::XML::Node &repr)
void _onLinkObj(Glib::ustring const &path, Glib::RefPtr< Gtk::TreeStore > const &store)
bool _on_foreach_iter(const Gtk::TreeModel::iterator &iter)
void _nodeRemoved(Inkscape::XML::Node &repr)
void _addRow(Glib::RefPtr< Gtk::TreeStore > const &store, Gtk::TreeView *css_tree, Glib::ustring const &selector, int pos)
void _addTreeViewHandlers(Gtk::TreeView &treeview)
void _addOwnerStyle(Glib::ustring name, Glib::ustring selector)
Interface for XML node observers.
Interface for refcounted XML nodes.
Definition node.h:80
virtual Node * next()=0
Get the next sibling of this node.
virtual void addSubtreeObserver(NodeObserver &observer)=0
Add an object that will be notified of the changes to this node and its descendants.
virtual void addChild(Node *child, Node *after)=0
Insert another node as a child of this node.
virtual int code() const =0
Get the integer code corresponding to the node's name.
virtual void appendChild(Node *child)=0
Append a node as the last child of this node.
void setAttribute(Util::const_char_ptr key, Util::const_char_ptr value)
Change an attribute of this node.
Definition node.cpp:25
virtual void removeSubtreeObserver(NodeObserver &observer)=0
Remove an object from the subtree observers list.
virtual Node * firstChild()=0
Get the first child of this node.
virtual void setContent(char const *value)=0
Set the content of a text or comment node.
virtual char const * attribute(char const *key) const =0
Get the string representation of a node's attribute.
void removeAttribute(Inkscape::Util::const_char_ptr key)
Remove an attribute of this node.
Definition node.h:280
virtual char const * content() const =0
Get the content of a text or comment node.
virtual void addObserver(NodeObserver &observer)=0
Add an object that will be notified of the changes to this node.
virtual Document * document()=0
Get the node's associated document.
virtual NodeType type() const =0
Get the type of the node.
virtual void removeObserver(NodeObserver &observer)=0
Remove an object from the list of observers.
void setXMLDialogSelectedObject(SPObject *activexmltree)
Definition document.h:157
SPObject * getObjectById(std::string const &id) const
Inkscape::XML::Node * getReprRoot()
Definition document.h:206
SPObject * getXMLDialogSelectedObject()
Definition document.h:158
std::vector< SPObject * > getObjectsBySelector(Glib::ustring const &selector) const
SPObject is an abstract base class of all of the document nodes at the SVG document level.
Definition sp-object.h:160
SPStyle * style
Represents the style properties, whether from presentation attributes, the style attribute,...
Definition sp-object.h:248
Inkscape::XML::Node * getRepr()
Returns the XML representation of tree.
void requestDisplayUpdate(unsigned int flags)
Queues an deferred update of this object's display.
void readFromObject(SPObject *object)
Read style properties from object's repr.
Definition style.cpp:608
std::vector< SPIBase * > const & properties() const
Definition style.h:49
RootCluster root
Utilities to more easily use Gtk::EventController & subclasses like Gesture.
std::shared_ptr< Css const > css
Css & result
TODO: insert short description here.
Icon Loader.
Inkscape::XML::Node * node
Glib::ustring label
static R & release(R &r)
Decrements the reference count of a anchored object.
Dialog code.
Definition desktop.h:117
static bool is_url(char const *p)
gboolean sp_styledialog_store_move_to_next(gpointer data)
XML::Node * get_first_style_text_node(XML::Node *root, bool create_if_missing)
Get the first <style> element's first text node.
void remove_all_children(Widget &widget)
For each child in get_children(widget), call widget.remove(*child). May not cause delete child!
Definition util.h:88
std::vector< Gtk::Widget * > get_children(Gtk::Widget &widget)
Get a vector of the widgetʼs children, from get_first_child() through each get_next_sibling().
Definition util.cpp:156
void pack_start(Gtk::Box &box, Gtk::Widget &child, bool const expand, bool const fill, unsigned const padding)
Adds child to box, packed with reference to the start of box.
Definition pack.cpp:141
Glib::RefPtr< Gtk::Builder > create_builder(const char *filename)
void trim(Glib::ustring &input, Glib::ustring const &also_remove="")
Modifies a string in place, removing leading and trailing whitespace characters.
Definition trim.h:34
@ TEXT_NODE
Text node, e.g. "Some text" in <group>Some text</group> is represented by a text node.
static void append(std::vector< T > &target, std::vector< T > &&source)
STL namespace.
Interface for XML node observers.
static cairo_user_data_key_t key
static gint counter
Definition box3d.cpp:39
Helpers for using Gtk::Boxes, encapsulating large changes between GTK3 & GTK4.
Ocnode * child[8]
Definition quantize.cpp:33
SPCSSAttr * sp_repr_css_attr_new()
Creates an empty SPCSSAttr (a class for manipulating CSS style properties).
Definition repr-css.cpp:67
void sp_repr_css_write_string(SPCSSAttr *css, Glib::ustring &str)
Write a style attribute string from a list of properties stored in an SPCSAttr object.
Definition repr-css.cpp:242
void sp_repr_css_attr_add_from_string(SPCSSAttr *css, gchar const *p)
Use libcroco to parse a string for CSS properties and then merge them into an existing SPCSSAttr.
Definition repr-css.cpp:340
Inkscape::IO::Resource - simple resource API.
SPCSSAttr - interface for CSS Attributes.
guint32 GQuark
static const Point data[]
virtual Node * createTextNode(char const *content)=0
virtual Node * createElement(char const *name)=0
char const * key
SPStyle enums: named public enums that correspond to SVG property values.
static SPStyleEnum const enum_baseline_shift[]
static SPStyleEnum const enum_font_variant_position[]
static SPStyleEnum const enum_direction[]
static SPStyleEnum const enum_overflow[]
static SPStyleEnum const enum_shape_rendering[]
static SPStyleEnum const enum_color_rendering[]
static SPStyleEnum const enum_visibility[]
static SPStyleEnum const enum_stroke_linecap[]
static SPStyleEnum const enum_fill_rule[]
static SPStyleEnum const enum_white_space[]
static SPStyleEnum const enum_display[]
static SPStyleEnum const enum_stroke_linejoin[]
static SPStyleEnum const enum_clip_rule[]
static SPStyleEnum const enum_text_align[]
static SPStyleEnum const enum_font_style[]
static SPStyleEnum const enum_font_weight[]
static SPStyleEnum const enum_color_interpolation[]
static SPStyleEnum const enum_font_stretch[]
static SPStyleEnum const enum_text_anchor[]
static SPStyleEnum const enum_text_transform[]
static SPStyleEnum const enum_font_variant[]
SPStyle internal: classes that are internal to SPStyle.
SPStyle - a style object for SPItem objects.
A dialog for CSS selectors.
Glib::ustring name
Definition toolbars.cpp:55
Glib::RefPtr< Gtk::Builder > builder
Inkscape::Util::trim - trim whitespace and other characters.