From ddb9f75a21c83c5979e467c5adf1b8a7cdf7b98e Mon Sep 17 00:00:00 2001 From: Tavmjong Bah Date: Wed, 8 Feb 2017 10:36:56 +0100 Subject: Allow dialog to be resizable. (Fixed inheritance.) Also basic housekeeping. (bzr r15496) --- src/ui/dialog/styledialog.cpp | 11 +++++++++++ src/ui/dialog/styledialog.h | 30 ++++++++++++++++++++++-------- 2 files changed, 33 insertions(+), 8 deletions(-) (limited to 'src/ui') diff --git a/src/ui/dialog/styledialog.cpp b/src/ui/dialog/styledialog.cpp index 5246290b4..8d56e10e7 100644 --- a/src/ui/dialog/styledialog.cpp +++ b/src/ui/dialog/styledialog.cpp @@ -1114,3 +1114,14 @@ void StyleDialog::_selectRow(Selection */*sel*/) } // 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 : diff --git a/src/ui/dialog/styledialog.h b/src/ui/dialog/styledialog.h index b03a1d2e1..3cc4349e6 100644 --- a/src/ui/dialog/styledialog.h +++ b/src/ui/dialog/styledialog.h @@ -30,16 +30,16 @@ namespace Dialog { /** * @brief The StyleDialog class - * A list of CSS selectors will show up in this dialog. This dialog allows to - * add and delete selectors. Objects can be added to and removed from the selectors - * in the dialog. Besides, selection of any selector row selects the matching - * objects in the drawing and vice-versa. + * A list of CSS selectors will show up in this dialog. This dialog allows one to + * add and delete selectors. Elements can be added to and removed from the selectors + * in the dialog. Selection of any selector row selects the matching objects in + * the drawing and vice-versa. */ typedef std::pair >, std::string> _selectorVecType; -class StyleDialog : public UI::Widget::Panel -{ +class StyleDialog : public Widget::Panel { + public: StyleDialog(); ~StyleDialog(); @@ -73,8 +73,11 @@ private: class ModelColumns : public Gtk::TreeModel::ColumnRecord { public: - ModelColumns() - { add(_selectorLabel); add(_colAddRemove); add(_colObj); } + ModelColumns() { + add(_selectorLabel); + add(_colAddRemove); + add(_colObj); + } Gtk::TreeModelColumn _selectorLabel; Gtk::TreeModelColumn _colAddRemove; Gtk::TreeModelColumn > _colObj; @@ -115,3 +118,14 @@ private: } // namespace Inkscape #endif // STYLEDIALOG_H + +/* + 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 : -- cgit v1.2.3 From 116c4c4853a080ff7210de2d4e999959c0c0e0ce Mon Sep 17 00:00:00 2001 From: Tavmjong Bah Date: Wed, 8 Feb 2017 13:09:58 +0100 Subject: Add naive selector validation on entry in style dialog. (bzr r15497) --- src/ui/dialog/styledialog.cpp | 64 +++++++++++++++++++++++++++---------------- 1 file changed, 41 insertions(+), 23 deletions(-) (limited to 'src/ui') diff --git a/src/ui/dialog/styledialog.cpp b/src/ui/dialog/styledialog.cpp index 8d56e10e7..7b68cfe53 100644 --- a/src/ui/dialog/styledialog.cpp +++ b/src/ui/dialog/styledialog.cpp @@ -16,8 +16,11 @@ #include "sp-object.h" #include "selection.h" #include "xml/attribute-record.h" +#include "attribute-rel-svg.h" + +#include #include - + using Inkscape::Util::List; using Inkscape::XML::AttributeRecord; @@ -169,45 +172,60 @@ void StyleDialog::_addSelector() * 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); + + 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(".Class1"); + } else { Inkscape::Selection* selection = _desktop->getSelection(); - std::vector selected = std::vector(selection - ->objects().begin(), - selection-> - objects().end()); + std::vector selected = + std::vector(selection->objects().begin(), + selection->objects().end()); textEditPtr->set_text(_setClassAttribute(selected)); } textDialogPtr->set_size_request(200, 100); - textDialogPtr->show_all(); - int result = textDialogPtr->run(); + textEditPtr->show(); + textLabelPtr->hide(); + textDialogPtr->show(); - /** - * @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()) { + int result = -1; + bool invalid = true; + + while (invalid) { + 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" + */ _selectorName = textEditPtr->get_text(); - } - else { - _selectorName = ".Class1"; - } - del->set_sensitive(true); + del->set_sensitive(true); + + if (_selectorName[0] == '.' || + _selectorName[0] == '#' || + SPAttributeRelSVG::isSVGElement( _selectorName ) ) { + invalid = false; + } else { + textLabelPtr->show(); + } + } /** * The selector name objects is set to the text that the user sets in the -- cgit v1.2.3 From bd4bad24c8e2c428e07d8afc5bc1009e9809be1b Mon Sep 17 00:00:00 2001 From: Eduard Braun Date: Fri, 10 Feb 2017 22:26:04 +0100 Subject: Fix encoding/escaping issues with symbols loaded from .vss (Visio stencils) files There were three issues: - When loading a .vss file an SVG document is created internally and the file name is used to derive this document's "title". This could result in illegal characters (e.g. the ampersand "&") in the title making the document fail to load. The title is now properly escaped. - Loading of files with non-ANSI characers in filename failed on Windows. Fix is equivalent to r15421, see also bug #1416949. - The .vss file name is also used to derive symbol IDs, which were not properly sanitized before (i.e. could cause broken hrefs later or even prevent to load the .vss file). Characters that do not match [a-zA-Z0-9_-] are now laways replaced with an underscore "_") which will result in valid IDs in almost all cases Fixed bugs: - https://launchpad.net/bugs/1662465 (bzr r15501) --- src/ui/dialog/symbols.cpp | 47 ++++++++++++++++++++++++++++------------------- 1 file changed, 28 insertions(+), 19 deletions(-) (limited to 'src/ui') diff --git a/src/ui/dialog/symbols.cpp b/src/ui/dialog/symbols.cpp index 4840b897b..c8a11b112 100644 --- a/src/ui/dialog/symbols.cpp +++ b/src/ui/dialog/symbols.cpp @@ -127,7 +127,7 @@ SymbolsDialog::SymbolsDialog( gchar const* prefsPath ) : sigc::connection connSet = symbolSet->signal_changed().connect( sigc::mem_fun(*this, &SymbolsDialog::rebuild)); instanceConns.push_back(connSet); - + ++row; /********************* Icon View **************************/ @@ -342,7 +342,7 @@ void SymbolsDialog::rebuild() { addSymbol->set_sensitive( true ); removeSymbol->set_sensitive( true ); } else { - addSymbol->set_sensitive( false ); + addSymbol->set_sensitive( false ); removeSymbol->set_sensitive( false ); } add_symbols( symbolDocument ); @@ -451,7 +451,14 @@ void SymbolsDialog::iconChanged() { #ifdef WITH_LIBVISIO // Read Visio stencil files -SPDocument* read_vss( gchar* fullname, gchar* filename ) { +SPDocument* read_vss( gchar* fullname, Glib::ustring name ) { + + #ifdef WIN32 + // RVNGFileStream uses fopen() internally which unfortunately only uses ANSI encoding on Windows + // therefore attempt to convert uri to the system codepage + // even if this is not possible the alternate short (8.3) file name will be used if available + fullname = g_win32_locale_filename_from_utf8(fullname); + #endif RVNGFileStream input(fullname); @@ -474,6 +481,12 @@ SPDocument* read_vss( gchar* fullname, gchar* filename ) { return NULL; } + // prepare a valid title for the symbol file + Glib::ustring title = Glib::Markup::escape_text(name); + // prepare a valid id prefix for the symbols (unfortunately libvisio doesn't give us a name) + Glib::RefPtr regex1 = Glib::Regex::create("[^a-zA-Z0-9_-]"); + Glib::ustring id = regex1->replace(name, 0, "_", Glib::REGEX_MATCH_PARTIAL); + Glib::ustring tmpSVGOutput; tmpSVGOutput += "\n"; tmpSVGOutput += "\n"; tmpSVGOutput += " "; - tmpSVGOutput += filename; + tmpSVGOutput += title; tmpSVGOutput += "\n"; tmpSVGOutput += " \n"; - // Create a string we can use for the symbol id (libvisio doesn't give us a name) - std::string sanitized( filename ); - sanitized.erase( sanitized.find_last_of(".vss")-3 ); - sanitized.erase( std::remove_if( sanitized.begin(), sanitized.end(), ispunct ), sanitized.end() ); - std::replace( sanitized.begin(), sanitized.end(), ' ', '_' ); - // std::cout << filename << " |" << sanitized << "|" << std::endl; - // Each "symbol" is in it's own SVG file, we wrap with and merge into one file. for (unsigned i=0; i\n"; @@ -521,12 +527,12 @@ SPDocument* read_vss( gchar* fullname, gchar* filename ) { tmpSVGOutput += " \n"; tmpSVGOutput += "\n"; - + return SPDocument::createNewDocFromMem( tmpSVGOutput.c_str(), strlen( tmpSVGOutput.c_str()), 0 ); } #endif - + /* Hunts preference directories for symbol files */ void SymbolsDialog::get_symbols() { @@ -565,11 +571,14 @@ void SymbolsDialog::get_symbols() { #ifdef WITH_LIBVISIO if( tag.compare( "vss" ) == 0 ) { + // strip extension from filename and use it as name for the symbol set + Glib::ustring name = Glib::ustring(filename); + name = name.erase(name.rfind('.')); - symbol_doc = read_vss( fullname, filename ); + symbol_doc = read_vss( fullname, name ); if( symbol_doc ) { - symbolSets[Glib::ustring(filename)]= symbol_doc; - symbolSet->append(filename); + symbolSets[name]= symbol_doc; + symbolSet->append(name); } } #endif @@ -630,7 +639,7 @@ GSList* SymbolsDialog::symbols_in_doc( SPDocument* symbolDocument ) { } GSList* SymbolsDialog::use_in_doc_recursive (SPObject *r, GSList *l) -{ +{ if ( dynamic_cast(r) ) { l = g_slist_prepend (l, r); @@ -816,7 +825,7 @@ SPDocument* SymbolsDialog::symbols_preview_doc() " xmlns:sodipodi=\"http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd\"" " xmlns:inkscape=\"http://www.inkscape.org/namespaces/inkscape\"" " xmlns:xlink=\"http://www.w3.org/1999/xlink\">" -" " +" " " " " " " " -- cgit v1.2.3 From 9f806b86e925bc6817cb111bc50e244cfa302d4a Mon Sep 17 00:00:00 2001 From: Eduard Braun Date: Fri, 10 Feb 2017 23:04:01 +0100 Subject: Add missing "#include " for previous commit (bzr r15502) --- src/ui/dialog/symbols.cpp | 1 + 1 file changed, 1 insertion(+) (limited to 'src/ui') diff --git a/src/ui/dialog/symbols.cpp b/src/ui/dialog/symbols.cpp index c8a11b112..dcfb7f242 100644 --- a/src/ui/dialog/symbols.cpp +++ b/src/ui/dialog/symbols.cpp @@ -25,6 +25,7 @@ #include #include #include +#include #include #include #include -- cgit v1.2.3 From 9faf941d45ce7d23f5cb0d066bd23346f7e3aedf Mon Sep 17 00:00:00 2001 From: Eduard Braun Date: Sat, 11 Feb 2017 01:16:53 +0100 Subject: Fix a memory leak introduced in r15421 and r15501 respectively (bzr r15505) --- src/ui/dialog/symbols.cpp | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'src/ui') diff --git a/src/ui/dialog/symbols.cpp b/src/ui/dialog/symbols.cpp index dcfb7f242..740ff4f39 100644 --- a/src/ui/dialog/symbols.cpp +++ b/src/ui/dialog/symbols.cpp @@ -463,6 +463,10 @@ SPDocument* read_vss( gchar* fullname, Glib::ustring name ) { RVNGFileStream input(fullname); + #ifdef WIN32 + g_free(fullname); + #endif + if (!libvisio::VisioDocument::isSupported(&input)) { return NULL; } -- cgit v1.2.3 From 38ecca9e4639b8aecc55a4becfc1ed9b2793fadc Mon Sep 17 00:00:00 2001 From: Eduard Braun Date: Mon, 13 Feb 2017 22:37:42 +0100 Subject: AboutBox: Remove superfluous space in website URL (bzr r15516) --- src/ui/dialog/aboutbox.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/ui') diff --git a/src/ui/dialog/aboutbox.cpp b/src/ui/dialog/aboutbox.cpp index 40b948a23..805bfb562 100644 --- a/src/ui/dialog/aboutbox.cpp +++ b/src/ui/dialog/aboutbox.cpp @@ -115,7 +115,7 @@ AboutBox::AboutBox() : Gtk::Dialog(_("About Inkscape")) { Gtk::Label *link = new Gtk::Label(); const gchar *website_link = - " https://www.inkscape.org"; + "https://www.inkscape.org"; link->set_markup(website_link); link->set_alignment(Gtk::ALIGN_END); -- cgit v1.2.3 From db37e627f21d79c6968f681725c7a4dd8de21081 Mon Sep 17 00:00:00 2001 From: Tavmjong Bah Date: Tue, 14 Feb 2017 16:39:13 +0100 Subject: Rewrite and cleanup of style dialog. Still WIP. Missing undo and document tracking. (bzr r15520) --- src/ui/dialog/cssdialog.cpp | 53 +- src/ui/dialog/cssdialog.h | 35 +- src/ui/dialog/styledialog.cpp | 1529 +++++++++++++++++++---------------------- src/ui/dialog/styledialog.h | 93 +-- 4 files changed, 791 insertions(+), 919 deletions(-) (limited to 'src/ui') diff --git a/src/ui/dialog/cssdialog.cpp b/src/ui/dialog/cssdialog.cpp index fa266b012..d0ae822bd 100644 --- a/src/ui/dialog/cssdialog.cpp +++ b/src/ui/dialog/cssdialog.cpp @@ -3,8 +3,10 @@ */ /* Authors: * Kamalpreet Kaur Grewal + * Tavmjong Bah * * Copyright (C) Kamalpreet Kaur Grewal 2016 + * Copyright (C) Tavmjong Bah 2017 * * Released under GNU GPL, read the file 'COPYING' for more information */ @@ -21,23 +23,6 @@ namespace Inkscape { namespace UI { namespace Dialog { -/** - * @brief CssDialog::_styleButton - * @param btn - * @param iconName - * @param tooltip - * This function sets the style of '+'button at the bottom of dialog. - */ -void CssDialog::_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 whose each row corresponds to a CSS property of selector selected. @@ -60,8 +45,7 @@ CssDialog::CssDialog(): _store = Gtk::ListStore::create(_cssColumns); _treeView.set_model(_store); - Inkscape::UI::Widget::AddToIcon * addRenderer = manage(new Inkscape::UI:: - Widget::AddToIcon()); + Inkscape::UI::Widget::AddToIcon * addRenderer = manage(new Inkscape::UI::Widget::AddToIcon()); addRenderer->property_active() = false; int addCol = _treeView.append_column("Unset Property", *addRenderer) - 1; @@ -74,6 +58,10 @@ CssDialog::CssDialog(): int nameColNum = _treeView.append_column("Property", *_textRenderer) - 1; _propCol = _treeView.get_column(nameColNum); + if (_propCol) { + _propCol->add_attribute(_textRenderer->property_text(), + _cssColumns._propertyLabel); + } Gtk::Button* create = manage(new Gtk::Button()); _styleButton(*create, "list-add", "Add a new property"); @@ -83,12 +71,12 @@ CssDialog::CssDialog(): _getContents()->pack_start(_mainBox, Gtk::PACK_EXPAND_WIDGET); - _targetDesktop = getDesktop(); - setDesktop(_targetDesktop); + setDesktop(getDesktop()); create->signal_clicked().connect(sigc::mem_fun(*this, &CssDialog::_addProperty)); } + /** * @brief CssDialog::~CssDialog * Class destructor @@ -98,6 +86,7 @@ CssDialog::~CssDialog() setDesktop(NULL); } + /** * @brief CssDialog::setDesktop * @param desktop @@ -108,16 +97,34 @@ void CssDialog::setDesktop(SPDesktop* desktop) _desktop = desktop; } + +/** + * @brief CssDialog::_styleButton + * @param btn + * @param iconName + * @param tooltip + * This function sets the style of '+'button at the bottom of dialog. + */ +void CssDialog::_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); +} + + /** * @brief CssDialog::_addProperty * This function is a slot to signal_clicked for '+' button at the bottom of CSS * panel. A new row is added, double clicking which text for new property can be - * added. _newProperty is set to true in which case the value is appended. + * added. */ void CssDialog::_addProperty() { _propRow = *(_store->append()); - _newProperty = true; } } // namespace Dialog diff --git a/src/ui/dialog/cssdialog.h b/src/ui/dialog/cssdialog.h index 3bbab5031..1f8b7ca98 100644 --- a/src/ui/dialog/cssdialog.h +++ b/src/ui/dialog/cssdialog.h @@ -3,8 +3,10 @@ */ /* Authors: * Kamalpreet Kaur Grewal + * Tavmjong Bah * * Copyright (C) Kamalpreet Kaur Grewal 2016 + * Copyright (C) Tavmjong Bah 2017 * * Released under GNU GPL, read the file 'COPYING' for more information */ @@ -38,32 +40,39 @@ public: ~CssDialog(); static CssDialog &getInstance() { return *new CssDialog(); } - void setDesktop(SPDesktop* desktop); - class CssColumns : public Gtk::TreeModel::ColumnRecord - { + // Data structure + class CssColumns : public Gtk::TreeModel::ColumnRecord { public: - CssColumns() - { add(_colUnsetProp); add(_propertyLabel); } + CssColumns() { + add(_colUnsetProp); + add(_propertyLabel); + } Gtk::TreeModelColumn _colUnsetProp; Gtk::TreeModelColumn _propertyLabel; }; - - SPDesktop* _desktop; - SPDesktop* _targetDesktop; CssColumns _cssColumns; - Gtk::VBox _mainBox; - Gtk::HBox _buttonBox; + + // TreeView Gtk::TreeView _treeView; Glib::RefPtr _store; - Gtk::ScrolledWindow _scrolledWindow; Gtk::TreeModel::Row _propRow; Gtk::CellRendererText *_textRenderer; Gtk::TreeViewColumn *_propCol; - Glib::ustring _editedProp; - bool _newProperty; + // Widgets + Gtk::VBox _mainBox; + Gtk::ScrolledWindow _scrolledWindow; + Gtk::HBox _buttonBox; + + // Variables - Inkscape + SPDesktop* _desktop; + + // Helper functions + void setDesktop(SPDesktop* desktop); void _styleButton(Gtk::Button& btn, char const* iconName, char const* tooltip); + + // Signal handlers void _addProperty(); }; diff --git a/src/ui/dialog/styledialog.cpp b/src/ui/dialog/styledialog.cpp index 7b68cfe53..581911a1b 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 + * Copyright (C) Tavmjong Bah 2017 * * Released under GNU GPL, read the file 'COPYING' for more information */ @@ -21,6 +23,8 @@ #include #include +//#define DEBUG_STYLEDIALOG + using Inkscape::Util::List; using Inkscape::XML::AttributeRecord; @@ -35,22 +39,6 @@ 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 @@ -60,42 +48,39 @@ 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) { - 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); + // Tree Inkscape::UI::Widget::AddToIcon * addRenderer = manage( new Inkscape::UI::Widget::AddToIcon() ); addRenderer->property_active() = true; + _store = Gtk::TreeStore::create(_mColumns); + _treeView.set_model(_store); + _treeView.set_headers_visible(false); 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.append_column("Selector Name", _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); @@ -105,8 +90,40 @@ 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)); + + // 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 @@ -114,27 +131,17 @@ 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()); + _document = _desktop->doc(); - _treeView.signal_button_press_event().connect(sigc::mem_fun(*this, - &StyleDialog:: - _handleButtonEvent), - false); + _readStyleElement(); - _treeView.signal_button_press_event().connect_notify(sigc::mem_fun - (*this, &StyleDialog:: - _buttonEventsSelectObjs), - false); - - _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 @@ -144,429 +151,619 @@ StyleDialog::~StyleDialog() 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()); + Inkscape::XML::Node *root = _document->getReprRoot(); + for (unsigned i = 0; i < root->childCount(); ++i) { + if (Glib::ustring(root->nthChild(i)->name()) == "svg:style") { + Inkscape::XML::Node * styleNode = root->nthChild(i); + + for (unsigned j = 0; j < styleNode->childCount(); ++j) { + if (styleNode->nthChild(j)->type() == Inkscape::XML::TEXT_NODE) { + return styleNode->nthChild(j); + } + } - /** - * 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(); - textDialogPtr->add_button("Add", Gtk::RESPONSE_OK); + // Style element found but does not contain text node! + std::cerr << "StyleDialog::_getStyleTextNode(): No text node!" << std::endl; + Inkscape::XML::Node *textNode = _document->getReprDoc()->createTextNode(""); + styleNode->appendChild(textNode); + Inkscape::GC::release(textNode); - Gtk::Entry *textEditPtr = manage ( new Gtk::Entry() ); - textDialogPtr->get_vbox()->pack_start(*textEditPtr, Gtk::PACK_SHRINK); + return textNode; + } + } - 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); + // Style element not found, create one + Inkscape::XML::Node *styleNode = _document->getReprDoc()->createElement("svg:style"); + Inkscape::XML::Node *textNode = _document->getReprDoc()->createTextNode(""); - /** - * 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 selected = - std::vector(selection->objects().begin(), - selection->objects().end()); - textEditPtr->set_text(_setClassAttribute(selected)); - } + styleNode->appendChild(textNode); + Inkscape::GC::release(textNode); - textDialogPtr->set_size_request(200, 100); - textEditPtr->show(); - textLabelPtr->hide(); - textDialogPtr->show(); + root->addChild(styleNode, NULL); + Inkscape::GC::release(styleNode); - int result = -1; - bool invalid = true; + return textNode; +} + + +/** + * @brief StyleDialog::_readStyleElement + * @return selVec + * Fill the Gtk::TreeStore from the svg:style element. + */ +void StyleDialog::_readStyleElement() +{ + + Inkscape::XML::Node * textNode = _getStyleTextNode(); + if (textNode == NULL) { + std::cerr << "StyleDialog::_readStyleElement: No text node!" << std::endl; + } + + // Get content from style text node. + std::string content = (textNode->content() ? textNode->content() : ""); - while (invalid) { - result = textDialogPtr->run(); + // 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 regex1 = + // Glib::Regex::create("([^\\{]+)\\{([^\\{]+)\\}"); + // + // Glib::MatchInfo minfo; + // regex1->match(content, minfo); + + // Split on curly brackets. Even tokens are selectors, odd are values. + std::vector tokens = Glib::Regex::split_simple("[}{]", content); + + // If text node is empty, return (avoids problem with negative below). + if (tokens.size() == 0) { + 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" - */ - _selectorName = textEditPtr->get_text(); + for (unsigned i = 0; i < tokens.size()-1; i += 2) { - del->set_sensitive(true); + Glib::ustring selector = tokens[i]; + REMOVE_SPACES(selector); // Remove leading/trailing spaces - if (_selectorName[0] == '.' || - _selectorName[0] == '#' || - SPAttributeRelSVG::isSVGElement( _selectorName ) ) { - invalid = false; + // Get list of objects selector matches + std::vector 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 { - textLabelPtr->show(); + std::cerr << "StyleDialog::_readStyleElement: Missing values " + "for last selector!" << std::endl; + } + REMOVE_SPACES(properties); + + Gtk::TreeModel::Row row = *(_store->append()); + row[_mColumns._colSelector] = selector; + row[_mColumns._colAddRemove] = 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._colAddRemove] = false; + childrow[_mColumns._colObj] = std::vector(1, obj); + childrow[_mColumns._colProperties] = ""; // Unused } } +} - /** - * 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 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); - } +/** + * @brief StyleDialog::_writeStyleElement + * Update the content of the style element as selectors (or objects) are added/removed. + */ +void StyleDialog::_writeStyleElement() +{ + Glib::ustring styleContent; + for (auto& row: _store->children()) { + styleContent = styleContent + row[_mColumns._colSelector] + + " { " + row[_mColumns._colProperties] + " }\n"; + } + // We could test if styleContent is empty and then delete the style node here but there is no + // harm in keeping it around ... - if (obj->getAttribute("style") == NULL) { - _selectorValue = _selectorName + "{" + "}" + "\n"; - } - else { - _selectorValue = _selectorName + "{" - + obj->getAttribute("style") + "}" + "\n"; - } + Inkscape::XML::Node *textNode = _getStyleTextNode(); + textNode->setContent(styleContent.c_str()); - if (_selectorName[0] == '.') { - if (!obj->getRepr()->attribute("class")) { - obj->getRepr()->setAttribute("class", textEditPtr->get_text() - .erase(0,1)); +#ifdef DEBUG_STYLEDIALOG + std::cout << "StyleDialog::_writeStyleElement(): |" << styleContent << "|" << std::endl; +#endif +} + + +/** + * @brief StyleDialog::_addToSelector + * @param row + * Add selected objects on the desktop to the selector corresponding to 'row'. + */ +void StyleDialog::_addToSelector(Gtk::TreeModel::Row row) +{ +#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 objVec = row[_mColumns._colObj]; + bool found = false; + for (auto& obj: objVec) { + if (id == obj->getId()) { + found = true; + break; + } } - else { - obj->getRepr()->setAttribute("class", std::string(obj-> - getRepr()-> - attribute("class")) - + " " + textEditPtr->get_text() - .erase(0,1)); + + 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._colAddRemove] = false; + childrow[_mColumns._colObj] = std::vector(1, obj); } } } - } - else { - _selectorValue = _selectorName + "{" + "}" + "\n"; - objExists = false; - } - switch (result) { - case Gtk::RESPONSE_OK: - textDialogPtr->hide(); - row[_mColumns._selectorLabel] = _selectorName; - row[_mColumns._colAddRemove] = true; - if (objExists) { + 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 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(); - row[_mColumns._colObj] = std::vector(selection->objects() - .begin(), selection - ->objects().end()); - objVec = row[_mColumns._colObj]; + std::vector 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._colAddRemove] = false; + childrow[_mColumns._colObj] = std::vector(1, obj); + } } - 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; + else { + // Do nothing for element selectors. + // std::cout << " Element selector... doing nothing!" << std::endl; + } } - _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()); + _writeStyleElement(); } + /** - * @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::_removeFromSelector + * @param row + * Remove the object corresponding to 'row' from the parent selector. */ -void StyleDialog::_delSelector() +void StyleDialog::_removeFromSelector(Gtk::TreeModel::Row row) { - Glib::RefPtr 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::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::_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); } } - Glib::ustring selectedRowLabel = row[_mColumns._selectorLabel]; - std::string matchSelector = selectedRowLabel; - REMOVE_SPACES(matchSelector); - if (key == matchSelector) { - it = _selectorVec.erase(it); + else if (selector[0] == '.') { + // 'class' selector... remove value to class attribute of selected objects. + + std::vector objVec = row[_mColumns._colObj]; // Just one + + // Get first class (split on white space or comma) + std::vector 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 { - ++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(); + // 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::_getIdList + * @param sel + * @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. */ -Inkscape::XML::Node* StyleDialog::_styleElementNode() +Glib::ustring StyleDialog::_getIdList(std::vector sel) { - 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); - } + 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 NULL; + return str; } /** - * @brief StyleDialog::_setClassAttribute - * @param sel - * @return This function returns the ids of objects selected which are passed - * to entrybox. + * @brief StyleDialog::_getObjVec + * @return objVec + * Return a vector of all objects that selector matches. This only handles simple id, class, and + * element selectors. Expanding this would take integrating a true CSS parser. */ -std::string StyleDialog::_setClassAttribute(std::vector sel) -{ - std::string str = ""; - for ( unsigned i = 0; i < sel.size(); ++i ) { - SPObject *obj = sel.at(i); - str = str + "#" + std::string(obj->getId()) + " "; +std::vector StyleDialog::_getObjVec(Glib::ustring selector) { + + // Split selector string into individual selectors (which are comma separated). + std::vector tokens = Glib::Regex::split_simple("\\s*,\\s*", selector ); + + std::vector objVec; + + for (auto& token: tokens) { + + // Find objects that match class selector + if (token[0] == '.') { + token.erase(0,1); + std::vector objects = _document->getObjectsByClass( token ); + objVec.insert(objVec.end(), objects.begin(), objects.end()); + } + + // Find objects that match id selector + else if (token[0] == '#') { + token.erase(0,1); + SPObject * object = _document->getObjectById( token ); + if (object) { + objVec.push_back(object); + } + } + + // Find objects that match element selector + else { + std::vector objects = _document->getObjectsByElement( token ); + objVec.insert(objVec.end(), objects.begin(), objects.end()); + } + } - return str; + +#ifdef DEBUG_STYLEDIALOG + std::cout << "StyleDialog::_getObjVec: |" << selector << "|" << std::endl; + for (auto& obj: objVec) { + std::cout << " " << (obj->getId()?obj->getId():"null") << std::endl; + } +#endif + + return objVec; } + /** - * @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::_insertClass + * @param objs: list of objects to insert class + * @param class: class to insert + * Insert a class name into objects' 'class' attribute. */ -std::vector 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 regex1 = - // Glib::Regex::create("([^\\{]+)\\{([^\\{]+)\\}"); - // - // Glib::MatchInfo minfo; - // regex1->match(content, minfo); - - // Split on curly brackets. Even tokens are selectors, odd are values. - std::vector 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::vectorobjVec; - - // Split selector string into individual selectors (which are comma separated). - std::vector 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 objects = _document - ->getObjectsByClass(token2); - objVec.insert(objVec.end(), objects.begin(), objects.end()); - } +void StyleDialog::_insertClass(const std::vector& objVec, const Glib::ustring& className) { - // 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); - } - } + for (auto& obj: objVec) { - // Find objects that match element selector - else { - std::vector objects = _document-> - getObjectsByElement(token2); - objVec.insert(objVec.end(), objects.begin(), objects.end()); - } + 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 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 ); + } + } + } + } - 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); +/** + * @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 + + _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 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 _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 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; + std::cout << " preferred: " << sreq2.width << "x" << sreq2.height << std::endl; + std::cout << " minimum: " << minWidth << "x" << minHeight << std::endl; + 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(); + del->set_sensitive(true); + + if (selectorValue[0] == '.' || + selectorValue[0] == '#' || + SPAttributeRelSVG::isSVGElement( selectorValue ) ) { + invalid = false; + } else { + textLabelPtr->show(); + } } + delete textDialogPtr; - return selectorValue; + // ==== Handle response ==== + + // 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); + } + + // If 'id' selector, regenerate objVec in case user modified list. + if (selectorValue[0] == '#') { + // To do + } + + // Add entry to GUI tree + Gtk::TreeModel::Row row = *(_store->append()); + row[_mColumns._colSelector] = selectorValue; + row[_mColumns._colAddRemove] = 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._colAddRemove] = false; + childrow[_mColumns._colObj] = std::vector(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 refTreeSelection = _treeView.get_selection(); + Gtk::TreeModel::iterator iter = refTreeSelection->get_selected(); + if (iter) { + Gtk::TreeModel::Row row = *iter; + _store->erase(iter); + _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(event->x); @@ -575,119 +772,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 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::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 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); } } } @@ -695,275 +789,129 @@ bool StyleDialog::_handleButtonEvent(GdkEventButton *event) 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 sel = std::vector - (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::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 + * @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) { - int x = static_cast(event->x); - int y = static_cast(event->y); - _selectObjects(x, y); - } - else if (event->type == GDK_2BUTTON_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(event->x); int y = static_cast(event->y); _selectObjects(x, y); + //} + //else if (event->type == GDK_2BUTTON_PRESS && event->button == 1) { + //int x = static_cast(event->x); + //int y = static_cast(event->y); + //_selectObjects(x, y); - //Open CSS dialog here. - if (!_cssPane->get_visible()) { - _paned.pack2(*_cssPane, Gtk::SHRINK); - _cssPane->show_all(); - } + _cssPane->_store->clear(); + // This should probably be in a member function of CSSDialog. Glib::RefPtr 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::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 = row[_mColumns._colProperties]; + REMOVE_SPACES(properties); // Remove leading/trailing spaces + + std::vector 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; } + } + } + } + _updating = false; +} - 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); - } - } - } + +/** + * @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*/) +{ + if (_updating) return; // Avoid updating if we have set row via dialog. + + 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 objVec = row[_mColumns._colObj]; + for (unsigned i = 0; i < objVec.size(); ++i) { + if (obj->getId() == objVec[i]->getId()) { + _treeView.get_selection()->select(row); + 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(); } + /** * @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 } /** * @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 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::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(); } } @@ -971,14 +919,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(event->x); @@ -989,45 +938,23 @@ 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); + + Glib::ustring properties; + for (auto& crow: _cssPane->_store->children()) { + properties = properties + crow[_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::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); - } - } - } + // Update selector data. + Glib::RefPtr refTreeSelection = _treeView.get_selection(); + Gtk::TreeModel::iterator iter = refTreeSelection->get_selected(); + if (iter) { + Gtk::TreeModel::Row row = *iter; + row[_mColumns._colProperties] = properties; + _writeStyleElement(); } } } @@ -1036,98 +963,24 @@ 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 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 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 selected = std::vector - (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 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 diff --git a/src/ui/dialog/styledialog.h b/src/ui/dialog/styledialog.h index 3cc4349e6..37a186e40 100644 --- a/src/ui/dialog/styledialog.h +++ b/src/ui/dialog/styledialog.h @@ -3,8 +3,10 @@ */ /* Authors: * Kamalpreet Kaur Grewal + * Tavmjong Bah * * Copyright (C) Kamalpreet Kaur Grewal 2016 + * Copyright (C) Tavmjong Bah 2017 * * Released under GNU GPL, read the file 'COPYING' for more information */ @@ -33,11 +35,12 @@ namespace Dialog { * A list of CSS selectors will show up in this dialog. This dialog allows one to * add and delete selectors. Elements can be added to and removed from the selectors * in the dialog. Selection of any selector row selects the matching objects in - * the drawing and vice-versa. + * the drawing and vice-versa. (Only simple selectors supported for now.) + * + * This class must keep two things in sync: + * 1. The text node of the style element. + * 2. The Gtk::TreeModel. */ -typedef std::pair >, std::string> -_selectorVecType; - class StyleDialog : public Widget::Panel { public: @@ -45,75 +48,75 @@ public: ~StyleDialog(); static StyleDialog &getInstance() { return *new StyleDialog(); } + void setDesktop(SPDesktop* desktop); private: - void _styleButton(Gtk::Button& btn, char const* iconName, char const* tooltip); - std::string _setClassAttribute(std::vector); - class InkSelector { - public: - std::string _selector; - std::vector _matchingObjs; - std::string _xmlContent; - }; - - InkSelector inkSelector; - std::vector _selectorVec; - std::vector _getSelectorVec(); - std::string _populateTree(std::vector); - bool _handleButtonEvent(GdkEventButton *event); - void _buttonEventsSelectObjs(GdkEventButton *event); - void _selectObjects(int, int); - void _checkAllChildren(Gtk::TreeModel::Children& children); - Inkscape::XML::Node *_styleElementNode(); - void _updateStyleContent(); - void _selectRow(Selection *); - - class ModelColumns : public Gtk::TreeModel::ColumnRecord - { + // Data structure + class ModelColumns : public Gtk::TreeModel::ColumnRecord { public: ModelColumns() { - add(_selectorLabel); + add(_colSelector); add(_colAddRemove); add(_colObj); + add(_colProperties); } - Gtk::TreeModelColumn _selectorLabel; - Gtk::TreeModelColumn _colAddRemove; - Gtk::TreeModelColumn > _colObj; + Gtk::TreeModelColumn _colSelector; // Selector or matching object id. + Gtk::TreeModelColumn _colAddRemove; // Selector row or child object row. + Gtk::TreeModelColumn > _colObj; // List of matching objects. + Gtk::TreeModelColumn _colProperties; // List of properties. }; - - SPDesktop* _desktop; - SPDesktop* _targetDesktop; ModelColumns _mColumns; + + // TreeView + Gtk::TreeView _treeView; + Glib::RefPtr _store; + + // Widgets Gtk::VPaned _paned; Gtk::VBox _mainBox; Gtk::HBox _buttonBox; - Gtk::TreeView _treeView; - Glib::RefPtr _store; Gtk::ScrolledWindow _scrolledWindow; Gtk::Button* del; Gtk::Button* create; - SPDocument* _document; - bool _styleExists; - Inkscape::XML::Node *_styleChild; - std::string _selectorName; - std::string _selectorValue; - bool _newDrawing; CssDialog *_cssPane; - void _selAdd(Gtk::TreeModel::Row row); + + // Variables - Inkscape + SPDesktop* _desktop; // To do: use panel _desktop + SPDocument* _document; + bool _updating; + + // Reading and writing the style element. + Inkscape::XML::Node *_getStyleTextNode(); + void _readStyleElement(); + void _writeStyleElement(); + + // Manipulate Tree + void _addToSelector(Gtk::TreeModel::Row row); + void _removeFromSelector(Gtk::TreeModel::Row row); + Glib::ustring _getIdList(std::vector); + std::vector _getObjVec(Glib::ustring selector); + void _insertClass(const std::vector& objVec, const Glib::ustring& className); + void _selectObjects(int, int); // Signal handlers void _addSelector(); void _delSelector(); + bool _handleButtonEvent(GdkEventButton *event); + void _buttonEventsSelectObjs(GdkEventButton *event); + void _selectRow(Selection *); // Select row in tree when selection changed. void _selChanged(); - // Signal handler for CssDialog + // Signal handlers for CssDialog void _handleEdited(const Glib::ustring& path, const Glib::ustring& new_text); bool _delProperty(GdkEventButton *event); + + // GUI + void _styleButton(Gtk::Button& btn, char const* iconName, char const* tooltip); }; -} // namespace Dialog +} // namespace Dialogc } // namespace UI } // namespace Inkscape -- cgit v1.2.3 From 7494443a3f6c759f615e4e98676c0ed027e3ad47 Mon Sep 17 00:00:00 2001 From: Tavmjong Bah Date: Sat, 18 Feb 2017 09:42:01 +0100 Subject: Implement Undo. Respond to external changes to