summaryrefslogtreecommitdiffstats
path: root/src/ui/dialog/styledialog.cpp
diff options
context:
space:
mode:
authorShlomi Fish <shlomif@shlomifish.org>2017-02-06 16:50:07 +0000
committerShlomi Fish <shlomif@shlomifish.org>2017-02-06 16:50:07 +0000
commit1232596134bcba8d19f2809ffdc84e3b5c33d3b3 (patch)
tree2fcb91d6fe9ef47a85ba3f73be10dc5dc7ee10a4 /src/ui/dialog/styledialog.cpp
parentMerged. (diff)
parentRemove some unneeded < C++11 fallback code (diff)
downloadinkscape-1232596134bcba8d19f2809ffdc84e3b5c33d3b3.tar.gz
inkscape-1232596134bcba8d19f2809ffdc84e3b5c33d3b3.zip
Merged.
(bzr r15369.1.18)
Diffstat (limited to 'src/ui/dialog/styledialog.cpp')
-rw-r--r--src/ui/dialog/styledialog.cpp1116
1 files changed, 1116 insertions, 0 deletions
diff --git a/src/ui/dialog/styledialog.cpp b/src/ui/dialog/styledialog.cpp
new file mode 100644
index 000000000..5246290b4
--- /dev/null
+++ b/src/ui/dialog/styledialog.cpp
@@ -0,0 +1,1116 @@
+/** @file
+ * @brief A dialog for CSS selectors
+ */
+/* Authors:
+ * Kamalpreet Kaur Grewal
+ *
+ * Copyright (C) Kamalpreet Kaur Grewal 2016 <grewalkamal005@gmail.com>
+ *
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ */
+
+#include "styledialog.h"
+#include "ui/widget/addtoicon.h"
+#include "widgets/icon.h"
+#include "verbs.h"
+#include "sp-object.h"
+#include "selection.h"
+#include "xml/attribute-record.h"
+#include <glibmm/regex.h>
+
+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 {
+
+/**
+ * @brief StyleDialog::_styleButton
+ * @param btn
+ * @param iconName
+ * @param tooltip
+ * This function sets the style of '+' and '-' buttons at the bottom of dialog.
+ */
+void StyleDialog::_styleButton(Gtk::Button& btn, char const* iconName,
+ char const* tooltip)
+{
+ 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);
+}
+
+/**
+ * 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.
+ */
+StyleDialog::StyleDialog() :
+ UI::Widget::Panel("", "/dialogs/style", SP_VERB_DIALOG_STYLE),
+ _desktop(0)
+{
+ 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);
+
+ Inkscape::UI::Widget::AddToIcon * addRenderer = manage(
+ new Inkscape::UI::Widget::AddToIcon() );
+ addRenderer->property_active() = true;
+
+ int addCol = _treeView.append_column("type", *addRenderer) - 1;
+
+ Gtk::TreeViewColumn *col = _treeView.get_column(addCol);
+ if ( col ) {
+ col->add_attribute( addRenderer->property_active(), _mColumns._colAddRemove );
+ }
+
+ _treeView.append_column("Selector Name", _mColumns._selectorLabel);
+ _treeView.set_expander_column(*(_treeView.get_column(1)));
+
+ create = manage( new Gtk::Button() );
+ _styleButton(*create, "list-add", "Add a new CSS Selector");
+ 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->set_sensitive(false);
+
+ _mainBox.pack_end(_buttonBox, Gtk::PACK_SHRINK);
+
+ _buttonBox.pack_start(*create, Gtk::PACK_SHRINK);
+ _buttonBox.pack_start(*del, Gtk::PACK_SHRINK);
+
+ _getContents()->pack_start(_paned, Gtk::PACK_EXPAND_WIDGET);
+
+ _targetDesktop = getDesktop();
+ setDesktop(_targetDesktop);
+
+ /**
+ * @brief document
+ * If an existing document is opened, its XML representation is obtained
+ * 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);
+
+ _treeView.signal_button_press_event().connect_notify(sigc::mem_fun
+ (*this, &StyleDialog::
+ _buttonEventsSelectObjs),
+ false);
+
+ _cssPane = new CssDialog;
+
+ _treeView.get_selection()->signal_changed().connect(sigc::mem_fun(*this,
+ &StyleDialog::
+ _selChanged));
+}
+
+/**
+ * @brief StyleDialog::~StyleDialog
+ * Class destructor
+ */
+StyleDialog::~StyleDialog()
+{
+ setDesktop(NULL);
+}
+
+/**
+ * @brief StyleDialog::setDesktop
+ * @param desktop
+ * This function sets the 'desktop' for the Style Dialog.
+ */
+void StyleDialog::setDesktop( SPDesktop* desktop )
+{
+ Panel::setDesktop(desktop);
+ _desktop = Panel::getDesktop();
+ _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.
+ */
+void StyleDialog::_addSelector()
+{
+ 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);
+
+ /**
+ * 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));
+ }
+
+ textDialogPtr->set_size_request(200, 100);
+ textDialogPtr->show_all();
+ int result = textDialogPtr->run();
+
+ /**
+ * @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();
+ }
+ else {
+ _selectorName = ".Class1";
+ }
+
+ del->set_sensitive(true);
+
+ /**
+ * 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";
+ }
+
+ 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));
+ }
+ }
+ }
+ }
+ else {
+ _selectorValue = _selectorName + "{" + "}" + "\n";
+ objExists = false;
+ }
+
+ 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];
+ }
+ break;
+ default:
+ break;
+ }
+
+ /**
+ * 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;
+ }
+ _selAdd(row);
+}
+
+/**
+ * @brief StyleDialog::_updateStyleContent
+ * This function updates the content in style element as new selectors (or
+ * objects) are added/removed.
+ */
+void StyleDialog::_updateStyleContent()
+{
+ std::string styleContent = "";
+ for (unsigned i = 0; i < _selectorVec.size(); ++i) {
+ styleContent = styleContent + _selectorVec[i]._xmlContent;
+ }
+ _styleChild->firstChild()->setContent(styleContent.c_str());
+}
+
+/**
+ * @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.
+ */
+void StyleDialog::_delSelector()
+{
+ 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, "}");
+ }
+ }
+
+ 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;
+ }
+
+ /**
+ * 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();
+ }
+ }
+ }
+}
+
+/**
+ * @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.
+ */
+Inkscape::XML::Node* StyleDialog::_styleElementNode()
+{
+ 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);
+ }
+ }
+ return NULL;
+}
+
+/**
+ * @brief StyleDialog::_setClassAttribute
+ * @param sel
+ * @return This function returns the ids of objects selected which are passed
+ * to entrybox.
+ */
+std::string StyleDialog::_setClassAttribute(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()) + " ";
+ }
+ 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.
+ */
+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());
+ }
+
+ // 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);
+ }
+ }
+
+ // Find objects that match element selector
+ else {
+ std::vector<SPObject *> objects = _document->
+ getObjectsByElement(token2);
+ objVec.insert(objVec.end(), objects.begin(), objects.end());
+ }
+ }
+
+ 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;
+ }
+
+ _selectorValue = selectors + "{" + values + "}\n";
+ inkSelector._selector = selectors;
+ inkSelector._matchingObjs = objVec;
+ inkSelector._xmlContent = _selectorValue;
+ _selectorVec.push_back(inkSelector);
+ }
+ }
+ }
+ return _selectorVec;
+}
+
+/**
+ * @brief StyleDialog::_populateTree
+ * @param _selVec
+ * This function populates the treeview with selectors available in the
+ * stylesheet.
+ */
+std::string StyleDialog::_populateTree(std::vector<InkSelector> _selVec)
+{
+ _selectorVec = _selVec;
+ std::string selectorValue;
+
+ 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());
+ }
+
+ if (_selectorVec.size() > 0) {
+ del->set_sensitive(true);
+ }
+
+ return selectorValue;
+}
+
+/**
+ * @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.
+ */
+bool StyleDialog::_handleButtonEvent(GdkEventButton *event)
+{
+ if (event->type == GDK_BUTTON_PRESS && event->button == 1) {
+ Gtk::TreeViewColumn *col = 0;
+ 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)) {
+ Glib::RefPtr<Gtk::TreeSelection> refTreeSelection =
+ _treeView.get_selection();
+ Gtk::TreeModel::iterator iter = refTreeSelection->
+ get_selected();
+ 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.
+ */
+ 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;
+ }
+ }
+ }
+ }
+ }
+ return false;
+}
+
+/**
+ * @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.
+ */
+void StyleDialog::_selAdd(Gtk::TreeModel::Row row)
+{
+ 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];
+ }
+
+ /**
+ * 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";
+ }
+
+ it = _selectorVec.erase(it);
+ it = _selectorVec.insert(it, inkSelector);
+ }
+ }
+ }
+ if (_styleElementNode()) {
+ _styleChild = _styleElementNode();
+ _updateStyleContent();
+ }
+ else if (_styleExists && !_newDrawing) {
+ _updateStyleContent();
+ }
+}
+
+/**
+ * @brief StyleDialog::_selectObjects
+ * @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.
+ */
+void StyleDialog::_buttonEventsSelectObjs(GdkEventButton* event )
+{
+ if (event->type == GDK_BUTTON_PRESS && 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);
+
+ //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, "}");
+ }
+ }
+
+ 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);
+ }
+ }
+ }
+ }
+ }
+ }
+ 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);
+ }
+}
+
+/**
+ * @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();
+ }
+}
+
+/**
+ * @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.
+ */
+void StyleDialog::_handleEdited(const Glib::ustring& path, const Glib::ustring& new_text)
+{
+ 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::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();
+ }
+ }
+ }
+}
+
+/**
+ * @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'.
+ */
+bool StyleDialog::_delProperty(GdkEventButton *event)
+{
+ if (event->type == GDK_BUTTON_PRESS && event->button == 1) {
+ Gtk::TreeViewColumn *col = 0;
+ Gtk::TreeModel::Path path;
+ int x = static_cast<int>(event->x);
+ int y = static_cast<int>(event->y);
+ int x2 = 0;
+ int y2 = 0;
+ Gtk::TreeModel::Row cssRow;
+ 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();
+ if (cssIter) {
+ cssRow = *cssIter;
+ toDelProperty = cssRow[_cssPane->_cssColumns._propertyLabel];
+ }
+
+ 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 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);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ 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.
+ */
+void StyleDialog::_checkAllChildren(Gtk::TreeModel::Children& children)
+{
+ 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);
+ }
+ }
+}
+
+/**
+ * @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