summaryrefslogtreecommitdiffstats
path: root/src/ui/dialog/selectorsdialog.cpp
diff options
context:
space:
mode:
authorJabier Arraiza <jabier.arraiza@marker.es>2019-06-10 14:14:58 +0000
committerJabier Arraiza <jabier.arraiza@marker.es>2019-06-10 14:15:25 +0000
commitf958a8f1d4e5ac324207aaaeb0c81a84b0646d1a (patch)
treeba5b070b2cfdc98e59b0fe7d54176ea8eb1b41bf /src/ui/dialog/selectorsdialog.cpp
parentFix typo bug found by PeterK (diff)
downloadinkscape-f958a8f1d4e5ac324207aaaeb0c81a84b0646d1a.tar.gz
inkscape-f958a8f1d4e5ac324207aaaeb0c81a84b0646d1a.zip
Move from XMLDialog to another paned dialog
Diffstat (limited to 'src/ui/dialog/selectorsdialog.cpp')
-rw-r--r--src/ui/dialog/selectorsdialog.cpp1396
1 files changed, 1396 insertions, 0 deletions
diff --git a/src/ui/dialog/selectorsdialog.cpp b/src/ui/dialog/selectorsdialog.cpp
new file mode 100644
index 000000000..0d772384e
--- /dev/null
+++ b/src/ui/dialog/selectorsdialog.cpp
@@ -0,0 +1,1396 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * @brief A dialog for CSS selectors
+ */
+/* Authors:
+ * Kamalpreet Kaur Grewal
+ * Tavmjong Bah
+ *
+ * Copyright (C) Kamalpreet Kaur Grewal 2016 <grewalkamal005@gmail.com>
+ * Copyright (C) Tavmjong Bah 2017 <tavmjong@free.fr>
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "selectorsdialog.h"
+#include "verbs.h"
+#include "selection.h"
+#include "attribute-rel-svg.h"
+#include "inkscape.h"
+#include "document-undo.h"
+
+#include "ui/icon-loader.h"
+#include "ui/widget/iconrenderer.h"
+
+#include "xml/attribute-record.h"
+#include "xml/node-observer.h"
+
+#include <glibmm/i18n.h>
+#include <glibmm/regex.h>
+
+#include <map>
+#include <regex>
+#include <utility>
+
+//#define DEBUG_SELECTORSDIALOG
+//#define G_LOG_DOMAIN "SELECTORSDIALOG"
+
+using Inkscape::DocumentUndo;
+using Inkscape::Util::List;
+using Inkscape::XML::AttributeRecord;
+
+/**
+ * This macro is used to remove spaces around selectors or any strings when
+ * parsing is done to update XML style element or row labels in this dialog.
+ */
+#define REMOVE_SPACES(x) \
+ x.erase(0, x.find_first_not_of(' ')); \
+ if (x.size() && x[0] == ',') \
+ x.erase(0, 1); \
+ if (x.size() && x[x.size() - 1] == ',') \
+ x.erase(x.size() - 1, 1); \
+ x.erase(x.find_last_not_of(' ') + 1);
+
+namespace Inkscape {
+namespace UI {
+namespace Dialog {
+
+// Keeps a watch on style element
+class SelectorsDialog::NodeObserver : public Inkscape::XML::NodeObserver {
+public:
+ NodeObserver(SelectorsDialog *selectorsdialog)
+ : _selectorsdialog(selectorsdialog)
+ {
+ g_debug("SelectorsDialog::NodeObserver: Constructor");
+ };
+
+ void notifyContentChanged(Inkscape::XML::Node &node,
+ Inkscape::Util::ptr_shared old_content,
+ Inkscape::Util::ptr_shared new_content) override;
+
+ SelectorsDialog *_selectorsdialog;
+};
+
+
+void
+SelectorsDialog::NodeObserver::notifyContentChanged(
+ Inkscape::XML::Node &/*node*/,
+ Inkscape::Util::ptr_shared /*old_content*/,
+ Inkscape::Util::ptr_shared /*new_content*/ ) {
+
+ g_debug("SelectorsDialog::NodeObserver::notifyContentChanged");
+ _selectorsdialog->_updating = false;
+ _selectorsdialog->_readStyleElement();
+ _selectorsdialog->_selectRow();
+}
+
+
+// Keeps a watch for new/removed/changed nodes
+// (Must update objects that selectors match.)
+class SelectorsDialog::NodeWatcher : public Inkscape::XML::NodeObserver {
+public:
+ NodeWatcher(SelectorsDialog *selectorsdialog, Inkscape::XML::Node *repr)
+ : _selectorsdialog(selectorsdialog)
+ , _repr(repr)
+ {
+ g_debug("SelectorsDialog::NodeWatcher: Constructor");
+ };
+
+ void notifyChildAdded( Inkscape::XML::Node &/*node*/,
+ Inkscape::XML::Node &child,
+ Inkscape::XML::Node */*prev*/ ) override
+ {
+ if (_selectorsdialog && _repr) {
+ _selectorsdialog->_nodeAdded(child);
+ }
+ }
+
+ void notifyChildRemoved( Inkscape::XML::Node &/*node*/,
+ Inkscape::XML::Node &child,
+ Inkscape::XML::Node */*prev*/ ) override
+ {
+ if (_selectorsdialog && _repr) {
+ _selectorsdialog->_nodeRemoved(child);
+ }
+ }
+
+ void notifyAttributeChanged( Inkscape::XML::Node &node,
+ GQuark qname,
+ Util::ptr_shared /*old_value*/,
+ Util::ptr_shared /*new_value*/ ) override {
+ if (_selectorsdialog && _repr) {
+
+ // For the moment only care about attributes that are directly used in selectors.
+ const gchar * cname = g_quark_to_string (qname );
+ Glib::ustring name;
+ if (cname) {
+ name = cname;
+ }
+
+ if ( name == "id" || name == "class" ) {
+ _selectorsdialog->_nodeChanged(node);
+ }
+ }
+ }
+
+ SelectorsDialog *_selectorsdialog;
+ Inkscape::XML::Node * _repr; // Need to track if document changes.
+};
+
+void
+SelectorsDialog::_nodeAdded( Inkscape::XML::Node &node ) {
+
+ SelectorsDialog::NodeWatcher *w = new SelectorsDialog::NodeWatcher (this, &node);
+ node.addObserver (*w);
+ _nodeWatchers.push_back(w);
+
+ _readStyleElement();
+ _selectRow();
+}
+
+void
+SelectorsDialog::_nodeRemoved( Inkscape::XML::Node &repr ) {
+
+ for (auto it = _nodeWatchers.begin(); it != _nodeWatchers.end(); ++it) {
+ if ( (*it)->_repr == &repr ) {
+ (*it)->_repr->removeObserver (**it);
+ _nodeWatchers.erase( it );
+ break;
+ }
+ }
+
+ _readStyleElement();
+ _selectRow();
+}
+
+void
+SelectorsDialog::_nodeChanged( Inkscape::XML::Node &object ) {
+
+ _readStyleElement();
+ _selectRow();
+}
+
+SelectorsDialog::TreeStore::TreeStore()
+= default;
+
+
+/**
+ * Allow dragging only selectors.
+ */
+bool
+SelectorsDialog::TreeStore::row_draggable_vfunc(const Gtk::TreeModel::Path& path) const
+{
+ g_debug("SelectorsDialog::TreeStore::row_draggable_vfunc");
+
+ auto unconstThis = const_cast<SelectorsDialog::TreeStore*>(this);
+ const_iterator iter = unconstThis->get_iter(path);
+ if (iter) {
+ Gtk::TreeModel::Row row = *iter;
+ bool is_draggable = row[_selectorsdialog->_mColumns._colType] == SELECTOR;
+ return is_draggable;
+ }
+ return Gtk::TreeStore::row_draggable_vfunc(path);
+}
+
+void SelectorsDialog::fixCSSSelectors(Glib::ustring &selector)
+{
+ REMOVE_SPACES(selector);
+ Glib::ustring my_selector = selector + " {"; // Parsing fails sometimes without '{'. Fix me
+ CRSelector *cr_selector = cr_selector_parse_from_buf((guchar *)my_selector.c_str(), CR_UTF_8);
+ selector = Glib::ustring("");
+ CRSelector const *cur = nullptr;
+ for (cur = cr_selector; cur; cur = cur->next) {
+ if (cur->simple_sel) {
+ gchar *selectorchar = reinterpret_cast<gchar *>(cr_simple_sel_to_string(cur->simple_sel));
+ if (selectorchar) {
+ Glib::ustring toadd = Glib::ustring(selectorchar);
+ selector = selector.empty() ? toadd : selector + "," + toadd;
+ g_free(selectorchar);
+ }
+ }
+ }
+ std::vector<Glib::ustring> tokens = Glib::Regex::split_simple("[,]+", selector);
+ std::vector<Glib::ustring> selectorresult;
+ selector = Glib::ustring("");
+ for (auto token : tokens) {
+ REMOVE_SPACES(token);
+ std::vector<Glib::ustring> tokensplus = Glib::Regex::split_simple("[ ]+", token);
+ Glib::ustring selectorpart = Glib::ustring("");
+ for (auto tokenplus : tokensplus) {
+ REMOVE_SPACES(tokenplus);
+ Glib::ustring toparse = Glib::ustring(tokenplus);
+ Glib::ustring tag = Glib::ustring("");
+ if (toparse[0] != '.' && toparse[0] != '#') {
+ auto i = std::min(toparse.find("#"), toparse.find("."));
+ tag = toparse.substr(0, i);
+ if (!SPAttributeRelSVG::isSVGElement(tag)) {
+ continue;
+ }
+ if (i != std::string::npos) {
+ toparse.erase(0, i);
+ }
+ }
+ auto i = toparse.find("#");
+ if (i != std::string::npos) {
+ toparse.erase(i, 1);
+ }
+ auto j = toparse.find("#");
+ if (i != std::string::npos && j != std::string::npos) {
+ continue;
+ } else if (i != std::string::npos) {
+ toparse.insert(i, "#");
+ }
+ toparse = tag + toparse;
+ selectorpart = selectorpart == Glib::ustring("") ? toparse : selectorpart + " " + toparse;
+ }
+ selectorresult.push_back(selectorpart);
+ }
+ for (auto selectorpart : selectorresult) {
+ selector = selector == Glib::ustring("") ? selectorpart : selector + "," + selectorpart;
+ }
+}
+
+/**
+ * Allow dropping only in between other selectors.
+ */
+bool
+SelectorsDialog::TreeStore::row_drop_possible_vfunc(const Gtk::TreeModel::Path& dest,
+ const Gtk::SelectionData& selection_data) const
+{
+ g_debug("SelectorsDialog::TreeStore::row_drop_possible_vfunc");
+
+ Gtk::TreeModel::Path dest_parent = dest;
+ dest_parent.up();
+ return dest_parent.empty();
+}
+
+
+// This is only here to handle updating style element after a drag and drop.
+void
+SelectorsDialog::TreeStore::on_row_deleted(const TreeModel::Path& path)
+{
+ if (_selectorsdialog->_updating) return; // Don't write if we deleted row (other than from DND)
+
+ g_debug("on_row_deleted");
+
+ _selectorsdialog->_writeStyleElement();
+}
+
+
+Glib::RefPtr<SelectorsDialog::TreeStore> SelectorsDialog::TreeStore::create(SelectorsDialog *selectorsdialog)
+{
+ SelectorsDialog::TreeStore * store = new SelectorsDialog::TreeStore();
+ store->_selectorsdialog = selectorsdialog;
+ store->set_column_types( store->_selectorsdialog->_mColumns );
+ return Glib::RefPtr<SelectorsDialog::TreeStore>( store );
+}
+
+/**
+ * Constructor
+ * A treeview and a set of two buttons are added to the dialog. _addSelector
+ * adds selectors to treeview. _delSelector deletes the selector from the dialog.
+ * Any addition/deletion of the selectors updates XML style element accordingly.
+ */
+SelectorsDialog::SelectorsDialog() :
+ UI::Widget::Panel("/dialogs/selectors", SP_VERB_DIALOG_SELECTORS),
+ _updating(false),
+ _textNode(nullptr),
+ _desktopTracker()
+{
+ g_debug("SelectorsDialog::SelectorsDialog");
+ // Tree
+ Inkscape::UI::Widget::IconRenderer * addRenderer = manage(
+ new Inkscape::UI::Widget::IconRenderer() );
+ addRenderer->add_icon("edit-delete");
+ addRenderer->add_icon("list-add");
+ _store = TreeStore::create(this);
+ _treeView.set_model(_store);
+
+ _treeView.set_headers_visible(true);
+ _treeView.enable_model_drag_source();
+ _treeView.enable_model_drag_dest( Gdk::ACTION_MOVE );
+ int addCol = _treeView.append_column("", *addRenderer) - 1;
+ Gtk::TreeViewColumn *col = _treeView.get_column(addCol);
+ if ( col ) {
+ col->add_attribute(addRenderer->property_icon(), _mColumns._colType);
+ }
+ _treeView.append_column("CSS Selector", _mColumns._colSelector);
+ _treeView.set_expander_column(*(_treeView.get_column(1)));
+
+ // Signal handlers
+ _treeView.signal_button_release_event().connect( // Needs to be release, not press.
+ sigc::mem_fun(*this, &SelectorsDialog::_handleButtonEvent),
+ false);
+
+ _treeView.signal_button_release_event().connect_notify(
+ sigc::mem_fun(*this, &SelectorsDialog::_buttonEventsSelectObjs),
+ false);
+
+ _treeView.signal_row_expanded().connect(sigc::mem_fun(*this, &SelectorsDialog::_rowExpand));
+
+ _treeView.signal_row_collapsed().connect(sigc::mem_fun(*this, &SelectorsDialog::_rowCollapse));
+
+ _showWidgets();
+
+ // Document & Desktop
+ _desktop_changed_connection = _desktopTracker.connectDesktopChanged(
+ sigc::mem_fun(*this, &SelectorsDialog::_handleDesktopChanged) );
+ _desktopTracker.connect(GTK_WIDGET(gobj()));
+
+ _document_replaced_connection = getDesktop()->connectDocumentReplaced(
+ sigc::mem_fun(this, &SelectorsDialog::_handleDocumentReplaced));
+
+ _selection_changed_connection = getDesktop()->getSelection()->connectChanged(
+ sigc::hide(sigc::mem_fun(this, &SelectorsDialog::_handleSelectionChanged)));
+
+ // Add watchers
+ _updateWatchers();
+
+ // Load tree
+ _readStyleElement();
+ _selectRow();
+
+ if (!_store->children().empty()) {
+ _del.show();
+ }
+ show_all();
+}
+
+void
+SelectorsDialog::_showWidgets()
+{
+ // Pack widgets
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ bool dir = prefs->getBool("/dialogs/selectors/updown", true);
+ _paned.set_orientation(dir ? Gtk::ORIENTATION_VERTICAL : Gtk::ORIENTATION_HORIZONTAL);
+ _selectors_box.set_orientation(Gtk::ORIENTATION_VERTICAL);
+ _selectors_box.set_name("SelectorsDialog");
+ _selectors_box.pack_start(_scrolled_window_selectors, Gtk::PACK_EXPAND_WIDGET);
+ _scrolled_window_selectors.add(_treeView);
+ _scrolled_window_selectors.set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC);
+ Gtk::Label *dirogglerlabel = Gtk::manage(new Gtk::Label(_("Paned vertical")));
+ _direction.property_active().signal_changed().connect(sigc::mem_fun(*this, &SelectorsDialog::_toggleDirection));
+ _direction.get_style_context()->add_class("directiontoggler");
+ _styleButton(_create, "list-add", "Add a new CSS Selector");
+ _create.signal_clicked().connect(sigc::mem_fun(*this, &SelectorsDialog::_addSelector));
+ _styleButton(_del, "list-remove", "Remove a CSS Selector");
+ _button_box.pack_start(_create, Gtk::PACK_SHRINK);
+ _button_box.pack_start(_del, Gtk::PACK_SHRINK);
+ _button_box.pack_start(_direction, Gtk::PACK_SHRINK);
+ _button_box.pack_start(*dirogglerlabel, Gtk::PACK_SHRINK);
+ _selectors_box.pack_end(_button_box, Gtk::PACK_SHRINK);
+ _del.signal_clicked().connect(sigc::mem_fun(*this, &SelectorsDialog::_delSelector));
+ _del.hide();
+ _style_dialog = new StyleDialog;
+ _selectors_box.set_name("StyleDialog");
+ _paned.pack1(*_style_dialog, Gtk::SHRINK);
+ _paned.pack2(_selectors_box, true, true);
+ _paned.set_position(-1);
+ _getContents()->pack_start(_paned, Gtk::PACK_EXPAND_WIDGET);
+ set_name("SelectorsAndStyleDialog");
+}
+
+void
+SelectorsDialog::_toggleDirection()
+{
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ bool dir = !prefs->getBool("/dialogs/selectors/updown", true);
+ prefs->setBool("/dialogs/selectors/updown", dir);
+ _paned.set_orientation(dir ? Gtk::ORIENTATION_VERTICAL : Gtk::ORIENTATION_HORIZONTAL);
+}
+
+/**
+ * Class destructor
+ */
+SelectorsDialog::~SelectorsDialog()
+{
+ g_debug("SelectorsDialog::~SelectorsDialog");
+ _desktop_changed_connection.disconnect();
+ _document_replaced_connection.disconnect();
+ _selection_changed_connection.disconnect();
+}
+
+
+/**
+ * @return Inkscape::XML::Node* pointing to a style element's text node.
+ * Returns the style element's text node. If there is no style element, one is created.
+ * Ditto for text node.
+ */
+Inkscape::XML::Node* SelectorsDialog::_getStyleTextNode()
+{
+
+ Inkscape::XML::Node *styleNode = nullptr;
+ Inkscape::XML::Node *textNode = nullptr;
+
+ Inkscape::XML::Node *root = SP_ACTIVE_DOCUMENT->getReprRoot();
+ for (unsigned i = 0; i < root->childCount(); ++i) {
+ if (Glib::ustring(root->nthChild(i)->name()) == "svg:style") {
+
+ styleNode = root->nthChild(i);
+
+ for (unsigned j = 0; j < styleNode->childCount(); ++j) {
+ if (styleNode->nthChild(j)->type() == Inkscape::XML::TEXT_NODE) {
+ textNode = styleNode->nthChild(j);
+ }
+ }
+
+ if (textNode == nullptr) {
+ // Style element found but does not contain text node!
+ std::cerr << "SelectorsDialog::_getStyleTextNode(): No text node!" << std::endl;
+ textNode = SP_ACTIVE_DOCUMENT->getReprDoc()->createTextNode("");
+ styleNode->appendChild(textNode);
+ Inkscape::GC::release(textNode);
+ }
+ }
+ }
+
+ if (styleNode == nullptr) {
+ // Style element not found, create one
+ styleNode = SP_ACTIVE_DOCUMENT->getReprDoc()->createElement("svg:style");
+ textNode = SP_ACTIVE_DOCUMENT->getReprDoc()->createTextNode("");
+
+ root->addChild(styleNode, nullptr);
+ Inkscape::GC::release(styleNode);
+
+ styleNode->appendChild(textNode);
+ Inkscape::GC::release(textNode);
+ }
+
+ if (_textNode != textNode) {
+ _textNode = textNode;
+ NodeObserver *no = new NodeObserver(this);
+ textNode->addObserver(*no);
+ }
+
+ return textNode;
+}
+
+
+/**
+ * Fill the Gtk::TreeStore from the svg:style element.
+ */
+void SelectorsDialog::_readStyleElement()
+{
+ g_debug("SelectorsDialog::_readStyleElement: updating %s", (_updating ? "true" : "false"));
+
+ if (_updating) return; // Don't read if we wrote style element.
+ _updating = true;
+
+ Inkscape::XML::Node * textNode = _getStyleTextNode();
+ if (textNode == nullptr) {
+ std::cerr << "SelectorsDialog::_readStyleElement: No text node!" << std::endl;
+ }
+
+ // Get content from style text node.
+ std::string content = (textNode->content() ? textNode->content() : "");
+
+ // Remove end-of-lines (check it works on Windoze).
+ content.erase(std::remove(content.begin(), content.end(), '\n'), content.end());
+
+ // Remove comments (/* xxx */)
+ /* while(content.find("/*") != std::string::npos) {
+ size_t start = content.find("/*");
+ content.erase(start, (content.find("*\/", start) - start) +2);
+ } */
+
+ // First split into selector/value chunks.
+ // An attempt to use Glib::Regex failed. A C++11 version worked but
+ // reportedly has problems on Windows. Using split_simple() is simpler
+ // and probably faster.
+ //
+ // Glib::RefPtr<Glib::Regex> regex1 =
+ // Glib::Regex::create("([^\\{]+)\\{([^\\{]+)\\}");
+ //
+ // Glib::MatchInfo minfo;
+ // regex1->match(content, minfo);
+
+ // Split on curly brackets. Even tokens are selectors, odd are values.
+ std::vector<Glib::ustring> tokens = Glib::Regex::split_simple("[}{]", content);
+
+ // If text node is empty, return (avoids problem with negative below).
+ if (tokens.size() == 0) {
+ _updating = false;
+ return;
+ }
+ std::vector<std::pair<Glib::ustring, bool>> expanderstatus;
+ for (unsigned i = 0; i < tokens.size() - 1; i += 2) {
+ Glib::ustring selector = tokens[i];
+ REMOVE_SPACES(selector); // Remove leading/trailing spaces
+ for (auto &row : _store->children()) {
+ Glib::ustring selectorold = row[_mColumns._colSelector];
+ if (selectorold == selector) {
+ expanderstatus.emplace_back(selector, row[_mColumns._colExpand]);
+ }
+ }
+ }
+ _store->clear();
+ bool rewrite = false;
+ for (unsigned i = 0; i < tokens.size()-1; i += 2) {
+
+ Glib::ustring selector = tokens[i];
+ REMOVE_SPACES(selector); // Remove leading/trailing spaces
+ Glib::ustring selector_old = selector;
+ fixCSSSelectors(selector);
+ if (selector_old != selector) {
+ rewrite = true;
+ }
+ if (selector.empty()) {
+ continue;
+ }
+ std::vector<Glib::ustring> tokensplus = Glib::Regex::split_simple("[,]+", selector);
+ coltype colType = SELECTOR;
+ // Get list of objects selector matches
+ std::vector<SPObject *> objVec = _getObjVec( selector );
+
+ Glib::ustring properties;
+ // Check to make sure we do have a value to match selector.
+ if ((i+1) < tokens.size()) {
+ properties = tokens[i+1];
+ } else {
+ std::cerr << "SelectorsDialog::_readStyleElement: Missing values "
+ "for last selector!" << std::endl;
+ }
+ REMOVE_SPACES(properties);
+ bool colExpand = false;
+ for (auto rowstatus : expanderstatus) {
+ if (selector == rowstatus.first) {
+ colExpand = rowstatus.second;
+ }
+ }
+ std::vector<Glib::ustring> properties_data = Glib::Regex::split_simple(";", properties);
+ Gtk::TreeModel::Row row = *(_store->append());
+ row[_mColumns._colSelector] = selector;
+ row[_mColumns._colExpand] = colExpand;
+ row[_mColumns._colType] = colType;
+ row[_mColumns._colObj] = objVec;
+ row[_mColumns._colProperties] = properties;
+ row[_mColumns._colVisible] = true;
+ // Add as children, objects that match selector.
+ for (auto &obj : objVec) {
+ Gtk::TreeModel::Row childrow = *(_store->append(row->children()));
+ childrow[_mColumns._colSelector] = "#" + Glib::ustring(obj->getId());
+ childrow[_mColumns._colExpand] = false;
+ childrow[_mColumns._colType] = colType == OBJECT;
+ ;
+ childrow[_mColumns._colObj] = std::vector<SPObject *>(1, obj);
+ childrow[_mColumns._colProperties] = ""; // Unused
+ childrow[_mColumns._colVisible] = true; // Unused
+ }
+ }
+
+
+ _updating = false;
+ if (rewrite) {
+ _writeStyleElement();
+ }
+}
+
+void SelectorsDialog::_rowExpand(const Gtk::TreeModel::iterator &iter, const Gtk::TreeModel::Path &path)
+{
+ g_debug("SelectorsDialog::_row_expand()");
+ Gtk::TreeModel::Row row = *iter;
+ row[_mColumns._colExpand] = true;
+}
+
+void SelectorsDialog::_rowCollapse(const Gtk::TreeModel::iterator &iter, const Gtk::TreeModel::Path &path)
+{
+ g_debug("SelectorsDialog::_row_collapse()");
+ Gtk::TreeModel::Row row = *iter;
+ row[_mColumns._colExpand] = false;
+}
+/**
+ * Update the content of the style element as selectors (or objects) are added/removed.
+ */
+void SelectorsDialog::_writeStyleElement()
+{
+ if (_updating) {
+ return;
+ }
+ _updating = true;
+
+ Glib::ustring styleContent;
+ for (auto& row: _store->children()) {
+ Glib::ustring selector = row[_mColumns._colSelector];
+ /*
+ REMOVE_SPACES(selector);
+ /* size_t len = selector.size();
+ if(selector[len-1] == ','){
+ selector.erase(len-1);
+ }
+ row[_mColumns._colSelector] = selector; */
+ styleContent = styleContent + selector + " { " + row[_mColumns._colProperties] + " }\n";
+ }
+ // We could test if styleContent is empty and then delete the style node here but there is no
+ // harm in keeping it around ...
+ Inkscape::XML::Node *textNode = _getStyleTextNode();
+ textNode->setContent(styleContent.c_str());
+
+ DocumentUndo::done(SP_ACTIVE_DOCUMENT, SP_VERB_DIALOG_STYLE, _("Edited style element."));
+
+ _updating = false;
+ g_debug("SelectorsDialog::_writeStyleElement(): | %s |", styleContent.c_str());
+}
+
+
+void SelectorsDialog::_addWatcherRecursive(Inkscape::XML::Node *node) {
+
+ g_debug("SelectorsDialog::_addWatcherRecursive()");
+
+ SelectorsDialog::NodeWatcher *w = new SelectorsDialog::NodeWatcher(this, node);
+ node->addObserver(*w);
+ _nodeWatchers.push_back(w);
+
+ for (unsigned i = 0; i < node->childCount(); ++i) {
+ _addWatcherRecursive(node->nthChild(i));
+ }
+}
+
+/**
+ * Update the watchers on objects.
+ */
+void SelectorsDialog::_updateWatchers()
+{
+ _updating = true;
+
+ // Remove old document watchers
+ while (!_nodeWatchers.empty()) {
+ SelectorsDialog::NodeWatcher *w = _nodeWatchers.back();
+ w->_repr->removeObserver(*w);
+ _nodeWatchers.pop_back();
+ delete w;
+ }
+
+ // Recursively add new watchers
+ Inkscape::XML::Node *root = SP_ACTIVE_DOCUMENT->getReprRoot();
+ _addWatcherRecursive(root);
+
+ g_debug("SelectorsDialog::_updateWatchers(): %d", (int)_nodeWatchers.size());
+
+ _updating = false;
+}
+/*
+void sp_get_selector_active(Glib::ustring &selector)
+{
+ std::vector<Glib::ustring> tokensplus = Glib::Regex::split_simple("[ ]+", selector);
+ selector = tokensplus[tokensplus.size() - 1];
+ // Erase any comma/space
+ REMOVE_SPACES(selector);
+ Glib::ustring toadd = Glib::ustring(selector);
+ Glib::ustring toparse = Glib::ustring(selector);
+ Glib::ustring tag = "";
+ if (toadd[0] != '.' || toadd[0] != '#') {
+ auto i = std::min(toadd.find("#"), toadd.find("."));
+ tag = toadd.substr(0,i-1);
+ toparse.erase(0, i-1);
+ }
+ auto i = toparse.find("#");
+ toparse.erase(i, 1);
+ auto j = toparse.find("#");
+ if (j == std::string::npos) {
+ selector = "";
+ } else if (i != std::string::npos) {
+ Glib::ustring post = toadd.substr(0,i-1);
+ Glib::ustring pre = toadd.substr(i, (toadd.size()-1)-i);
+ selector = tag + pre + post;
+ }
+} */
+
+Glib::ustring sp_get_selector_classes(Glib::ustring selector) //, SelectorType selectortype, Glib::ustring id = "")
+{
+ std::pair<Glib::ustring, Glib::ustring> result;
+ std::vector<Glib::ustring> tokensplus = Glib::Regex::split_simple("[ ]+", selector);
+ selector = tokensplus[tokensplus.size() - 1];
+ // Erase any comma/space
+ REMOVE_SPACES(selector);
+ Glib::ustring toparse = Glib::ustring(selector);
+ selector = Glib::ustring("");
+ if (toparse[0] != '.' && toparse[0] != '#') {
+ auto i = std::min(toparse.find("#"), toparse.find("."));
+ Glib::ustring tag = toparse.substr(0, i);
+ if (!SPAttributeRelSVG::isSVGElement(tag)) {
+ return selector;
+ }
+ if (i != std::string::npos) {
+ toparse.erase(0, i);
+ }
+ }
+ auto i = toparse.find("#");
+ if (i != std::string::npos) {
+ toparse.erase(i, 1);
+ }
+ auto j = toparse.find("#");
+ if (j != std::string::npos) {
+ return selector;
+ }
+ if (i != std::string::npos) {
+ toparse.insert(i, "#");
+ if (i) {
+ Glib::ustring post = toparse.substr(0, i);
+ Glib::ustring pre = toparse.substr(i, toparse.size() - i);
+ toparse = pre + post;
+ }
+ auto k = toparse.find(".");
+ if (k != std::string::npos) {
+ toparse = toparse.substr(k, toparse.size() - k);
+ }
+ }
+ return toparse;
+}
+
+/**
+ * @param row
+ * Add selected objects on the desktop to the selector corresponding to 'row'.
+ */
+void SelectorsDialog::_addToSelector(Gtk::TreeModel::Row row)
+{
+ g_debug("SelectorsDialog::_addToSelector: Entrance");
+ if (*row) {
+ // Store list of selected elements on desktop (not to be confused with selector).
+ _updating = true;
+ Inkscape::Selection *selection = getDesktop()->getSelection();
+ std::vector<SPObject *> toAddObjVec(selection->objects().begin(), selection->objects().end());
+ Glib::ustring multiselector = row[_mColumns._colSelector];
+ std::vector<SPObject *> objVec = _getObjVec(multiselector);
+ row[_mColumns._colObj] = objVec;
+ row[_mColumns._colExpand] = true;
+ std::vector<Glib::ustring> tokens = Glib::Regex::split_simple("[,]+", multiselector);
+ for (auto &obj : toAddObjVec) {
+ Glib::ustring id = (obj->getId() ? obj->getId() : "");
+ for (auto tok : tokens) {
+ Glib::ustring clases = sp_get_selector_classes(tok);
+ if (!clases.empty()) {
+ _insertClass(obj, clases);
+ std::vector<SPObject *> currentobjs = _getObjVec(multiselector);
+ bool removeclass = true;
+ for (auto currentobj : currentobjs) {
+ if (currentobj->getId() == id) {
+ removeclass = false;
+ }
+ }
+ if (removeclass) {
+ _removeClass(obj, clases);
+ }
+ }
+ }
+ std::vector<SPObject *> currentobjs = _getObjVec(multiselector);
+ bool insertid = true;
+ for (auto currentobj : currentobjs) {
+ if (currentobj->getId() == id) {
+ insertid = false;
+ }
+ }
+ if (insertid) {
+ multiselector = multiselector + ",#" + id;
+ }
+ Gtk::TreeModel::Row childrow = *(_store->append(row->children()));
+ childrow[_mColumns._colSelector] = "#" + Glib::ustring(id);
+ childrow[_mColumns._colExpand] = false;
+ childrow[_mColumns._colType] = OBJECT;
+ childrow[_mColumns._colObj] = std::vector<SPObject *>(1, obj);
+ childrow[_mColumns._colProperties] = ""; // Unused
+ childrow[_mColumns._colVisible] = true; // Unused
+ }
+ objVec = _getObjVec(multiselector);
+ row[_mColumns._colSelector] = multiselector;
+ row[_mColumns._colObj] = objVec;
+ row[_mColumns._colExpand] = true;
+ _updating = false;
+
+ // Add entry to style element
+ _writeStyleElement();
+ }
+}
+
+/**
+ * @param row
+ * Remove the object corresponding to 'row' from the parent selector.
+ */
+void SelectorsDialog::_removeFromSelector(Gtk::TreeModel::Row row)
+{
+ g_debug("SelectorsDialog::_removeFromSelector: Entrance");
+ if (*row) {
+ _updating = true;
+ Glib::ustring objectLabel = row[_mColumns._colSelector];
+ Gtk::TreeModel::iterator iter = row->parent();
+ if (iter) {
+ Gtk::TreeModel::Row parent = *iter;
+ Glib::ustring multiselector = parent[_mColumns._colSelector];
+ REMOVE_SPACES(multiselector);
+ SPObject *obj = _getObjVec(objectLabel)[0];
+ std::vector<Glib::ustring> tokens = Glib::Regex::split_simple("[,]+", multiselector);
+ Glib::ustring selector = "";
+ for (auto tok : tokens) {
+ if (tok.empty()) {
+ continue;
+ }
+ // TODO: handle when other selectors has the removed class applied to maybe not remove
+ Glib::ustring clases = sp_get_selector_classes(tok);
+ if (!clases.empty()) {
+ _removeClass(obj, tok, true);
+ }
+ auto i = tok.find(row[_mColumns._colSelector]);
+ if (i == std::string::npos) {
+ selector = selector.empty() ? tok : selector + "," + tok;
+ }
+ }
+ REMOVE_SPACES(selector);
+ if (selector.empty()) {
+ _store->erase(parent);
+
+ } else {
+ _store->erase(row);
+ parent[_mColumns._colSelector] = selector;
+ parent[_mColumns._colExpand] = true;
+ parent[_mColumns._colObj] = _getObjVec(selector);
+ }
+ }
+ _updating = false;
+
+ // Add entry to style element
+ _writeStyleElement();
+ }
+}
+
+
+/**
+ * @param sel
+ * @return This function returns a comma separated list of ids for objects in input vector.
+ * It is used in creating an 'id' selector. It relies on objects having 'id's.
+ */
+Glib::ustring SelectorsDialog::_getIdList(std::vector<SPObject*> sel)
+{
+ Glib::ustring str;
+ for (auto& obj: sel) {
+ str += "#" + Glib::ustring(obj->getId()) + ", ";
+ }
+ if (!str.empty()) {
+ str.erase(str.size()-1); // Remove space at end. c++11 has pop_back() but not ustring.
+ str.erase(str.size()-1); // Remove comma at end.
+ }
+ return str;
+}
+
+/**
+ * @param selector: a valid CSS selector string.
+ * @return objVec: a vector of pointers to SPObject's the selector matches.
+ * Return a vector of all objects that selector matches.
+ */
+std::vector<SPObject *> SelectorsDialog::_getObjVec(Glib::ustring selector) {
+
+ g_debug("SelectorsDialog::_getObjVec: | %s |", selector.c_str());
+ std::vector<SPObject *> objVec;
+ std::vector<Glib::ustring> tokensplus = Glib::Regex::split_simple("[,]+", selector);
+ for (auto tok : tokensplus) {
+ REMOVE_SPACES(tok);
+ std::vector<SPObject *> objVecSplited = SP_ACTIVE_DOCUMENT->getObjectsBySelector(tok);
+ for (auto obj : objVecSplited) {
+ bool insert = true;
+ for (auto objv : objVec) {
+ if (objv->getId() == obj->getId()) {
+ insert = false;
+ }
+ }
+ if (insert) {
+ objVec.push_back(obj);
+ }
+ }
+ }
+ /* for (auto& obj: objVec) {
+ g_debug(" %s", obj->getId() ? obj->getId() : "null");
+ } */
+ return objVec;
+}
+
+
+/**
+ * @param objs: list of objects to insert class
+ * @param class: class to insert
+ * Insert a class name into objects' 'class' attribute.
+ */
+void SelectorsDialog::_insertClass(const std::vector<SPObject *> &objVec, const Glib::ustring &className)
+{
+ for (auto& obj: objVec) {
+ _insertClass(obj, className);
+ }
+}
+
+/**
+ * @param objs: list of objects to insert class
+ * @param class: class to insert
+ * Insert a class name into objects' 'class' attribute.
+ */
+void SelectorsDialog::_insertClass(SPObject *obj, const Glib::ustring &className)
+{
+ Glib::ustring classAttr = Glib::ustring("");
+ if (obj->getRepr()->attribute("class")) {
+ classAttr = obj->getRepr()->attribute("class");
+ }
+ std::vector<Glib::ustring> tokens = Glib::Regex::split_simple("[.]+", className);
+ std::sort(tokens.begin(), tokens.end());
+ tokens.erase(std::unique(tokens.begin(), tokens.end()), tokens.end());
+ std::vector<Glib::ustring> tokensplus = Glib::Regex::split_simple("[\\s]+", classAttr);
+ for (auto tok : tokens) {
+ bool exist = false;
+ for (auto &tokenplus : tokensplus) {
+ if (tokenplus == tok) {
+ exist = true;
+ }
+ }
+ if (!exist) {
+ classAttr = classAttr.empty() ? tok : classAttr + " " + tok;
+ }
+ }
+ obj->getRepr()->setAttribute("class", classAttr);
+}
+
+/**
+ * @param objs: list of objects to insert class
+ * @param class: class to insert
+ * Insert a class name into objects' 'class' attribute.
+ */
+void SelectorsDialog::_removeClass(const std::vector<SPObject *> &objVec, const Glib::ustring &className, bool all)
+{
+ for (auto &obj : objVec) {
+ _removeClass(obj, className, all);
+ }
+}
+
+/**
+ * @param objs: list of objects to insert class
+ * @param class: class to insert
+ * Insert a class name into objects' 'class' attribute.
+ */
+void SelectorsDialog::_removeClass(SPObject *obj, const Glib::ustring &className, bool all) // without "."
+{
+ if (obj->getRepr()->attribute("class")) {
+ std::vector<Glib::ustring> tokens = Glib::Regex::split_simple("[.]+", className);
+ Glib::ustring classAttr = obj->getRepr()->attribute("class");
+ Glib::ustring classAttrRestore = classAttr;
+ bool notfound = false;
+ for (auto tok : tokens) {
+ auto i = classAttr.find(tok);
+ if (i != std::string::npos) {
+ classAttr.erase(i, tok.length());
+ } else {
+ notfound = true;
+ }
+ }
+ if (all && notfound) {
+ classAttr = classAttrRestore;
+ }
+ REMOVE_SPACES(classAttr);
+ if (classAttr.empty()) {
+ obj->getRepr()->setAttribute("class", nullptr);
+ } else {
+ obj->getRepr()->setAttribute("class", classAttr);
+ }
+ }
+}
+
+
+/**
+ * @param eventX
+ * @param eventY
+ * This function selects objects in the drawing corresponding to the selector
+ * selected in the treeview.
+ */
+void SelectorsDialog::_selectObjects(int eventX, int eventY)
+{
+ g_debug("SelectorsDialog::_selectObjects: %d, %d", eventX, eventY);
+ getDesktop()->selection->clear();
+ Gtk::TreeViewColumn *col = _treeView.get_column(1);
+ Gtk::TreeModel::Path path;
+ int x2 = 0;
+ int y2 = 0;
+ // To do: We should be able to do this via passing in row.
+ if (_treeView.get_path_at_pos(eventX, eventY, path, col, x2, y2)) {
+ if (col == _treeView.get_column(1)) {
+ Gtk::TreeModel::iterator iter = _store->get_iter(path);
+ if (iter) {
+ Gtk::TreeModel::Row row = *iter;
+ Gtk::TreeModel::Children children = row.children();
+ if (children.empty() || children.size() == 1) {
+ _del.show();
+ }
+ std::vector<SPObject *> objVec = row[_mColumns._colObj];
+
+ for (auto obj : objVec) {
+ getDesktop()->selection->add(obj);
+ }
+ }
+ }
+ }
+}
+
+/**
+ * This function opens a dialog to add a selector. The dialog is prefilled
+ * with an 'id' selector containing a list of the id's of selected objects
+ * or with a 'class' selector if no objects are selected.
+ */
+void SelectorsDialog::_addSelector()
+{
+ g_debug("SelectorsDialog::_addSelector: Entrance");
+
+ // Store list of selected elements on desktop (not to be confused with selector).
+ Inkscape::Selection* selection = getDesktop()->getSelection();
+ std::vector<SPObject *> objVec( selection->objects().begin(),
+ selection->objects().end() );
+
+ // ==== Create popup dialog ====
+ Gtk::Dialog *textDialogPtr = new Gtk::Dialog();
+ textDialogPtr->add_button(_("Cancel"), Gtk::RESPONSE_CANCEL);
+ textDialogPtr->add_button(_("Add"), Gtk::RESPONSE_OK);
+
+ Gtk::Entry *textEditPtr = manage ( new Gtk::Entry() );
+ textEditPtr->signal_activate().connect(
+ sigc::bind<Gtk::Dialog *>(sigc::mem_fun(*this, &SelectorsDialog::_closeDialog), textDialogPtr));
+ textDialogPtr->get_content_area()->pack_start(*textEditPtr, Gtk::PACK_SHRINK);
+
+ Gtk::Label *textLabelPtr = manage(new Gtk::Label(_("Invalid CSS selector.")));
+ textDialogPtr->get_content_area()->pack_start(*textLabelPtr, Gtk::PACK_SHRINK);
+
+ /**
+ * By default, the entrybox contains 'Class1' as text. However, if object(s)
+ * is(are) selected and user clicks '+' at the bottom of dialog, the
+ * entrybox will have the id(s) of the selected objects as text.
+ */
+ if (getDesktop()->getSelection()->isEmpty()) {
+ textEditPtr->set_text(".Class1");
+ } else {
+ textEditPtr->set_text(_getIdList(objVec));
+ }
+
+ Gtk::Requisition sreq1, sreq2;
+ textDialogPtr->get_preferred_size(sreq1, sreq2);
+ int minWidth = 200;
+ int minHeight = 100;
+ minWidth = (sreq2.width > minWidth ? sreq2.width : minWidth );
+ minHeight = (sreq2.height > minHeight ? sreq2.height : minHeight);
+ textDialogPtr->set_size_request(minWidth, minHeight);
+ textEditPtr->show();
+ textLabelPtr->hide();
+ textDialogPtr->show();
+
+
+ // ==== Get response ====
+ int result = -1;
+ bool invalid = true;
+ Glib::ustring selectorValue;
+ while (invalid) {
+ result = textDialogPtr->run();
+ if (result != Gtk::RESPONSE_OK) { // Cancel, close dialog, etc.
+ textDialogPtr->hide();
+ delete textDialogPtr;
+ return;
+ }
+ /**
+ * @brief selectorName
+ * This string stores selector name. The text from entrybox is saved as name
+ * for selector. If the entrybox is empty, the text (thus selectorName) is
+ * set to ".Class1"
+ */
+ selectorValue = textEditPtr->get_text();
+ _del.show();
+ fixCSSSelectors(selectorValue);
+ if (selectorValue.empty()) {
+ textLabelPtr->show();
+ } else {
+ invalid = false;
+ }
+ }
+ delete textDialogPtr;
+ // ==== Handle response ====
+
+ // If class selector, add selector name to class attribute for each object
+ REMOVE_SPACES(selectorValue);
+ std::vector<Glib::ustring> tokens = Glib::Regex::split_simple("[,]+", selectorValue);
+ for (auto &obj : objVec) {
+ for (auto tok : tokens) {
+ Glib::ustring clases = sp_get_selector_classes(tok);
+ if (clases.empty()) {
+ continue;
+ }
+ _insertClass(obj, clases);
+ std::vector<SPObject *> currentobjs = _getObjVec(selectorValue);
+ bool removeclass = true;
+ for (auto currentobj : currentobjs) {
+ if (currentobj->getId() == obj->getId()) {
+ removeclass = false;
+ }
+ }
+ if (removeclass) {
+ _removeClass(obj, clases);
+ }
+ }
+ }
+ objVec = _getObjVec(selectorValue);
+ Gtk::TreeModel::Row row = *(_store->append());
+ row[_mColumns._colExpand] = true;
+ row[_mColumns._colType] = SELECTOR;
+ row[_mColumns._colSelector] = selectorValue;
+ row[_mColumns._colObj] = objVec;
+ row[_mColumns._colProperties] = "";
+ row[_mColumns._colVisible] = true;
+ for (auto &obj : objVec) {
+ Gtk::TreeModel::Row childrow = *(_store->append(row->children()));
+ childrow[_mColumns._colSelector] = "#" + Glib::ustring(obj->getId());
+ childrow[_mColumns._colExpand] = false;
+ childrow[_mColumns._colType] = OBJECT;
+ childrow[_mColumns._colObj] = std::vector<SPObject *>(1, obj);
+ childrow[_mColumns._colProperties] = ""; // Unused
+ childrow[_mColumns._colVisible] = true; // Unused
+ }
+ // Add entry to style element
+ _writeStyleElement();
+}
+
+void SelectorsDialog::_closeDialog(Gtk::Dialog *textDialogPtr) { textDialogPtr->response(Gtk::RESPONSE_OK); }
+
+/**
+ * This function deletes selector when '-' at the bottom is clicked.
+ * Note: If deleting a class selector, class attributes are NOT changed.
+ */
+void SelectorsDialog::_delSelector()
+{
+ g_debug("SelectorsDialog::_delSelector");
+
+ Glib::RefPtr<Gtk::TreeSelection> refTreeSelection = _treeView.get_selection();
+ _treeView.get_selection()->set_mode(Gtk::SELECTION_SINGLE);
+ Gtk::TreeModel::iterator iter = refTreeSelection->get_selected();
+ if (iter) {
+ Gtk::TreeModel::Row row = *iter;
+ if (row.children().size() > 2) {
+ return;
+ }
+ _updating = true;
+ _store->erase(iter);
+ _updating = false;
+ _writeStyleElement();
+ _del.hide();
+ }
+}
+
+/**
+ * @param event
+ * @return
+ * Handles the event when '+' button in front of a selector name is clicked or when a '-' button in
+ * front of a child object is clicked. In the first case, the selected objects on the desktop (if
+ * any) are added as children of the selector in the treeview. In the latter case, the object
+ * corresponding to the row is removed from the selector.
+ */
+bool SelectorsDialog::_handleButtonEvent(GdkEventButton *event)
+{
+ g_debug("SelectorsDialog::_handleButtonEvent: Entrance");
+ if (event->type == GDK_BUTTON_RELEASE && event->button == 1) {
+ Gtk::TreeViewColumn *col = nullptr;
+ Gtk::TreeModel::Path path;
+ int x = static_cast<int>(event->x);
+ int y = static_cast<int>(event->y);
+ int x2 = 0;
+ int y2 = 0;
+
+ if (_treeView.get_path_at_pos(x, y, path, col, x2, y2)) {
+ if (col == _treeView.get_column(0)) {
+ bool remove_parent = false;
+ Gtk::TreeModel::iterator iter = _store->get_iter(path);
+ Gtk::TreeModel::Row row = *iter;
+ Glib::RefPtr<Gtk::TreeSelection> sel = _treeView.get_selection();
+ sel->select(row);
+ // Add or remove objects from a
+ Gtk::TreeModel::Row row_to_sel;
+ if (!row.parent()) {
+ row_to_sel = row;
+ _addToSelector(row);
+ } else {
+ row_to_sel = *row.parent();
+ _removeFromSelector(row);
+ }
+ }
+ }
+ }
+ return false;
+}
+
+// -------------------------------------------------------------------
+
+class PropertyData
+{
+public:
+ PropertyData() = default;;
+ PropertyData(Glib::ustring name) : _name(std::move(name)) {};
+
+ void _setSheetValue(Glib::ustring value) { _sheetValue = value; };
+ void _setAttrValue(Glib::ustring value) { _attrValue = value; };
+ Glib::ustring _getName() { return _name; };
+ Glib::ustring _getSheetValue() { return _sheetValue; };
+ Glib::ustring _getAttrValue() { return _attrValue; };
+
+private:
+ Glib::ustring _name;
+ Glib::ustring _sheetValue;
+ Glib::ustring _attrValue;
+};
+
+// -------------------------------------------------------------------
+
+
+/**
+ * Handle document replaced. (Happens when a default document is immediately replaced by another
+ * document in a new window.)
+ */
+void
+SelectorsDialog::_handleDocumentReplaced(SPDesktop *desktop, SPDocument * /* document */)
+{
+ g_debug("SelectorsDialog::handleDocumentReplaced()");
+
+ _selection_changed_connection.disconnect();
+
+ _selection_changed_connection = desktop->getSelection()->connectChanged(
+ sigc::hide(sigc::mem_fun(this, &SelectorsDialog::_handleSelectionChanged)));
+
+ _updateWatchers();
+ _readStyleElement();
+ _selectRow();
+}
+
+
+/*
+ * When a dialog is floating, it is connected to the active desktop.
+ */
+void
+SelectorsDialog::_handleDesktopChanged(SPDesktop* desktop) {
+ g_debug("SelectorsDialog::handleDesktopReplaced()");
+
+ if (getDesktop() == desktop) {
+ // This will happen after construction of dialog. We've already
+ // set up signals so just return.
+ return;
+ }
+
+ _selection_changed_connection.disconnect();
+ _document_replaced_connection.disconnect();
+
+ setDesktop( desktop );
+
+ _selection_changed_connection = desktop->getSelection()->connectChanged(
+ sigc::hide(sigc::mem_fun(this, &SelectorsDialog::_handleSelectionChanged)));
+ _document_replaced_connection = desktop->connectDocumentReplaced(
+ sigc::mem_fun(this, &SelectorsDialog::_handleDocumentReplaced));
+
+ _updateWatchers();
+ _readStyleElement();
+ _selectRow();
+}
+
+
+/*
+ * Handle a change in which objects are selected in a document.
+ */
+void
+SelectorsDialog::_handleSelectionChanged() {
+ g_debug("SelectorsDialog::_handleSelectionChanged()");
+ _treeView.get_selection()->set_mode(Gtk::SELECTION_MULTIPLE);
+ _selectRow();
+}
+
+
+/**
+ * @param event
+ * This function detects single or double click on a selector in any row. Clicking
+ * on a selector selects the matching objects on the desktop. A double click will
+ * in addition open the CSS dialog.
+ */
+void SelectorsDialog::_buttonEventsSelectObjs(GdkEventButton* event )
+{
+ g_debug("SelectorsDialog::_buttonEventsSelectObjs");
+ _treeView.get_selection()->set_mode(Gtk::SELECTION_SINGLE);
+ _updating = true;
+ _del.show();
+ if (event->type == GDK_BUTTON_RELEASE && event->button == 1) {
+ int x = static_cast<int>(event->x);
+ int y = static_cast<int>(event->y);
+ _selectObjects(x, y);
+ }
+ _updating = false;
+}
+
+
+/**
+ * This function selects the row in treeview corresponding to an object selected
+ * in the drawing. If more than one row matches, the first is chosen.
+ */
+void SelectorsDialog::_selectRow()
+{
+ g_debug("SelectorsDialog::_selectRow: updating: %s", (_updating ? "true" : "false"));
+ _del.hide();
+ std::vector<Gtk::TreeModel::Path> selectedrows = _treeView.get_selection()->get_selected_rows();
+ if (selectedrows.size() == 1) {
+ Gtk::TreeModel::Row row = *_store->get_iter(selectedrows[0]);
+ if (!row->parent() && row->children().size() < 2) {
+ _del.show();
+ }
+ } else if (selectedrows.size() == 0) {
+ _del.show();
+ }
+ if (_updating || !getDesktop()) return; // Avoid updating if we have set row via dialog.
+ if (SP_ACTIVE_DESKTOP != getDesktop()) {
+ std::cerr << "SelectorsDialog::_selectRow: SP_ACTIVE_DESKTOP != getDesktop()" << std::endl;
+ return;
+ }
+
+ _treeView.get_selection()->unselect_all();
+ Gtk::TreeModel::Children children = _store->children();
+ Inkscape::Selection* selection = getDesktop()->getSelection();
+ SPObject *obj = nullptr;
+ if (!selection->isEmpty()) {
+ obj = selection->objects().back();
+ }
+
+ for (auto row : children) {
+ std::vector<SPObject *> objVec = row[_mColumns._colObj];
+ if (obj) {
+ for (auto & i : objVec) {
+ if (obj->getId() == i->getId()) {
+ _treeView.get_selection()->select(row);
+ row[_mColumns._colVisible] = true;
+ break;
+ }
+ }
+ }
+ if (row[_mColumns._colExpand]) {
+ _treeView.expand_to_path(Gtk::TreePath(row));
+ }
+ }
+}
+
+/**
+ * @param btn
+ * @param iconName
+ * @param tooltip
+ * Set the style of '+' and '-' buttons at the bottom of dialog.
+ */
+void SelectorsDialog::_styleButton(Gtk::Button& btn, char const* iconName,
+ char const* tooltip)
+{
+ GtkWidget *child = sp_get_icon_image(iconName, GTK_ICON_SIZE_SMALL_TOOLBAR);
+ gtk_widget_show(child);
+ btn.add(*manage(Glib::wrap(child)));
+ btn.set_relief(Gtk::RELIEF_NONE);
+ btn.set_tooltip_text (tooltip);
+}
+
+
+} // namespace Dialog
+} // namespace UI
+} // namespace Inkscape
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :