summaryrefslogtreecommitdiffstats
path: root/src/ui/dialog/styledialog.cpp
diff options
context:
space:
mode:
authorShlomi Fish <shlomif@shlomifish.org>2017-02-23 14:39:39 +0000
committerShlomi Fish <shlomif@shlomifish.org>2017-02-23 14:39:39 +0000
commitd82d8e6de61b5a3da73af7d7003b8281720c3dc1 (patch)
tree50a151920b32f95afd0fa0c04f175322657ebb18 /src/ui/dialog/styledialog.cpp
parentMerged. (diff)
parentDisplay style attribute properties when object row selected. Allow their dele... (diff)
downloadinkscape-d82d8e6de61b5a3da73af7d7003b8281720c3dc1.tar.gz
inkscape-d82d8e6de61b5a3da73af7d7003b8281720c3dc1.zip
Merged.
(bzr r15369.1.19)
Diffstat (limited to 'src/ui/dialog/styledialog.cpp')
-rw-r--r--src/ui/dialog/styledialog.cpp1681
1 files changed, 865 insertions, 816 deletions
diff --git a/src/ui/dialog/styledialog.cpp b/src/ui/dialog/styledialog.cpp
index 5246290b4..8d8ebf1b2 100644
--- a/src/ui/dialog/styledialog.cpp
+++ b/src/ui/dialog/styledialog.cpp
@@ -3,8 +3,10 @@
*/
/* 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, read the file 'COPYING' for more information
*/
@@ -16,8 +18,16 @@
#include "sp-object.h"
#include "selection.h"
#include "xml/attribute-record.h"
+#include "xml/node-observer.h"
+#include "attribute-rel-svg.h"
+#include "document-undo.h"
+
+#include <glibmm/i18n.h>
#include <glibmm/regex.h>
-
+
+//#define DEBUG_STYLEDIALOG
+
+using Inkscape::DocumentUndo;
using Inkscape::Util::List;
using Inkscape::XML::AttributeRecord;
@@ -32,23 +42,104 @@ namespace Inkscape {
namespace UI {
namespace Dialog {
+class StyleDialog::NodeObserver : public Inkscape::XML::NodeObserver {
+public:
+ NodeObserver(StyleDialog* styleDialog) :
+ _styleDialog(styleDialog)
+ {
+#ifdef DEBUG_STYLEDIALOG
+ std::cout << "StyleDialog::NodeObserver: Constructor" << std::endl;
+#endif
+ };
+
+ virtual void notifyContentChanged(Inkscape::XML::Node &node,
+ Inkscape::Util::ptr_shared<char> old_content,
+ Inkscape::Util::ptr_shared<char> new_content);
+
+ StyleDialog * _styleDialog;
+};
+
+
+void
+StyleDialog::NodeObserver::notifyContentChanged(
+ Inkscape::XML::Node &/*node*/,
+ Inkscape::Util::ptr_shared<char> /*old_content*/,
+ Inkscape::Util::ptr_shared<char> /*new_content*/ ) {
+
+#ifdef DEBUG_STYLEDIALOG
+ std::cout << "StyleDialog::NodeObserver::notifyContentChanged" << std::endl;
+#endif
+
+ _styleDialog->_readStyleElement();
+ _styleDialog->_selectRow(NULL);
+}
+
+
+StyleDialog::TreeStore::TreeStore()
+{
+}
+
+
/**
- * @brief StyleDialog::_styleButton
- * @param btn
- * @param iconName
- * @param tooltip
- * This function sets the style of '+' and '-' buttons at the bottom of dialog.
+ * Allow dragging only selectors.
*/
-void StyleDialog::_styleButton(Gtk::Button& btn, char const* iconName,
- char const* tooltip)
+bool
+StyleDialog::TreeStore::row_draggable_vfunc(const Gtk::TreeModel::Path& path) const
{
- GtkWidget *child = sp_icon_new(Inkscape::ICON_SIZE_SMALL_TOOLBAR, iconName);
- gtk_widget_show(child);
- btn.add(*manage(Glib::wrap(child)));
- btn.set_relief(Gtk::RELIEF_NONE);
- btn.set_tooltip_text (tooltip);
+#ifdef DEBUG_STYLEDIALOG
+ std::cout << "StyleDialog::TreeStore::row_draggable_vfunc" << std::endl;
+#endif
+ auto unconstThis = const_cast<StyleDialog::TreeStore*>(this);
+ const_iterator iter = unconstThis->get_iter(path);
+ if (iter) {
+ Gtk::TreeModel::Row row = *iter;
+ bool is_draggable = row[_styledialog->_mColumns._colIsSelector];
+ return is_draggable;
+ }
+ return Gtk::TreeStore::row_draggable_vfunc(path);
}
+
+/**
+ * Allow dropping only inbetween other selectors.
+ */
+bool
+StyleDialog::TreeStore::row_drop_possible_vfunc(const Gtk::TreeModel::Path& dest,
+ const Gtk::SelectionData& selection_data) const
+{
+#ifdef DEBUG_STYLEDIALOG
+ std::cout << "StyleDialog::TreeStore::row_drop_possible_vfunc" << std::endl;
+#endif
+
+ 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
+StyleDialog::TreeStore::on_row_deleted(const TreeModel::Path& path)
+{
+#ifdef DEBUG_STYLEDIALOG
+ std::cout << "on_row_deleted" << std::endl;
+#endif
+
+ if (_styledialog->_updating) return; // Don't write if we deleted row (other than from DND)
+
+ _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 );
+}
+
+
/**
* Constructor
* A treeview and a set of two buttons are added to the dialog. _addSelector
@@ -57,42 +148,45 @@ void StyleDialog::_styleButton(Gtk::Button& btn, char const* iconName,
*/
StyleDialog::StyleDialog() :
UI::Widget::Panel("", "/dialogs/style", SP_VERB_DIALOG_STYLE),
- _desktop(0)
+ _desktop(0),
+ _updating(false),
+ _textNode(NULL)
{
- set_size_request(200, 200);
-
- _paned.pack1(_mainBox, Gtk::SHRINK);
- _mainBox.pack_start(_scrolledWindow, Gtk::PACK_EXPAND_WIDGET);
- _treeView.set_headers_visible(false);
- _scrolledWindow.add(_treeView);
- _scrolledWindow.set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC);
-
- _store = Gtk::TreeStore::create(_mColumns);
- _treeView.set_model(_store);
+#ifdef DEBUG_STYLEDIALOG
+ std::cout << "StyleDialog::StyleDialog" << std::endl;
+#endif
+ // Tree
Inkscape::UI::Widget::AddToIcon * addRenderer = manage(
new Inkscape::UI::Widget::AddToIcon() );
- addRenderer->property_active() = true;
- int addCol = _treeView.append_column("type", *addRenderer) - 1;
+ _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_active(), _mColumns._colAddRemove );
+ col->add_attribute( addRenderer->property_active(), _mColumns._colIsSelector );
}
-
- _treeView.append_column("Selector Name", _mColumns._selectorLabel);
+ _treeView.append_column("CSS Selector", _mColumns._colSelector);
_treeView.set_expander_column(*(_treeView.get_column(1)));
+ // Pack widgets
+ _paned.pack1(_mainBox, Gtk::SHRINK);
+ _mainBox.pack_start(_scrolledWindow, Gtk::PACK_EXPAND_WIDGET);
+ _scrolledWindow.add(_treeView);
+ _scrolledWindow.set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC);
+
create = manage( new Gtk::Button() );
_styleButton(*create, "list-add", "Add a new CSS Selector");
- create->signal_clicked().connect(sigc::mem_fun(*this,
- &StyleDialog::_addSelector));
+ create->signal_clicked().connect(sigc::mem_fun(*this, &StyleDialog::_addSelector));
del = manage( new Gtk::Button() );
_styleButton(*del, "list-remove", "Remove a CSS Selector");
- del->signal_clicked().connect(sigc::mem_fun(*this,
- &StyleDialog::_delSelector));
+ del->signal_clicked().connect(sigc::mem_fun(*this, &StyleDialog::_delSelector));
del->set_sensitive(false);
_mainBox.pack_end(_buttonBox, Gtk::PACK_SHRINK);
@@ -102,8 +196,43 @@ StyleDialog::StyleDialog() :
_getContents()->pack_start(_paned, Gtk::PACK_EXPAND_WIDGET);
- _targetDesktop = getDesktop();
- setDesktop(_targetDesktop);
+ // Dialog size request
+ Gtk::Requisition sreq1, sreq2;
+ get_preferred_size(sreq1, sreq2);
+ int minWidth = 200;
+ int minHeight = 300;
+ minWidth = (sreq2.width > minWidth ? sreq2.width : minWidth );
+ minHeight = (sreq2.height > minHeight ? sreq2.height : minHeight);
+ set_size_request(minWidth, minHeight);
+
+ // Signal handlers
+ _treeView.signal_button_release_event().connect( // Needs to be release, not press.
+ sigc::mem_fun(*this, &StyleDialog::_handleButtonEvent),
+ false);
+
+ _treeView.signal_button_release_event().connect_notify(
+ sigc::mem_fun(*this, &StyleDialog::_buttonEventsSelectObjs),
+ false);
+
+ _treeView.get_selection()->signal_changed().connect(
+ sigc::mem_fun(*this, &StyleDialog::_selChanged));
+
+ _objObserver.signal_changed().connect(sigc::mem_fun(*this, &StyleDialog::_objChanged));
+
+
+ // Add CSS dialog
+ _cssPane = new CssDialog;
+ _paned.pack2(*_cssPane, Gtk::SHRINK);
+ _cssPane->show_all();
+
+ _cssPane->_textRenderer->signal_edited().connect(
+ sigc::mem_fun(*this, &StyleDialog::_handleEdited));
+ _cssPane->_treeView.signal_button_release_event().connect(
+ sigc::mem_fun(*this, &StyleDialog::_delProperty),
+ false);
+
+ // Document & Desktop TO DO: Fix this brokeness
+ setDesktop(getDesktop()); // Adds signal handler
/**
* @brief document
@@ -111,444 +240,643 @@ StyleDialog::StyleDialog() :
* and is then used to populate the treeview with the already existing
* selectors in the style element.
*/
- _styleExists = false;
- _document = _targetDesktop->doc();
- _selectorValue = _populateTree(_getSelectorVec());
-
- _treeView.signal_button_press_event().connect(sigc::mem_fun(*this,
- &StyleDialog::
- _handleButtonEvent),
- false);
+ _document = _desktop->doc();
- _treeView.signal_button_press_event().connect_notify(sigc::mem_fun
- (*this, &StyleDialog::
- _buttonEventsSelectObjs),
- false);
+ _readStyleElement();
+ _selectRow(NULL);
- _cssPane = new CssDialog;
+ if (!_store->children().empty()) {
+ del->set_sensitive(true);
+ }
- _treeView.get_selection()->signal_changed().connect(sigc::mem_fun(*this,
- &StyleDialog::
- _selChanged));
}
+
/**
* @brief StyleDialog::~StyleDialog
* Class destructor
*/
StyleDialog::~StyleDialog()
{
+#ifdef DEBUG_STYLEDIALOOG
+ std::cout << "StyleDialog::~StyleDialog" << std::endl;
+#endif
setDesktop(NULL);
}
+
/**
* @brief StyleDialog::setDesktop
* @param desktop
- * This function sets the 'desktop' for the Style Dialog.
+ * Set the 'desktop' for the Style Dialog.
*/
void StyleDialog::setDesktop( SPDesktop* desktop )
{
+#ifdef DEBUG_STYLEDIALOOG
+ std::cout << "StyleDialog::setDesktop" << std::endl;
+#endif
Panel::setDesktop(desktop);
_desktop = Panel::getDesktop();
- _desktop->getSelection()->connectChanged(sigc::mem_fun(*this, &StyleDialog::
- _selectRow));
+ _desktop->getSelection()->connectChanged(sigc::mem_fun(*this, &StyleDialog::_selectRow));
}
+
/**
- * @brief StyleDialog::_addSelector
- * This function is the slot to the signal emitted when '+' at the bottom of
- * the dialog is clicked.
+ * @brief StyleDialog::_styleTextNode
+ * @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.
*/
-void StyleDialog::_addSelector()
+Inkscape::XML::Node* StyleDialog::_getStyleTextNode()
{
- Gtk::TreeModel::Row row = *(_store->append());
- /**
- * On clicking '+' button, an entrybox with default text opens up. If an
- * object is already selected, a selector with value in the entry
- * is added to a new style element.
- */
- Gtk::Dialog *textDialogPtr = new Gtk::Dialog();
- Gtk::Entry *textEditPtr = manage ( new Gtk::Entry() );
- textDialogPtr->add_button("Add", Gtk::RESPONSE_OK);
- textDialogPtr->get_vbox()->pack_start(*textEditPtr, Gtk::PACK_SHRINK);
+ Inkscape::XML::Node *styleNode = NULL;
+ Inkscape::XML::Node *textNode = NULL;
- /**
- * 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 (_desktop->getSelection()->isEmpty()) {
- textEditPtr->set_text("Class1");
- }
- else {
- Inkscape::Selection* selection = _desktop->getSelection();
- std::vector<SPObject*> selected = std::vector<SPObject *>(selection
- ->objects().begin(),
- selection->
- objects().end());
- textEditPtr->set_text(_setClassAttribute(selected));
+ Inkscape::XML::Node *root = _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 == NULL) {
+ // Style element found but does not contain text node!
+ std::cerr << "StyleDialog::_getStyleTextNode(): No text node!" << std::endl;
+ textNode = _document->getReprDoc()->createTextNode("");
+ styleNode->appendChild(textNode);
+ Inkscape::GC::release(textNode);
+ }
+ }
}
- textDialogPtr->set_size_request(200, 100);
- textDialogPtr->show_all();
- int result = textDialogPtr->run();
+ if (styleNode == NULL) {
+ // Style element not found, create one
+ styleNode = _document->getReprDoc()->createElement("svg:style");
+ textNode = _document->getReprDoc()->createTextNode("");
- /**
- * @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"
- */
- if (!textEditPtr->get_text().empty()) {
- _selectorName = textEditPtr->get_text();
+ styleNode->appendChild(textNode);
+ Inkscape::GC::release(textNode);
+
+ root->addChild(styleNode, NULL);
+ Inkscape::GC::release(styleNode);
}
- else {
- _selectorName = ".Class1";
+
+ if (_textNode != textNode) {
+ _textNode = textNode;
+ NodeObserver *no = new NodeObserver(this);
+ textNode->addObserver(*no);
}
- del->set_sensitive(true);
+ return textNode;
+}
- /**
- * The selector name objects is set to the text that the user sets in the
- * entrybox. If the attribute does not exist, it is
- * created. In case the attribute already has a value, the new value entered
- * is appended to the values. If a style attribute does not exist, it is
- * created with an empty value. Also if a class selector is added, then
- * class attribute for the selected object is set too.
- */
- std::vector<SPObject *> objVec;
-
- bool objExists = false;
- if (!_desktop->getSelection()->isEmpty()) {
- for (auto& obj: _desktop->getSelection()->objects()) {
- objExists = true;
- if (!obj->getRepr()->attribute("style")) {
- obj->getRepr()->setAttribute("style", NULL);
- }
- if (obj->getAttribute("style") == NULL) {
- _selectorValue = _selectorName + "{" + "}" + "\n";
- }
- else {
- _selectorValue = _selectorName + "{"
- + obj->getAttribute("style") + "}" + "\n";
- }
+/**
+ * @brief StyleDialog::_readStyleElement
+ * Fill the Gtk::TreeStore from the svg:style element.
+ */
+void StyleDialog::_readStyleElement()
+{
+#ifdef DEBUG_STYLEDIALOG
+ std::cout << "StyleDialog::_readStyleElement: updating " << (_updating?"true":"false")<< std::endl;
+#endif
- if (_selectorName[0] == '.') {
- if (!obj->getRepr()->attribute("class")) {
- obj->getRepr()->setAttribute("class", textEditPtr->get_text()
- .erase(0,1));
- }
- else {
- obj->getRepr()->setAttribute("class", std::string(obj->
- getRepr()->
- attribute("class"))
- + " " + textEditPtr->get_text()
- .erase(0,1));
- }
- }
- }
+ if (_updating) return; // Don't read if we wrote style element.
+ _updating = true;
+
+ _store->clear();
+
+ Inkscape::XML::Node * textNode = _getStyleTextNode();
+ if (textNode == NULL) {
+ std::cerr << "StyleDialog::_readStyleElement: No text node!" << std::endl;
}
- else {
- _selectorValue = _selectorName + "{" + "}" + "\n";
- objExists = false;
+
+ // 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());
+
+ // 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) {
+ return;
}
- switch (result) {
- case Gtk::RESPONSE_OK:
- textDialogPtr->hide();
- row[_mColumns._selectorLabel] = _selectorName;
- row[_mColumns._colAddRemove] = true;
- if (objExists) {
- Inkscape::Selection* selection = _desktop->getSelection();
- row[_mColumns._colObj] = std::vector<SPObject *>(selection->objects()
- .begin(), selection
- ->objects().end());
- objVec = row[_mColumns._colObj];
+ for (unsigned i = 0; i < tokens.size()-1; i += 2) {
+
+ Glib::ustring selector = tokens[i];
+ REMOVE_SPACES(selector); // Remove leading/trailing spaces
+
+ // 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;
}
- break;
- default:
- break;
- }
+ REMOVE_SPACES(properties);
- /**
- * A new style element is added to the document with value obtained
- * from selectorValue above. If style element already exists, then
- * the new selector's content is appended to its previous content.
- */
- inkSelector._selector = _selectorName;
- inkSelector._matchingObjs = objVec;
- inkSelector._xmlContent = _selectorValue;
- _selectorVec.push_back(inkSelector);
-
- if (_styleElementNode()) {
- _styleChild = _styleElementNode();
- _updateStyleContent();
- }
- else if (_styleExists && !_newDrawing) {
- _updateStyleContent();
- }
- else if (!_styleExists) {
- Inkscape::XML::Node *root = _document->getReprDoc()->root();
- Inkscape::XML::Node *newChild = _document->getReprDoc()
- ->createElement("svg:style");
- Inkscape::XML::Node *smallChildren = _document->getReprDoc()
- ->createTextNode(_selectorValue.c_str());
-
- newChild->appendChild(smallChildren);
- Inkscape::GC::release(smallChildren);
-
- root->addChild(newChild, NULL);
- Inkscape::GC::release(newChild);
- _styleChild = newChild;
+ Gtk::TreeModel::Row row = *(_store->append());
+ row[_mColumns._colSelector] = selector;
+ row[_mColumns._colIsSelector] = true;
+ row[_mColumns._colObj] = objVec;
+ row[_mColumns._colProperties] = properties;
+
+ // 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._colIsSelector] = false;
+ childrow[_mColumns._colObj] = std::vector<SPObject *>(1, obj);
+ childrow[_mColumns._colProperties] = ""; // Unused
+ }
}
- _selAdd(row);
+ _updating = false;
}
+
/**
- * @brief StyleDialog::_updateStyleContent
- * This function updates the content in style element as new selectors (or
- * objects) are added/removed.
+ * @brief StyleDialog::_writeStyleElement
+ * Update the content of the style element as selectors (or objects) are added/removed.
*/
-void StyleDialog::_updateStyleContent()
+void StyleDialog::_writeStyleElement()
{
- std::string styleContent = "";
- for (unsigned i = 0; i < _selectorVec.size(); ++i) {
- styleContent = styleContent + _selectorVec[i]._xmlContent;
+ _updating = true;
+
+ Glib::ustring styleContent;
+ for (auto& row: _store->children()) {
+ styleContent = styleContent + row[_mColumns._colSelector] +
+ " { " + row[_mColumns._colProperties] + " }\n";
}
- _styleChild->firstChild()->setContent(styleContent.c_str());
+ // 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(_document, SP_VERB_DIALOG_STYLE, _("Edited style element."));
+
+ _updating = false;
+#ifdef DEBUG_STYLEDIALOG
+ std::cout << "StyleDialog::_writeStyleElement(): |" << styleContent << "|" << std::endl;
+#endif
}
+
/**
- * @brief StyleDialog::_delSelector
- * This function deletes selector when '-' at the bottom is clicked. The index
- * of selected row is obtained and the corresponding selector and its values are
- * deleted from the selector vector. If a row has no parent, it is directly
- * erased from the vector along with its child rows. The style element is updated
- * accordingly.
+ * @brief StyleDialog::_addToSelector
+ * @param row
+ * Add selected objects on the desktop to the selector corresponding to 'row'.
*/
-void StyleDialog::_delSelector()
+void StyleDialog::_addToSelector(Gtk::TreeModel::Row row)
{
- Glib::RefPtr<Gtk::TreeSelection> refTreeSelection = _treeView.get_selection();
- Gtk::TreeModel::iterator iter = refTreeSelection->get_selected();
- if (iter) {
- Gtk::TreeModel::Row row = *iter;
- std::string sel, key, value;
- std::vector<InkSelector>::iterator it;
- for (it = _selectorVec.begin(); it != _selectorVec.end();) {
- sel = (*it)._xmlContent;
- REMOVE_SPACES(sel);
- if (!sel.empty()) {
- key = strtok((char*)sel.c_str(), "{");
- REMOVE_SPACES(key);
- char *temp = strtok(NULL, "}");
- if (strtok(temp, "}") != NULL) {
- value = strtok(temp, "}");
+#ifdef DEBUG_STYLEDIALOG
+ std::cout << "StyleDialog::_addToSelector: Entrance" << std::endl;
+#endif
+ if (*row) {
+
+ Glib::ustring selector = row[_mColumns._colSelector];
+
+ if (selector[0] == '#') {
+ // 'id' selector... add selected object's id's to list.
+ Inkscape::Selection* selection = _desktop->getSelection();
+ for (auto& obj: selection->objects()) {
+
+ Glib::ustring id = (obj->getId()?obj->getId():"");
+
+ std::vector<SPObject *> objVec = row[_mColumns._colObj];
+ bool found = false;
+ for (auto& obj: objVec) {
+ if (id == obj->getId()) {
+ found = true;
+ break;
+ }
}
- }
- Glib::ustring selectedRowLabel = row[_mColumns._selectorLabel];
- std::string matchSelector = selectedRowLabel;
- REMOVE_SPACES(matchSelector);
- if (key == matchSelector) {
- it = _selectorVec.erase(it);
- _store->erase(row);
- }
- else {
- ++it;
+ if (!found) {
+ // Update row
+ objVec.push_back(obj); // Adding to copy so need to update tree
+ row[_mColumns._colObj] = objVec;
+ row[_mColumns._colSelector] = _getIdList( objVec );
+
+ // Add child row
+ Gtk::TreeModel::Row childrow = *(_store->append(row->children()));
+ childrow[_mColumns._colSelector] = "#" + Glib::ustring(obj->getId());
+ childrow[_mColumns._colIsSelector] = false;
+ childrow[_mColumns._colObj] = std::vector<SPObject *>(1, obj);
+ childrow[_mColumns._colProperties] = ""; // Unused
+ }
}
+ }
- /**
- * The _stylechild is obtained which contains the style element and
- * the content in style element is updated. If _selectorVec is
- * empty, the style element is removed from the XML repr else
- * the content is updated simply using _updateStyleContent().
- */
- _styleChild = _styleElementNode();
- if (_store->children().empty()) {
- _document->getReprRoot()->removeChild(_styleChild);
- _styleExists = false;
- }
- else {
- _updateStyleContent();
+ else if (selector[0] == '.') {
+ // 'class' selector... add value to class attribute of selected objects.
+
+ // Get first class (split on white space or comma)
+ std::vector<Glib::ustring> tokens = Glib::Regex::split_simple("[,\\s]+", selector);
+ Glib::ustring className = tokens[0];
+ className.erase(0,1);
+
+ // Get list of objects to modify
+ Inkscape::Selection* selection = _desktop->getSelection();
+ std::vector<SPObject *> objVec( selection->objects().begin(),
+ selection->objects().end() );
+
+ _insertClass( objVec, className );
+
+ row[_mColumns._colObj] = _getObjVec( selector );
+
+ for (auto& obj: objVec) {
+ // Add child row
+ Gtk::TreeModel::Row childrow = *(_store->append(row->children()));
+ childrow[_mColumns._colSelector] = "#" + Glib::ustring(obj->getId());
+ childrow[_mColumns._colIsSelector] = false;
+ childrow[_mColumns._colObj] = std::vector<SPObject *>(1, obj);
+ childrow[_mColumns._colProperties] = ""; // Unused
}
}
+
+ else {
+ // Do nothing for element selectors.
+ // std::cout << " Element selector... doing nothing!" << std::endl;
+ }
}
+
+ _writeStyleElement();
}
+
/**
- * @brief StyleDialog::_styleElementNode
- * @return
- * This function returns the node containing style element. The document's
- * children are iterated and the repr of the style element that occurs is
- * obtained.
+ * @brief StyleDialog::_removeFromSelector
+ * @param row
+ * Remove the object corresponding to 'row' from the parent selector.
*/
-Inkscape::XML::Node* StyleDialog::_styleElementNode()
+void StyleDialog::_removeFromSelector(Gtk::TreeModel::Row row)
{
- for (unsigned i = 0; i < _document->getReprRoot()->childCount(); ++i) {
- if (std::string(_document->getReprRoot()->nthChild(i)->name())
- == "svg:style") {
- _styleExists = true;
- _newDrawing = true;
- return _document->getReprRoot()->nthChild(i);
+#ifdef DEBUG_STYLEDIALOG
+ std::cout << "StyleDialog::_removeFromSelector: Entrance" << std::endl;
+#endif
+ if (*row) {
+
+ Glib::ustring objectLabel = row[_mColumns._colSelector];
+ Gtk::TreeModel::iterator iter = row->parent();
+ if (iter) {
+ Gtk::TreeModel::Row parent = *iter;
+ Glib::ustring selector = parent[_mColumns._colSelector];
+
+ if (selector[0] == '#') {
+ // 'id' selector... remove selected object's id's to list.
+
+ // Erase from selector label.
+ auto i = selector.find(objectLabel);
+ if (i != Glib::ustring::npos) {
+ selector.erase(i, objectLabel.length());
+ }
+ // Erase any comma/space
+ if (i != Glib::ustring::npos && selector[i] == ',') {
+ selector.erase(i, 1);
+ }
+ if (i != Glib::ustring::npos && selector[i] == ' ') {
+ selector.erase(i, 1);
+ }
+
+ // Update store
+ if (selector.empty()) {
+ _store->erase(parent);
+ } else {
+ // Save new selector and update object vector.
+ parent[_mColumns._colSelector] = selector;
+ parent[_mColumns._colObj] = _getObjVec( selector );
+ _store->erase(row);
+ }
+ }
+
+ else if (selector[0] == '.') {
+ // 'class' selector... remove value to class attribute of selected objects.
+
+ std::vector<SPObject *> objVec = row[_mColumns._colObj]; // Just one
+
+ // Get first class (split on white space or comma)
+ std::vector<Glib::ustring> tokens = Glib::Regex::split_simple("[,\\s]+", selector);
+ Glib::ustring className = tokens[0];
+ className.erase(0,1); // Erase '.'
+
+ // Erase class name from 'class' attribute.
+ Glib::ustring classAttr = objVec[0]->getRepr()->attribute("class");
+ auto i = classAttr.find( className );
+ if (i != Glib::ustring::npos) {
+ classAttr.erase(i, className.length());
+ }
+ if (i != Glib::ustring::npos && classAttr[i] == ' ') {
+ classAttr.erase(i, 1);
+ }
+ objVec[0]->getRepr()->setAttribute("class", classAttr);
+
+ parent[_mColumns._colObj] = _getObjVec( selector );
+ _store->erase(row);
+ }
+
+ else {
+ // Do nothing for element selectors.
+ // std::cout << " Element selector... doing nothing!" << std::endl;
+ }
}
}
- return NULL;
+
+ _writeStyleElement();
+
}
+
/**
- * @brief StyleDialog::_setClassAttribute
+ * @brief StyleDialog::_getIdList
* @param sel
- * @return This function returns the ids of objects selected which are passed
- * to entrybox.
+ * @return This function returns a comma seperated list of ids for objects in input vector.
+ * It is used in creating an 'id' selector. It relies on objects having 'id's.
*/
-std::string StyleDialog::_setClassAttribute(std::vector<SPObject*> sel)
+Glib::ustring StyleDialog::_getIdList(std::vector<SPObject*> sel)
{
- std::string str = "";
- for ( unsigned i = 0; i < sel.size(); ++i ) {
- SPObject *obj = sel.at(i);
- str = str + "#" + std::string(obj->getId()) + " ";
+ 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;
}
/**
- * @brief StyleDialog::_getSelectorVec
- * @return selVec
- * This function returns a vector whose key is the style selector name and value
- * is the style properties. All style selectors are extracted from svg:style
- * element. _newDrawing is flag is set to false check if an existing drawing is
- * opened.
+ * @brief StyleDialog::_getObjVec
+ * @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<StyleDialog::InkSelector> StyleDialog::_getSelectorVec()
-{
- for (unsigned i = 0; i < _document->getReprRoot()->childCount(); ++i) {
- if (std::string(_document->getReprRoot()->nthChild(i)->name()) == "svg:style") {
- _styleExists = true;
- _newDrawing = false;
- _styleChild = _document->getReprRoot()->nthChild(i);
-
- // Get content from first style element.
- std::string content = _styleChild->firstChild()->content();
-
- // Remove end-of-lines (check it works on Windoze).
- content.erase(std::remove(content.begin(), content.end(), '\n'), content.end());
-
- // 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<std::string> tokens = Glib::Regex::split_simple("[}{]", content);
-
- for (unsigned i = 0; i < tokens.size()-1; i += 2) {
- std::string selectors = tokens[i];
- REMOVE_SPACES(selectors); // Remove leading/trailing spaces
-
- /** Make a list of all objects that selector matches. This is
- * currently limited to simple id, class, and element selectors.
- * Expanding this would take integrating a true CSS parser.
- */
- std::vector<SPObject *>objVec;
-
- // Split selector string into individual selectors (which are comma separated).
- std::vector<std::string> tokens2 = Glib::Regex::split_simple
- ("\\s*,\\s*", selectors );
-
- for(unsigned i = 0; i < tokens2.size(); ++i) {
- std::string token2 = tokens2[i];
-
- // Find objects that match class selector
- if (token2[0] == '.') {
- token2.erase(0,1);
- std::vector<SPObject *> objects = _document
- ->getObjectsByClass(token2);
- objVec.insert(objVec.end(), objects.begin(), objects.end());
- }
+std::vector<SPObject *> StyleDialog::_getObjVec(Glib::ustring selector) {
- // Find objects that match id selector
- else if (token2[0] == '#') {
- token2.erase(0,1);
- SPObject * object = _document->getObjectById(token2);
- if (object) {
- objVec.push_back(object);
- }
- }
+ std::vector<SPObject *> objVec = _document->getObjectsBySelector( selector );
- // Find objects that match element selector
- else {
- std::vector<SPObject *> objects = _document->
- getObjectsByElement(token2);
- objVec.insert(objVec.end(), objects.begin(), objects.end());
- }
- }
+#ifdef DEBUG_STYLEDIALOG
+ std::cout << "StyleDialog::_getObjVec: |" << selector << "|" << std::endl;
+ for (auto& obj: objVec) {
+ std::cout << " " << (obj->getId()?obj->getId():"null") << std::endl;
+ }
+#endif
- std::string values;
- // Check to make sure we do have a value to match selector.
- if ((i+1) < tokens.size()) {
- values = tokens[i+1];
- } else {
- std::cerr << "StyleDialog::_getSelectorVec: Missing values "
- "for last selector!" << std::endl;
+ return objVec;
+}
+
+
+/**
+ * @brief StyleDialog::_insertClass
+ * @param objs: list of objects to insert class
+ * @param class: class to insert
+ * Insert a class name into objects' 'class' attribute.
+ */
+void StyleDialog::_insertClass(const std::vector<SPObject *>& objVec, const Glib::ustring& className) {
+
+ for (auto& obj: objVec) {
+
+ if (!obj->getRepr()->attribute("class")) {
+ // 'class' attribute does not exist, create it.
+ obj->getRepr()->setAttribute("class", className);
+ } else {
+ // 'class' attribute exists, append.
+ Glib::ustring classAttr = obj->getRepr()->attribute("class");
+
+ // Split on white space.
+ std::vector<Glib::ustring> tokens = Glib::Regex::split_simple("\\s+", classAttr);
+ bool add = true;
+ for (auto& token: tokens) {
+ if (token == className) {
+ add = false; // Might be useful to still add...
+ break;
}
+ }
+ if (add) {
+ obj->getRepr()->setAttribute("class", classAttr + " " + className );
+ }
+ }
+ }
+ }
+
+
+/**
+ * @brief StyleDialog::_selectObjects
+ * @param eventX
+ * @param eventY
+ * This function selects objects in the drawing corresponding to the selector
+ * selected in the treeview.
+ */
+void StyleDialog::_selectObjects(int eventX, int eventY)
+{
+#ifdef DEBUG_STYLEDIALOG
+ std::cout << "StyleDialog::_selectObjects: " << eventX << ", " << eventY << std::endl;
+#endif
- _selectorValue = selectors + "{" + values + "}\n";
- inkSelector._selector = selectors;
- inkSelector._matchingObjs = objVec;
- inkSelector._xmlContent = _selectorValue;
- _selectorVec.push_back(inkSelector);
+ _desktop->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();
+ std::vector<SPObject *> objVec = row[_mColumns._colObj];
+ for (unsigned i = 0; i < objVec.size(); ++i) {
+ SPObject *obj = objVec[i];
+ _desktop->selection->add(obj);
+ }
}
}
}
- return _selectorVec;
}
+
/**
- * @brief StyleDialog::_populateTree
- * @param _selVec
- * This function populates the treeview with selectors available in the
- * stylesheet.
+ * @brief StyleDialog::_addSelector
+ *
+ * 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.
*/
-std::string StyleDialog::_populateTree(std::vector<InkSelector> _selVec)
+void StyleDialog::_addSelector()
{
- _selectorVec = _selVec;
- std::string selectorValue;
+#ifdef DEBUG_STYLEDIALOG
+ std::cout << "StyleDialog::_addSelector: Entrance" << std::endl;
+#endif
- for(unsigned it = 0; it < _selectorVec.size(); ++it) {
- Gtk::TreeModel::Row row = *(_store->append());
- row[_mColumns._selectorLabel] = _selectorVec[it]._selector;
- row[_mColumns._colAddRemove] = true;
- row[_mColumns._colObj] = _selectorVec[it]._matchingObjs;
- std::string selValue = _selectorVec[it]._xmlContent;
- selectorValue.append(selValue.c_str());
+ // Store list of selected elements on desktop (not to be confused with selector).
+ Inkscape::Selection* selection = _desktop->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() );
+ textDialogPtr->get_vbox()->pack_start(*textEditPtr, Gtk::PACK_SHRINK);
+
+ Gtk::Label *textLabelPtr = manage ( new Gtk::Label(
+ _("Invalid entry: Not an id (#), class (.), or element CSS selector.")
+ ) );
+ textDialogPtr->get_vbox()->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 (_desktop->getSelection()->isEmpty()) {
+ textEditPtr->set_text(".Class1");
+ } else {
+ textEditPtr->set_text(_getIdList(objVec));
}
- if (_selectorVec.size() > 0) {
+ 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();
+ 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();
+ Glib::ustring firstWord = selectorValue.substr(0, selectorValue.find(" "));
+
del->set_sensitive(true);
+
+ if (selectorValue[0] == '.' ||
+ selectorValue[0] == '#' ||
+ selectorValue[0] == '*' ||
+ SPAttributeRelSVG::isSVGElement( firstWord ) ) {
+ invalid = false;
+ } else {
+ textLabelPtr->show();
+ }
}
+ delete textDialogPtr;
+
+ // ==== Handle response ====
- return selectorValue;
+ // If class selector, add selector name to class attribute for each object
+ if (selectorValue[0] == '.') {
+
+ Glib::ustring className = selectorValue;
+ className.erase(0,1);
+ _insertClass(objVec, className);
+ }
+
+ // Generate a new object vector (we could have an element selector,
+ // the user could have edited the id selector list, etc.).
+ objVec = _getObjVec( selectorValue );
+
+ // Add entry to GUI tree
+ Gtk::TreeModel::Row row = *(_store->append());
+ row[_mColumns._colSelector] = selectorValue;
+ row[_mColumns._colIsSelector] = true;
+ row[_mColumns._colObj] = objVec;
+
+ // 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._colIsSelector] = false;
+ childrow[_mColumns._colObj] = std::vector<SPObject *>(1, obj);
+ }
+
+ // Add entry to style element
+ _writeStyleElement();
+}
+
+/**
+ * @brief StyleDialog::_delSelector
+ * This function deletes selector when '-' at the bottom is clicked.
+ * Note: If deleting a class selector, class attributes are NOT changed.
+ */
+void StyleDialog::_delSelector()
+{
+#ifdef DEBUG_STYLEDIALOG
+ std::cout << "StyleDialog::_delSelector" << std::endl;
+#endif
+ Glib::RefPtr<Gtk::TreeSelection> refTreeSelection = _treeView.get_selection();
+ Gtk::TreeModel::iterator iter = refTreeSelection->get_selected();
+ if (iter) {
+ Gtk::TreeModel::Row row = *iter;
+ _updating = true;
+ _store->erase(iter);
+ _updating = false;
+ _writeStyleElement();
+ }
}
/**
* @brief StyleDialog::_handleButtonEvent
* @param event
* @return
- * This function handles the event when '+' button in front of a selector name
- * is clicked. The selected objects (if any) is added to the selector as a child
- * in the treeview.
+ * 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 StyleDialog::_handleButtonEvent(GdkEventButton *event)
{
- if (event->type == GDK_BUTTON_PRESS && event->button == 1) {
+#ifdef DEBUG_STYLEDIALOG
+ std::cout << "StyleDialog::_handleButtonEvent: Entrance" << std::endl;
+#endif
+ if (event->type == GDK_BUTTON_RELEASE && event->button == 1) {
Gtk::TreeViewColumn *col = 0;
Gtk::TreeModel::Path path;
int x = static_cast<int>(event->x);
@@ -557,119 +885,16 @@ bool StyleDialog::_handleButtonEvent(GdkEventButton *event)
int y2 = 0;
if (_treeView.get_path_at_pos(x, y, path, col, x2, y2)) {
if (col == _treeView.get_column(0)) {
- Glib::RefPtr<Gtk::TreeSelection> refTreeSelection =
- _treeView.get_selection();
- Gtk::TreeModel::iterator iter = refTreeSelection->
- get_selected();
+ Gtk::TreeModel::iterator iter = _store->get_iter(path);
Gtk::TreeModel::Row row = *iter;
- /**
- * This adds child rows to selected rows. If the parent row is
- * a class selector, then the class attribute of object added
- * to child row is appended with class in the parent row. The
- * else below deletes objects from selectors when 'delete' button
- * in front of child row is clicked. The class attribute is updated
- * by removing the parent row's class selector name.
- */
+ // Add or remove objects from a
if (!row.parent()) {
- _selAdd(row);
- }
-
- else {
- std::string sel, key, value;
- std::vector<InkSelector>::iterator it;
- Gtk::TreeModel::Row parentRow = *(row).parent();
- Glib::ustring parentKey = parentRow[_mColumns._selectorLabel];
-
- for (it = _selectorVec.begin(); it != _selectorVec.end(); ++it) {
- sel = (*it)._xmlContent;
- REMOVE_SPACES(sel);
- if (!sel.empty()) {
- key = strtok((char*)sel.c_str(), "{");
- REMOVE_SPACES(key);
- char *temp = strtok(NULL, "}");
- if (strtok(temp, "}") != NULL) {
- value = strtok(temp, "}");
- }
- }
-
- /**
- * @brief matchSelector
- * For id selectors, whenever any child row is deleted,
- * the row label is updated and so is the entry for the
- * selector in style element.
- */
- std::string matchSelector = parentKey;
- REMOVE_SPACES(matchSelector);
- if (key == matchSelector) {
- if (key[0] == '#') {
- std::string s = parentKey;
- Glib::ustring toDelRow = row[_mColumns._selectorLabel];
- std::string toDelKey = toDelRow;
- std::size_t idFound = s.find(toDelKey);
- if (idFound != std::string::npos) {
- if (idFound == 0) {
- s.erase(idFound, toDelKey.length()+1);
- parentKey = s;
- parentRow[_mColumns._selectorLabel] = parentKey;
- (*it)._xmlContent.erase(idFound, toDelKey.length());
- }
- else {
- s.erase(idFound-2, toDelKey.length()+2);
- parentKey = s;
- parentRow[_mColumns._selectorLabel] = parentKey;
- (*it)._xmlContent.erase(idFound-2, toDelKey.
- length()+2);
- }
- }
- }
- }
-
- if (parentKey[0] == '.') {
- std::vector<SPObject *> objVec = row[_mColumns._colObj];
- for (unsigned i = 0; i < objVec.size(); ++i) {
- SPObject *obj = objVec[i];
- std::string classAttr = std::string(obj->getRepr()
- ->attribute("class"));
- std::size_t found = classAttr.find(parentKey.erase(0,1));
- if (found != std::string::npos) {
- classAttr.erase(found, parentKey.length()+1);
- obj->getRepr()->setAttribute("class", classAttr);
- }
- }
- }
-
- if (parentKey.empty()) {
- (*it)._xmlContent = "";
- }
- }
-
- if (_styleChild) {
- _updateStyleContent();
- }
- else {
- _styleChild = _styleElementNode();
- _updateStyleContent();
- }
-
- _store->erase(row);
-
- /**
- * On continuous deletion of objects (child rows) from the
- * selector (parent row), if the parent row has no child, then
- * the row is erased from the _store. Further if there is no
- * row left in _store, which implies there is no content in
- * XML style element, then the 'svg:style' element is also
- * removed.
- */
- if (parentKey.empty()) {
- _store->erase(parentRow);
- }
-
- if (parentKey.empty() && _store->children().empty()) {
- _document->getReprRoot()->removeChild(_styleChild);
- _styleExists = false;
- }
+ // Add selected objects to selector.
+ _addToSelector(row);
+ } else {
+ // Remove object from selector
+ _removeFromSelector(row);
}
}
}
@@ -678,274 +903,169 @@ bool StyleDialog::_handleButtonEvent(GdkEventButton *event)
}
/**
- * @brief StyleDialog::_selAdd
- * @param row
- * This routine is called when an object is added to a selector by clicking on
- * '+' in front of the row with selector's label.
+ * @brief StyleDialog::_updateCSSPanel
+ * Updates CSS panel according to row in Style panel.
*/
-void StyleDialog::_selAdd(Gtk::TreeModel::Row row)
+void StyleDialog::_updateCSSPanel()
{
- Glib::ustring selectorName;
- Gtk::TreeModel::Row childrow;
- Inkscape::Selection* selection = _desktop->getSelection();
- std::vector<SPObject *> sel = std::vector<SPObject *>
- (selection->objects().begin(), selection->objects().end());
- for (auto& obj: selection->objects()) {
- if (*row) {
- if (_selectorVec.size() != 0) {
- childrow = *(_store->append(row->children()));
- childrow[_mColumns._selectorLabel] = "#" +
- std::string(obj->getId());
- childrow[_mColumns._colAddRemove] = false;
- childrow[_mColumns._colObj] = sel;
- Glib::ustring key = row[_mColumns._selectorLabel];
- if (key[0] == '.') {
- if (!obj->getRepr()->attribute("class")) {
- obj->setAttribute("class", key.erase(0,1));
- }
- else {
- if (obj->getRepr()->attribute("class") != key
- .erase(0,1)) {
- obj->setAttribute("class", std::string
- (obj->getRepr()->
- attribute("class"))
- + " " + key
- .erase(0,1));
- }
- }
- }
- }
- selectorName = row[_mColumns._selectorLabel];
- }
+#ifdef DEBUG_STYLEDIALOG
+ std::cout << "StyleDialog::_updateCSSPanel" << std::endl;
+#endif
+ _updating = true;
- /**
- * If the object's parent row is a class selector, then
- * there are no changes in style element except the class
- * attribute is updated. For the id selector cases, XML
- * content's style element is updated.
- */
- REMOVE_SPACES(selectorName);
- std::vector<InkSelector>::iterator it;
- for (it = _selectorVec.begin(); it != _selectorVec.end(); ++it) {
- std::string sel, key, value;
- sel = (*it)._xmlContent;
- REMOVE_SPACES(sel);
- if (!sel.empty()) {
- key = strtok((char*)sel.c_str(), "{");
- REMOVE_SPACES(key);
- char *temp = strtok(NULL, "}");
- if (strtok(temp, "}") != NULL) {
- value = strtok(temp, "}");
- }
- }
-
- Glib::ustring selectedRowLabel = row[_mColumns._selectorLabel];
- std::string matchSelector = selectedRowLabel;
- REMOVE_SPACES(matchSelector);
- if (key == matchSelector) {
- REMOVE_SPACES((*it)._selector);
- if (selectorName[0] == '#') {
- if ("#" + std::string(obj->getId()) != selectorName) {
- inkSelector._selector = (*it)._selector;
- inkSelector._selector.append(", #" + std::string(obj->getId()));
- inkSelector._xmlContent = inkSelector._selector + "{" + value + "}\n";
- row[_mColumns._selectorLabel] = selectorName + ", " +
- childrow[_mColumns._selectorLabel];
- }
- }
- else if (selectorName[0] == '.') {
- inkSelector._xmlContent = (*it)._selector + "{" + value + "}\n";
- }
+ // This should probably be in a member function of CSSDialog.
+ _cssPane->_store->clear();
- it = _selectorVec.erase(it);
- it = _selectorVec.insert(it, inkSelector);
+ Glib::RefPtr<Gtk::TreeSelection> refTreeSelection = _treeView.get_selection();
+ Gtk::TreeModel::iterator iter = refTreeSelection->get_selected();
+ if (iter) {
+ Gtk::TreeModel::Row row = *iter;
+ Glib::ustring properties;
+ if (row[_mColumns._colIsSelector]) {
+ properties = row[_mColumns._colProperties];
+ _objObserver.set( NULL );
+ } else {
+ std::vector<SPObject *> objects = row[_mColumns._colObj];
+ _objObserver.set( objects[0] );
+ if (objects[0] && objects[0]->getAttribute("style") != NULL) {
+ properties = objects[0]->getAttribute("style");
+ }
+ }
+ REMOVE_SPACES(properties); // Remove leading/trailing spaces
+
+ std::vector<Glib::ustring> tokens =
+ Glib::Regex::split_simple("\\s*;\\s*", properties);
+ for (auto& token: tokens) {
+ if (!token.empty()) {
+ _cssPane->_propRow = *(_cssPane->_store->append());
+ _cssPane->_propRow[_cssPane->_cssColumns._colUnsetProp] = false;
+ _cssPane->_propRow[_cssPane->_cssColumns._propertyLabel] = token;
}
}
}
- if (_styleElementNode()) {
- _styleChild = _styleElementNode();
- _updateStyleContent();
- }
- else if (_styleExists && !_newDrawing) {
- _updateStyleContent();
- }
+
+ _updating = false;
}
+
/**
- * @brief StyleDialog::_selectObjects
+ * @brief StyleDialog::_buttonEventsSelectObjs
* @param event
- * This function detects single or double click on a selector in any row. Single
- * click on a selector selects the matching objects. A double click on any
- * selector selects the matching objects as well as will open CSS dialog. It
- * calls _selectObjects to add objects to selection.
+ * 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 StyleDialog::_buttonEventsSelectObjs(GdkEventButton* event )
{
- if (event->type == GDK_BUTTON_PRESS && event->button == 1) {
+#ifdef DEBUG_STYLEDIALOG
+ std::cout << "StyleDialog::_buttonEventsSelectObjs" << std::endl;
+#endif
+ _updating = true;
+
+ 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);
+ //}
+ //else if (event->type == GDK_2BUTTON_PRESS && event->button == 1) {
+ //int x = static_cast<int>(event->x);
+ //int y = static_cast<int>(event->y);
+ //_selectObjects(x, y);
+
+ _updateCSSPanel();
}
- else if (event->type == GDK_2BUTTON_PRESS && event->button == 1) {
- int x = static_cast<int>(event->x);
- int y = static_cast<int>(event->y);
- _selectObjects(x, y);
+ _updating = false;
+}
- //Open CSS dialog here.
- if (!_cssPane->get_visible()) {
- _paned.pack2(*_cssPane, Gtk::SHRINK);
- _cssPane->show_all();
- }
- Glib::RefPtr<Gtk::TreeSelection> refTreeSelection = _treeView.get_selection();
- Gtk::TreeModel::iterator iter = refTreeSelection->get_selected();
- if (iter) {
- Gtk::TreeModel::Row row = *iter;
- std::string sel, key, value;
- std::vector<InkSelector>::iterator it;
- for (it = _selectorVec.begin(); it != _selectorVec.end(); ++it) {
- sel = (*it)._xmlContent;
- REMOVE_SPACES(sel);
- if (!sel.empty()) {
- key = strtok((char*)sel.c_str(), "{");
- REMOVE_SPACES(key);
- char *temp = strtok(NULL, "}");
- if (strtok(temp, "}") != NULL) {
- value = strtok(temp, "}");
- }
- }
+/**
+ * @brief StyleDialog::_selectRow
+ * 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::_selectRow(Selection */*sel*/)
+{
+#ifdef DEBUG_STYLEDIALOG
+ std::cout << "StyleDialog::_selectRow: updating: " << (_updating?"true":"false") << std::endl;
+#endif
+ if (_updating) return; // Avoid updating if we have set row via dialog.
- Glib::ustring selectedRowLabel = row[_mColumns._selectorLabel];
- std::string matchSelector = selectedRowLabel;
- REMOVE_SPACES(matchSelector);
-
- if (key == matchSelector) {
- _cssPane->_store->clear();
- std::stringstream ss(value);
- std::string token;
- std::size_t found = value.find(";");
- if (found!=std::string::npos) {
- while(std::getline(ss, token, ';')) {
- REMOVE_SPACES(token);
- if (!token.empty()) {
- _cssPane->_propRow = *(_cssPane->_store->append());
- _cssPane->_propRow[_cssPane->_cssColumns._colUnsetProp] = false;
- _cssPane->_propRow[_cssPane->_cssColumns._propertyLabel] = token;
- _cssPane->_propCol->add_attribute(_cssPane->_textRenderer
- ->property_text(),
- _cssPane->_cssColumns
- ._propertyLabel);
- }
- }
- }
+ Inkscape::Selection* selection = _desktop->getSelection();
+ if (!selection->isEmpty()) {
+ SPObject *obj = selection->objects().back();
+
+ Gtk::TreeModel::Children children = _store->children();
+ for(Gtk::TreeModel::Children::iterator iter = children.begin();
+ iter != children.end(); ++iter) {
+
+ Gtk::TreeModel::Row row = *iter;
+ std::vector<SPObject *> objVec = row[_mColumns._colObj];
+ for (unsigned i = 0; i < objVec.size(); ++i) {
+ if (obj->getId() == objVec[i]->getId()) {
+ _treeView.get_selection()->select(row);
+ _updateCSSPanel();
+ return;
}
}
}
- else {
- _cssPane->_store->clear();
- _cssPane->hide();
- }
-
- _cssPane->_textRenderer->signal_edited().connect(sigc::mem_fun(*this,
- &StyleDialog::
- _handleEdited));
- _cssPane->_treeView.signal_button_press_event().connect(sigc::mem_fun
- (*this, &StyleDialog::
- _delProperty),
- false);
}
+
+ // Selection empty or no row matches.
+ _treeView.get_selection()->unselect_all();
+ _updateCSSPanel();
}
+
/**
* @brief StyleDialog::_selChanged
- * When no row in _treeView of Style Dialog is selected, the _cssPane is hidden.
*/
void StyleDialog::_selChanged() {
- if (_treeView.get_selection()->count_selected_rows() == 0) {
- _cssPane->hide();
- }
+#ifdef DEBUG_STYLEDIALOG
+ std::cout << "StyleDialog::_selChanged" << std::endl;
+#endif
+}
+
+
+void StyleDialog::_objChanged() {
+#ifdef DEBUG_STYLEDIALOG
+ std::cout << "StyleDialog::_objChanged" << std::endl;
+#endif
+ if (_updating) return;
+ _updateCSSPanel();
}
+
/**
* @brief StyleDialog::_handleEdited
* @param path
* @param new_text
- * This function edits CSS properties of the selector chosen. new_text is used
- * to update the property in XML repr. The value from selected selector is
- * obtained and modified as per value of new_text. If a new property is added,
- * value is appended with new_text. Later _updateStyleContent() is called to
- * update XML repr and hence changes are reflected in the drawing too.
+ * Update trees when new text is entered into a CSS dialog row.
*/
void StyleDialog::_handleEdited(const Glib::ustring& path, const Glib::ustring& new_text)
{
+#ifdef DEBUG_STYLEDIALOG
+ std::cout << "StyleDialog::_handleEdited: path: " << path
+ << " new_text: " << new_text << std::endl;
+#endif
+
Gtk::TreeModel::iterator iterCss = _cssPane->_treeView.get_model()->get_iter(path);
if (iterCss) {
Gtk::TreeModel::Row row = *iterCss;
row[_cssPane->_cssColumns._propertyLabel] = new_text;
- _cssPane->_editedProp = new_text;
}
- // Selected selector row is obtained here to get corresponding key and value.
+ Glib::ustring properties;
+ for (auto& crow: _cssPane->_store->children()) {
+ properties = properties + crow[_cssPane->_cssColumns._propertyLabel] + "; ";
+ }
+
+ // Update selector data.
Glib::RefPtr<Gtk::TreeSelection> refTreeSelection = _treeView.get_selection();
Gtk::TreeModel::iterator iter = refTreeSelection->get_selected();
if (iter) {
Gtk::TreeModel::Row row = *iter;
- std::string sel, key, value;
- std::vector<InkSelector>::iterator it;
- for (it = _selectorVec.begin(); it != _selectorVec.end(); ++it) {
- sel = (*it)._xmlContent;
- REMOVE_SPACES(sel);
- if (!sel.empty()) {
- key = strtok((char*)sel.c_str(), "{");
- REMOVE_SPACES(key);
- char *temp = strtok(NULL, "}");
- if (strtok(temp, "}") != NULL) {
- value = strtok(temp, "}");
- }
- }
-
- Glib::ustring selectedRowLabel = row[_mColumns._selectorLabel];
- std::string matchSelector = selectedRowLabel;
- REMOVE_SPACES(matchSelector);
-
- if (key == matchSelector) {
- /** If a new property is added, existing value is appended with new
- * property, else replacements in value are done in the 'else' block.
- */
- if (_cssPane->_newProperty) {
- if (!new_text.empty()) {
- value.append((new_text + ";").c_str());
- _cssPane->_propCol->add_attribute(_cssPane->_textRenderer
- ->property_text(),
- _cssPane->_cssColumns
- ._propertyLabel);
- _cssPane->_newProperty = false;
- }
- }
- else {
- std::stringstream ss(value);
- std::string token, editedToken;
- std::size_t found = value.find(";");
- if (found!=std::string::npos) {
- while(std::getline(ss, token, ';')) {
- REMOVE_SPACES(token);
- if (!token.empty()) {
- if (token.substr(0, token.find(":")) == _cssPane
- ->_editedProp.substr(0, _cssPane->_editedProp
- .find(":"))) {
- editedToken = _cssPane->_editedProp;
- size_t startPos = value.find(token);
- value.replace(startPos, token.length(), editedToken);
- }
- }
- }
- }
- }
- value.erase(std::remove(value.begin(), value.end(), '\n'), value.end());
- (*it)._xmlContent = key + "{" + value + "}\n";
- _updateStyleContent();
- }
- }
+ row[_mColumns._colProperties] = properties;
+ _writeStyleElement();
}
}
@@ -953,14 +1073,15 @@ void StyleDialog::_handleEdited(const Glib::ustring& path, const Glib::ustring&
* @brief StyleDialog::_delProperty
* @param event
* @return
- * This function deletes property when '-' in front of property in CSS panel is
- * clicked. The property row is deleted from CSS panel and XML repr is updated.
- * toDelProperty is the property to be deleted which is looked in 'value' and is
- * erased from 'value'.
+ * Delete a property from the CSS dialog and then update trees.
*/
bool StyleDialog::_delProperty(GdkEventButton *event)
{
- if (event->type == GDK_BUTTON_PRESS && event->button == 1) {
+#ifdef DEBUG_STYLEDIALOG
+ std::cout << "StyleDialog::_delProperty" << std::endl;
+#endif
+
+ if (event->type == GDK_BUTTON_RELEASE && event->button == 1) {
Gtk::TreeViewColumn *col = 0;
Gtk::TreeModel::Path path;
int x = static_cast<int>(event->x);
@@ -971,42 +1092,33 @@ bool StyleDialog::_delProperty(GdkEventButton *event)
Glib::ustring toDelProperty;
if (_cssPane->_treeView.get_path_at_pos(x, y, path, col, x2, y2)) {
if (col == _cssPane->_treeView.get_column(0)) {
- Gtk::TreeModel::iterator cssIter = _cssPane->_treeView.get_selection()
- ->get_selected();
+ Gtk::TreeModel::iterator cssIter =
+ _cssPane->_treeView.get_selection()->get_selected();
if (cssIter) {
- cssRow = *cssIter;
- toDelProperty = cssRow[_cssPane->_cssColumns._propertyLabel];
- }
+ _cssPane->_store->erase(cssIter);
- Gtk::TreeModel::iterator iter = _treeView.get_selection()->get_selected();
- if (iter) {
- Gtk::TreeModel::Row row = *iter;
- std::string sel, key, value;
- std::vector<InkSelector>::iterator it;
- for (it = _selectorVec.begin(); it != _selectorVec.end(); ++it) {
- sel = (*it)._xmlContent;
- REMOVE_SPACES(sel);
- if (!sel.empty()) {
- key = strtok((char*)sel.c_str(), "{");
- REMOVE_SPACES(key);
- char *temp = strtok(NULL, "}");
- if (strtok(temp, "}") != NULL) {
- value = strtok(temp, "}");
- }
- }
+ Glib::ustring properties;
+ for (auto& crow: _cssPane->_store->children()) {
+ properties = properties + crow[_cssPane->_cssColumns._propertyLabel] + "; ";
+ }
- Glib::ustring selectedRowLabel = row[_mColumns._selectorLabel];
- std::string matchSelector = selectedRowLabel;
- REMOVE_SPACES(matchSelector);
-
- if (key == matchSelector) {
- std::size_t found = value.find(toDelProperty);
- if (found!=std::string::npos) {
- if (!toDelProperty.empty()) {
- value.erase(found, toDelProperty.length()+1);
- (*it)._xmlContent = key + "{" + value + "}\n";
- _updateStyleContent();
- _cssPane->_store->erase(cssRow);
+ // Update selector data.
+ Glib::RefPtr<Gtk::TreeSelection> refTreeSelection = _treeView.get_selection();
+ Gtk::TreeModel::iterator iter = refTreeSelection->get_selected();
+ if (iter) {
+ Gtk::TreeModel::Row row = *iter;
+ row[_mColumns._colProperties] = properties;
+ _writeStyleElement();
+
+ // Update style attribute
+ if (!row[_mColumns._colIsSelector]) {
+ std::vector<SPObject *> objects = row[_mColumns._colObj];
+ if (objects[0]) {
+ Glib::ustring properties = row[_mColumns._colProperties];
+ if (properties.empty()) {
+ objects[0]->setAttribute("style", NULL);
+ } else {
+ objects[0]->setAttribute("style", properties);
}
}
}
@@ -1018,99 +1130,36 @@ bool StyleDialog::_delProperty(GdkEventButton *event)
return false;
}
-/**
- * @brief StyleDialog::_selectObjects
- * @param eventX
- * @param eventY
- * This function selects objects in the drawing corresponding to the selector
- * selected in the treeview.
- */
-void StyleDialog::_selectObjects(int eventX, int eventY)
-{
- _desktop->selection->clear();
- Gtk::TreeViewColumn *col = _treeView.get_column(1);
- Gtk::TreeModel::Path path;
- int x = eventX;
- int y = eventY;
- int x2 = 0;
- int y2 = 0;
- if (_treeView.get_path_at_pos(x, y, 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();
- std::vector<SPObject *> objVec = row[_mColumns._colObj];
- for (unsigned i = 0; i < objVec.size(); ++i) {
- SPObject *obj = objVec[i];
- _desktop->selection->add(obj);
- }
- if (children) {
- _checkAllChildren(children);
- }
- }
- }
- }
-}
/**
- * @brief StyleDialog::_checkAllChildren
- * @param children
- * This function iterates children of the row selected in treeview and selects
- * the objects corresponding to any selector in child rows.
+ * @brief StyleDialog::_styleButton
+ * @param btn
+ * @param iconName
+ * @param tooltip
+ * Set the style of '+' and '-' buttons at the bottom of dialog.
*/
-void StyleDialog::_checkAllChildren(Gtk::TreeModel::Children& children)
+void StyleDialog::_styleButton(Gtk::Button& btn, char const* iconName,
+ char const* tooltip)
{
- for (Gtk::TreeModel::Children::iterator iter = children.begin();
- iter!= children.end(); ++iter) {
- Gtk::TreeModel::Row childrow = *iter;
- std::vector<SPObject *> objVec = childrow[_mColumns._colObj];
- for (unsigned i = 0; i < objVec.size(); ++i) {
- SPObject *obj = objVec[i];
- _desktop->selection->add(obj);
- }
- }
+ GtkWidget *child = sp_icon_new(Inkscape::ICON_SIZE_SMALL_TOOLBAR, iconName);
+ gtk_widget_show(child);
+ btn.add(*manage(Glib::wrap(child)));
+ btn.set_relief(Gtk::RELIEF_NONE);
+ btn.set_tooltip_text (tooltip);
}
-/**
- * @brief StyleDialog::_selectRow
- * This function selects the rows in treeview corresponding to an object selected
- * in the drawing.
- */
-void StyleDialog::_selectRow(Selection */*sel*/)
-{
- SPObject *obj = NULL;
- if (!_desktop->selection->isEmpty()) {
- Inkscape::Selection* selection = _desktop->getSelection();
- std::vector<SPObject*> selected = std::vector<SPObject *>
- (selection->objects().begin(), selection->objects().end());
- obj = selected.back();
- }
-
- /**
- * If obj has some SPObject, then it is added to desktop's selection. If a
- * row in treeview has children, those rows are checked too against selected
- * object's id. If an object which is not present in any selector is selected,
- * the treeview's selections are unselected.
- */
- if (obj != NULL) {
- Gtk::TreeModel::Children children = _store->children();
- for(Gtk::TreeModel::Children::iterator iter = children.begin();
- iter != children.end(); ++iter) {
- Gtk::TreeModel::Row row = *iter;
- std::vector<SPObject *> objVec = row[_mColumns._colObj];
- for (unsigned i = 0; i < objVec.size(); ++i) {
- if (obj->getId() == objVec[i]->getId()) {
- _treeView.get_selection()->select(row);
- }
- }
- }
- }
- else {
- _treeView.get_selection()->unselect_all();
- }
-}
} // 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 :