diff options
| author | Jabier Arraiza <jabier.arraiza@marker.es> | 2019-04-26 20:55:02 +0000 |
|---|---|---|
| committer | Jabier Arraiza <jabier.arraiza@marker.es> | 2019-06-02 09:50:16 +0000 |
| commit | cc1d3e4bb71dcc1df631c57e1922b12be331d2c1 (patch) | |
| tree | 475bad214df32daa0732a491a60294c26fbd0167 /src/ui/dialog/styledialog.cpp | |
| parent | Backup fro full refactor (diff) | |
| download | inkscape-cc1d3e4bb71dcc1df631c57e1922b12be331d2c1.tar.gz inkscape-cc1d3e4bb71dcc1df631c57e1922b12be331d2c1.zip | |
Working on selectors and unhandled ones
Diffstat (limited to 'src/ui/dialog/styledialog.cpp')
| -rw-r--r-- | src/ui/dialog/styledialog.cpp | 783 |
1 files changed, 520 insertions, 263 deletions
diff --git a/src/ui/dialog/styledialog.cpp b/src/ui/dialog/styledialog.cpp index 10cde4734..a8994047a 100644 --- a/src/ui/dialog/styledialog.cpp +++ b/src/ui/dialog/styledialog.cpp @@ -13,367 +13,624 @@ */ #include "styledialog.h" - -#include "message-context.h" -#include "message-stack.h" +#include "verbs.h" #include "selection.h" -#include "style-internal.h" -#include "style.h" +#include "attribute-rel-svg.h" +#include "inkscape.h" +#include "document-undo.h" #include "ui/icon-loader.h" #include "ui/widget/iconrenderer.h" -#include "verbs.h" + #include "xml/attribute-record.h" -#include "xml/node-event-vector.h" -#include <glibmm/i18n.h> +#include "xml/node-observer.h" #include <glibmm/i18n.h> +#include <glibmm/regex.h> -static void on_attr_changed(Inkscape::XML::Node *repr, const gchar *name, const gchar * /*old_value*/, - const gchar *new_value, bool /*is_interactive*/, gpointer data) -{ - STYLE_DIALOG(data)->onAttrChanged(repr, name, new_value); -} +#include <map> +#include <utility> -Inkscape::XML::NodeEventVector css_repr_events = { - nullptr, /* child_added */ - nullptr, /* child_removed */ - on_attr_changed, nullptr, /* content_changed */ - nullptr /* order_changed */ -}; +//#define DEBUG_STYLEDIALOG +//#define G_LOG_DOMAIN "STYLEDIALOG" + +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(' ')); \ + x.erase(x.find_last_not_of(' ') + 1); namespace Inkscape { namespace UI { namespace Dialog { -/** - * Constructor - * A treeview whose each row corresponds to a CSS property of selector selected. - * New CSS property can be added by clicking '+' at bottom of the CSS pane. '-' - * in front of the CSS property row can be clicked to delete the CSS property. - * Besides clicking on an already selected property row makes the property editable - * and clicking 'Enter' updates the property with changes reflected in the - * drawing. - */ -StyleDialog::StyleDialog() - : UI::Widget::Panel("/dialogs/css", SP_VERB_DIALOG_CSS) - , _desktop(nullptr) - , _repr(nullptr) -{ - set_size_request(20, 15); - _treeView.set_headers_visible(true); - auto _scrolledWindow = new Gtk::ScrolledWindow(); - _selectordialog = new Inkscape::UI::Dialog::SelectorDialog(true); - Gtk::Box *style_box = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_VERTICAL)); - Gtk::Label *selectoropen = new Gtk::Label("element {",Gtk::ALIGN_START); - selectoropen->set_use_markup(); - selectoropen->set_markup(Glib::ustring("<b>element {</b>")); - Gtk::Label *selectorclose = new Gtk::Label("}",Gtk::ALIGN_START); - selectorclose->set_use_markup(); - selectorclose->set_markup(Glib::ustring("<b>}</b>")); - Gtk::Separator *separator = new Gtk::Separator(Gtk::ORIENTATION_HORIZONTAL); - style_box->set_homogeneous(false); - style_box->pack_start(*selectoropen, Gtk::PACK_SHRINK); - style_box->pack_start(_treeView, Gtk::PACK_SHRINK); - style_box->pack_start(*selectorclose, Gtk::PACK_SHRINK); - style_box->pack_start(*separator, Gtk::PACK_SHRINK); - style_box->pack_start(*_selectordialog, Gtk::PACK_EXPAND_WIDGET); - _scrolledWindow->add(*style_box); - _scrolledWindow->set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC); - - _store = Gtk::ListStore::create(_cssColumns); - _treeView.set_model(_store); - - Inkscape::UI::Widget::IconRenderer *addRenderer = manage(new Inkscape::UI::Widget::IconRenderer()); - addRenderer->add_icon("edit-delete"); - addRenderer->signal_activated().connect(sigc::mem_fun(*this, &StyleDialog::onPropertyDelete)); - - _message_stack = std::make_shared<Inkscape::MessageStack>(); - _message_context = std::unique_ptr<Inkscape::MessageContext>(new Inkscape::MessageContext(_message_stack)); - _message_changed_connection = - _message_stack->connectChanged(sigc::bind(sigc::ptr_fun(_set_status_message), GTK_WIDGET(status.gobj()))); - Gtk::CellRendererText *renderer = Gtk::manage(new Gtk::CellRendererText()); - _treeView.set_reorderable(false); - renderer->property_editable() = true; - int nameColNum = _treeView.append_column("", *renderer) - 1; - _propCol = _treeView.get_column(nameColNum); - if (_propCol) { - _propCol->add_attribute(renderer->property_text(), _cssColumns.label); - _propCol->add_attribute(renderer->property_foreground_rgba(), _cssColumns.label_color); +// Keeps a watch on style element +class StyleDialog::NodeObserver : public Inkscape::XML::NodeObserver { +public: + NodeObserver(StyleDialog* styledialog) : + _styledialog(styledialog) + { + g_debug("StyleDialog::NodeObserver: Constructor"); + }; + + void notifyContentChanged(Inkscape::XML::Node &node, + Inkscape::Util::ptr_shared old_content, + Inkscape::Util::ptr_shared new_content) override; + + StyleDialog * _styledialog; +}; + + +void +StyleDialog::NodeObserver::notifyContentChanged( + Inkscape::XML::Node &/*node*/, + Inkscape::Util::ptr_shared /*old_content*/, + Inkscape::Util::ptr_shared /*new_content*/ ) { + + g_debug("StyleDialog::NodeObserver::notifyContentChanged"); + _styledialog->_updating = false; + _styledialog->_readStyleElement(); + _styledialog->_filterRow(); +} + + +// Keeps a watch for new/removed/changed nodes +// (Must update objects that selectors match.) +class StyleDialog::NodeWatcher : public Inkscape::XML::NodeObserver { +public: + NodeWatcher(StyleDialog* styledialog, Inkscape::XML::Node *repr) : + _styledialog(styledialog), + _repr(repr) + { + g_debug("StyleDialog::NodeWatcher: Constructor"); + }; + + void notifyChildAdded( Inkscape::XML::Node &/*node*/, + Inkscape::XML::Node &child, + Inkscape::XML::Node */*prev*/ ) override + { + if ( _styledialog && _repr ) { + _styledialog->_nodeAdded( child ); + } } - _treeView.set_headers_visible(false); - renderer->signal_edited().connect(sigc::mem_fun(*this, &StyleDialog::nameEdited)); - renderer = Gtk::manage(new Gtk::CellRendererText()); - renderer->property_editable() = true; - int attrColNum = _treeView.append_column("", *renderer) - 1; - _attrCol = _treeView.get_column(attrColNum); - if (_attrCol) { - _attrCol->add_attribute(renderer->property_text(), _cssColumns._styleAttrVal); - _attrCol->add_attribute(renderer->property_foreground_rgba(), _cssColumns.attr_color); + + void notifyChildRemoved( Inkscape::XML::Node &/*node*/, + Inkscape::XML::Node &child, + Inkscape::XML::Node */*prev*/ ) override + { + if ( _styledialog && _repr ) { + _styledialog->_nodeRemoved( child ); + } } - renderer->signal_edited().connect(sigc::mem_fun(*this, &StyleDialog::valueEdited)); - renderer = Gtk::manage(new Gtk::CellRendererText()); - renderer->property_editable() = true; - // Set the initial sort column (and direction) to place real attributes at the top. - _store->set_sort_column(_cssColumns.label, Gtk::SORT_ASCENDING); + void notifyAttributeChanged( Inkscape::XML::Node &node, + GQuark qname, + Util::ptr_shared /*old_value*/, + Util::ptr_shared /*new_value*/ ) override { + if ( _styledialog && _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; + } - _getContents()->pack_start(*_scrolledWindow, Gtk::PACK_EXPAND_WIDGET); + if ( name == "id" || name == "class" ) { + _styledialog->_nodeChanged( node ); + } + } + } + + StyleDialog * _styledialog; + Inkscape::XML::Node * _repr; // Need to track if document changes. +}; + +void +StyleDialog::_nodeAdded( Inkscape::XML::Node &node ) { + + StyleDialog::NodeWatcher *w = new StyleDialog::NodeWatcher (this, &node); + node.addObserver (*w); + _nodeWatchers.push_back(w); - css_reset_context(0); - setDesktop(getDesktop()); + _readStyleElement(); + _filterRow(); } -/** - * Class destructor - */ -StyleDialog::~StyleDialog() -{ - setDesktop(nullptr); - _repr = nullptr; - _message_changed_connection.disconnect(); - _message_context = nullptr; - _message_stack = nullptr; - _message_changed_connection.~connection(); +void +StyleDialog::_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(); + _filterRow(); } -void StyleDialog::_set_status_message(Inkscape::MessageType /*type*/, const gchar *message, GtkWidget *widget) +void +StyleDialog::_nodeChanged( Inkscape::XML::Node &object ) { + + _readStyleElement(); + _filterRow(); +} + +StyleDialog::TreeStore::TreeStore() += default; + + +// This is only here to handle updating style element after a drag and drop. +void +StyleDialog::TreeStore::on_row_deleted(const TreeModel::Path& path) { - if (widget) { - gtk_label_set_markup(GTK_LABEL(widget), message ? message : ""); - } + if (_styledialog->_updating) return; // Don't write if we deleted row (other than from DND) + + g_debug("on_row_deleted"); + + _styledialog->_writeStyleElement(); } +Glib::RefPtr<StyleDialog::TreeStore> StyleDialog::TreeStore::create(StyleDialog *styledialog) +{ + StyleDialog::TreeStore * store = new StyleDialog::TreeStore(); + store->_styledialog = styledialog; + store->set_column_types( store->_styledialog->_mColumns ); + return Glib::RefPtr<StyleDialog::TreeStore>( store ); +} + /** - * @param desktop - * This function sets the 'desktop' for the CSS pane. + * 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. */ -void StyleDialog::setDesktop(SPDesktop* desktop) +StyleDialog::StyleDialog() : + UI::Widget::Panel("/dialogs/style", SP_VERB_DIALOG_STYLE), + _updating(false), + _textNode(nullptr), + _desktopTracker() { - _desktop = desktop; + g_debug("StyleDialog::StyleDialog"); + _store = TreeStore::create(this); + _modelfilter = Gtk::TreeModelFilter::create(_store); + _modelfilter->set_visible_column(_mColumns._colVisible); + _treeView.set_model(_modelfilter); + _treeView.set_headers_visible(false); + _treeView.set_grid_lines (Gtk::TREE_VIEW_GRID_LINES_HORIZONTAL); + _treeView.enable_model_drag_source(); + _treeView.enable_model_drag_dest( Gdk::ACTION_MOVE ); + _treeView.append_column("CSS Selector", _mColumns._colData); + + // Pack widgets + _paned.set_orientation(Gtk::ORIENTATION_VERTICAL); + _paned.pack1(_mainBox, Gtk::SHRINK); + _mainBox.set_orientation(Gtk::ORIENTATION_VERTICAL); + + _mainBox.pack_start(_treeView, Gtk::PACK_EXPAND_WIDGET); + _getContents()->pack_start(_paned, Gtk::PACK_EXPAND_WIDGET); + + + // Document & Desktop + _desktop_changed_connection = _desktopTracker.connectDesktopChanged( + sigc::mem_fun(*this, &StyleDialog::_handleDesktopChanged) ); + _desktopTracker.connect(GTK_WIDGET(gobj())); + + _document_replaced_connection = getDesktop()->connectDocumentReplaced( + sigc::mem_fun(this, &StyleDialog::_handleDocumentReplaced)); + + _selection_changed_connection = getDesktop()->getSelection()->connectChanged( + sigc::hide(sigc::mem_fun(this, &StyleDialog::_handleSelectionChanged))); + + // Add watchers + _updateWatchers(); + + // Load tree + _readStyleElement(); + _filterRow(); } + /** - * Set the internal xml object that I'm working on right now. + * Class destructor */ -void StyleDialog::setRepr(Inkscape::XML::Node *repr) +StyleDialog::~StyleDialog() { - if (repr == _repr) - return; - if (_repr) { - _store->clear(); - _repr->removeListenerByData(this); - Inkscape::GC::release(_repr); - _repr = nullptr; - } - _repr = repr; - if (repr) { - Inkscape::GC::anchor(_repr); - _repr->addListener(&css_repr_events, this); - _repr->synthesizeEvents(&css_repr_events, this); - } + g_debug("StyleDialog::~StyleDialog"); + _desktop_changed_connection.disconnect(); + _document_replaced_connection.disconnect(); + _selection_changed_connection.disconnect(); } + /** - * Convert a style string into a vector map. This should be moved to style.cpp - * + * @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. */ -std::map<Glib::ustring, Glib::ustring> StyleDialog::parseStyle(Glib::ustring style_string) +Inkscape::XML::Node* StyleDialog::_getStyleTextNode() { - std::map<Glib::ustring, Glib::ustring> ret; - REMOVE_SPACES(style_string); // We'd use const, but we need to trip spaces - std::vector<Glib::ustring> props = r_props->split(style_string); + Inkscape::XML::Node *styleNode = nullptr; + Inkscape::XML::Node *textNode = nullptr; - for (auto const token : props) { - if (token.empty()) - break; - std::vector<Glib::ustring> pair = r_pair->split(token); + 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); - if (pair.size() > 1) { - ret[pair[0]] = pair[1]; + 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 << "StyleDialog::_getStyleTextNode(): No text node!" << std::endl; + textNode = SP_ACTIVE_DOCUMENT->getReprDoc()->createTextNode(""); + styleNode->appendChild(textNode); + Inkscape::GC::release(textNode); + } } } - return ret; -} -/** - * Turn a vector map back into a style string. - * - */ -Glib::ustring StyleDialog::compileStyle(std::map<Glib::ustring, Glib::ustring> props) -{ - auto ret = Glib::ustring(""); - for (auto const pair : props) { - if (!pair.first.empty() && !pair.second.empty()) { - ret += pair.first; - ret += ":"; - ret += pair.second; - ret += ";"; - } + if (styleNode == nullptr) { + // Style element not found, create one + styleNode = SP_ACTIVE_DOCUMENT->getReprDoc()->createElement("svg:style"); + textNode = SP_ACTIVE_DOCUMENT->getReprDoc()->createTextNode(""); + + styleNode->appendChild(textNode); + Inkscape::GC::release(textNode); + + root->addChild(styleNode, nullptr); + Inkscape::GC::release(styleNode); } - return ret; + + if (_textNode != textNode) { + _textNode = textNode; + NodeObserver *no = new NodeObserver(this); + textNode->addObserver(*no); + } + + return textNode; } /** - * This is called when the XML has an updated attribute (we only care about style) + * Fill the Gtk::TreeStore from the svg:style element. */ -void StyleDialog::onAttrChanged(Inkscape::XML::Node *repr, const gchar *name, const gchar *new_value) +void StyleDialog::_readStyleElement() { - if (strcmp(name, "style") != 0) - return; + g_debug("StyleDialog::_readStyleElement: updating %s", (_updating ? "true" : "false")); - // Clear the list and return if the new_value is empty - _store->clear(); - if (!new_value || new_value[0] == 0) + if (_updating) return; // Don't read if we wrote style element. + _updating = true; + + Inkscape::XML::Node * textNode = _getStyleTextNode(); + if (textNode == nullptr) { + std::cerr << "StyleDialog::_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; + } + _store->clear(); - // Get the object's style attribute and it's calculated properties - SPDocument *document = this->_desktop->doc(); - SPObject *obj = document->getObjectByRepr(repr); - // std::vector<SPIBase *> calc_prop = obj->style->properties(); - - // Get a dictionary lookup of the style in the attribute - std::map<Glib::ustring, Glib::ustring> attr_prop = parseStyle(new_value); - for (auto iter : obj->style->properties()) { - if (iter->style_src != SP_STYLE_SRC_UNSET) { - if (attr_prop.count(iter->name)) { - Gtk::TreeModel::Row row = *(_store->append()); - row[_cssColumns.label] = iter->name; - row[_cssColumns._styleAttrVal] = attr_prop[iter->name]; - row[_cssColumns.isSelector] = false; - } + for (unsigned i = 0; i < tokens.size()-1; i += 2) { + + Glib::ustring selector = tokens[i]; + REMOVE_SPACES(selector); // Remove leading/trailing spaces + std::vector<Glib::ustring> tokensplus = Glib::Regex::split_simple("[,]+", selector); + for (auto tok : tokensplus) { + REMOVE_SPACES(tok); +/* if (tok.find(" ") != -1 || tok.erase(0, 1).find(".") != -1) { + colType = UNHANDLED; + } */ + } + // 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 << "StyleDialog::_readStyleElement: Missing values " + "for last selector!" << std::endl; + } + Gtk::TreeModel::Row row = *(_store->append()); + row[_mColumns._colData] = selector; + row[_mColumns._colObj] = objVec; + row[_mColumns._colVisible] = true; + row[_mColumns._colProperties] = properties; + std::vector<Glib::ustring> properties_data = Glib::Regex::split_simple(";", properties); + for (auto property : properties_data) { + property = REMOVE_SPACES(property); + Gtk::TreeModel::Row childrow = *(_store->append(row->children())); + childrow[_mColumns._colData] = Glib::ustring(property); + childrow[_mColumns._colObj] = {}; + childrow[_mColumns._colProperties] = ""; // Unused + childrow[_mColumns._colVisible] = true; // Unused } } + _updating = false; } -/* - * Sets the CSSDialog status bar, depending on which attr is selected. +/** + * Update the content of the style element as selectors (or objects) are added/removed. */ -void StyleDialog::css_reset_context(gint css) +void StyleDialog::_writeStyleElement() { - if (css == 0) { - _message_context->set(Inkscape::NORMAL_MESSAGE, _("<b>Click</b> CSS property to edit.")); - } else { - const gchar *name = g_quark_to_string(css); - _message_context->setF( - Inkscape::NORMAL_MESSAGE, - _("Property <b>%s</b> selected. Press <b>Ctrl+Enter</b> when done editing to commit changes."), name); + if (_updating) { + return; + } + _updating = true; + + Glib::ustring styleContent; + for (auto& row: _store->children()) { + Glib::ustring selector = row[_mColumns._colData]; + /* + REMOVE_SPACES(selector); + /* size_t len = selector.size(); + if(selector[len-1] == ','){ + selector.erase(len-1); + } + row[_mColumns._colData] = 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("StyleDialog::_writeStyleElement(): | %s |", styleContent.c_str()); +} + + +void StyleDialog::_addWatcherRecursive(Inkscape::XML::Node *node) { + + g_debug("StyleDialog::_addWatcherRecursive()"); + + StyleDialog::NodeWatcher *w = new StyleDialog::NodeWatcher(this, node); + node->addObserver(*w); + _nodeWatchers.push_back(w); + + for (unsigned i = 0; i < node->childCount(); ++i) { + _addWatcherRecursive(node->nthChild(i)); } } /** - * Set or delete a single property in the style attribute. + * Update the watchers on objects. */ -bool StyleDialog::setStyleProperty(Glib::ustring name, Glib::ustring value) +void StyleDialog::_updateWatchers() { - auto original = this->_repr->attribute("style"); - std::map<Glib::ustring, Glib::ustring> properties = parseStyle(original); - - bool updated = false; - if (!value.empty()) { - if (properties[name] != value) { - // Set value (create or update) - properties[name] = value; - updated = true; - } - } else if (properties.count(name)) { - // Delete value - properties.erase(name); - updated = true; + _updating = true; + + // Remove old document watchers + while (!_nodeWatchers.empty()) { + StyleDialog::NodeWatcher *w = _nodeWatchers.back(); + w->_repr->removeObserver(*w); + _nodeWatchers.pop_back(); + delete w; } - if (updated) { - auto new_styles = this->compileStyle(properties); - this->_repr->setAttribute("style", new_styles, false); - // this->setUndo(_("Delete style property")); - } - return updated; + // Recursively add new watchers + Inkscape::XML::Node *root = SP_ACTIVE_DOCUMENT->getReprRoot(); + _addWatcherRecursive(root); + + g_debug("StyleDialog::_updateWatchers(): %d", (int)_nodeWatchers.size()); + + _updating = false; } + /** - * This function is a slot to signal_activated for '-' button panel. + * @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. */ -void StyleDialog::onPropertyDelete(Glib::ustring path) +Glib::ustring StyleDialog::_getIdList(std::vector<SPObject*> sel) { - Gtk::TreeModel::Row row = *_store->get_iter(path); - if (row) { - this->setStyleProperty(row[_cssColumns.label], ""); + 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; } /** - * This function is a slot to signal_clicked for '+' button panel. + * @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. */ -void StyleDialog::onPropertyCreate() -{ - Gtk::TreeIter iter = _store->append(); - Gtk::TreeModel::Path path = (Gtk::TreeModel::Path)iter; - _treeView.set_cursor(path, *_propCol, true); - grab_focus(); +std::vector<SPObject *> StyleDialog::_getObjVec(Glib::ustring selector) { + + std::vector<SPObject *> objVec = SP_ACTIVE_DOCUMENT->getObjectsBySelector( selector ); + + g_debug("StyleDialog::_getObjVec: | %s |", selector.c_str()); + for (auto& obj: objVec) { + g_debug(" %s", obj->getId() ? obj->getId() : "null"); + } + + return objVec; } +void StyleDialog::_closeDialog(Gtk::Dialog *textDialogPtr) { textDialogPtr->response(Gtk::RESPONSE_OK); } + +// ------------------------------------------------------------------- + +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; +}; + +// ------------------------------------------------------------------- + + /** - * @param event_description + * Handle document replaced. (Happens when a default document is immediately replaced by another + * document in a new window.) */ -void StyleDialog::setUndo(Glib::ustring const &event_description) +void +StyleDialog::_handleDocumentReplaced(SPDesktop *desktop, SPDocument * /* document */) { - SPDocument *document = this->_desktop->doc(); - DocumentUndo::done(document, SP_VERB_DIALOG_XML_EDITOR, event_description); + g_debug("StyleDialog::handleDocumentReplaced()"); + + _selection_changed_connection.disconnect(); + + _selection_changed_connection = desktop->getSelection()->connectChanged( + sigc::hide(sigc::mem_fun(this, &StyleDialog::_handleSelectionChanged))); + + _updateWatchers(); + _readStyleElement(); + _filterRow(); } -/** - * @param path - * @param name - * Called when the name is edited in the TreeView editable column + +/* + * When a dialog is floating, it is connected to the active desktop. */ -void StyleDialog::nameEdited (const Glib::ustring& path, const Glib::ustring& name) -{ - Gtk::TreeModel::Row row = *_store->get_iter(path); - if(row && this->_repr && !row[_cssColumns.isSelector]) { - Glib::ustring old_name = row[_cssColumns.label]; - Glib::ustring value = row[_cssColumns._styleAttrVal]; - // Move to editing value, we set the name as a temporary store value - if (!old_name.empty()) { - // Remove old named value - onPropertyDelete(path); - setStyleProperty(name, " "); - } - if (!name.empty()) { - row[_cssColumns.label] = name; - } - this->setUndo(_("Rename CSS attribute")); +void +StyleDialog::_handleDesktopChanged(SPDesktop* desktop) { + g_debug("StyleDialog::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, &StyleDialog::_handleSelectionChanged))); + _document_replaced_connection = desktop->connectDocumentReplaced( + sigc::mem_fun(this, &StyleDialog::_handleDocumentReplaced)); + + _updateWatchers(); + _readStyleElement(); + _filterRow(); +} + + +/* + * Handle a change in which objects are selected in a document. + */ +void +StyleDialog::_handleSelectionChanged() { + g_debug("StyleDialog::_handleSelectionChanged()"); + _filterRow(); } /** - * @brief StyleDialog::valueEdited - * @param event - * @return - * Called when the value is edited in the TreeView editable column + * 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 StyleDialog::valueEdited (const Glib::ustring& path, const Glib::ustring& value) +void StyleDialog::_filterRow() { - Gtk::TreeModel::Row row = *_store->get_iter(path); - if(row && this->_repr && !row[_cssColumns.isSelector]) { - Glib::ustring name = row[_cssColumns.label]; - if(name.empty()) return; - setStyleProperty(name, value); - if(!value.empty()) { - row[_cssColumns._styleAttrVal] = value; - } - std::cout << path << std::endl; - Gtk::TreeIter iter =_store->get_iter(path); - ++iter; - if (!iter) { - onPropertyCreate(); + g_debug("StyleDialog::_selectRow: updating: %s", (_updating ? "true" : "false")); + if (_updating || !getDesktop()) return; // Avoid updating if we have set row via dialog. + if (SP_ACTIVE_DESKTOP != getDesktop()) { + std::cerr << "StyleDialog::_selectRow: SP_ACTIVE_DESKTOP != getDesktop()" << std::endl; + return; + } + Gtk::TreeModel::Children children = _store->children(); + Inkscape::Selection* selection = getDesktop()->getSelection(); + SPObject *obj = nullptr; + if(selection->objects().size() == 1) { + 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()) { + row[_mColumns._colVisible] = true; + break; + } else { + row[_mColumns._colVisible] = false; + } + } } - this->setUndo(_("Change attribute value")); } + _modelfilter->refilter(); } } // 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 : |
