diff options
| author | Shlomi Fish <shlomif@shlomifish.org> | 2017-02-06 16:50:07 +0000 |
|---|---|---|
| committer | Shlomi Fish <shlomif@shlomifish.org> | 2017-02-06 16:50:07 +0000 |
| commit | 1232596134bcba8d19f2809ffdc84e3b5c33d3b3 (patch) | |
| tree | 2fcb91d6fe9ef47a85ba3f73be10dc5dc7ee10a4 /src/ui/dialog/styledialog.cpp | |
| parent | Merged. (diff) | |
| parent | Remove some unneeded < C++11 fallback code (diff) | |
| download | inkscape-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.cpp | 1116 |
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 |
