summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorJabier Arraiza <jabier.arraiza@marker.es>2019-04-20 17:08:32 +0000
committerJabier Arraiza <jabier.arraiza@marker.es>2019-04-20 17:08:32 +0000
commit76faa84ef18d5272f3625db09f75de4dbc51a557 (patch)
tree4002bc57ffe0d5e17e974274f42be8c7953bf4af /src
parentFix ur.po (2) (diff)
downloadinkscape-76faa84ef18d5272f3625db09f75de4dbc51a557.tar.gz
inkscape-76faa84ef18d5272f3625db09f75de4dbc51a557.zip
Rename files to fit better his function
Diffstat (limited to 'src')
-rw-r--r--src/ui/CMakeLists.txt4
-rw-r--r--src/ui/dialog/cssdialog.cpp367
-rw-r--r--src/ui/dialog/cssdialog.h136
-rw-r--r--src/ui/dialog/dialog-manager.cpp6
-rw-r--r--src/ui/dialog/selectordialog.cpp1243
-rw-r--r--src/ui/dialog/selectordialog.h182
-rw-r--r--src/ui/dialog/styledialog.cpp1360
-rw-r--r--src/ui/dialog/styledialog.h230
8 files changed, 1764 insertions, 1764 deletions
diff --git a/src/ui/CMakeLists.txt b/src/ui/CMakeLists.txt
index 885bcc34d..bf773ba6f 100644
--- a/src/ui/CMakeLists.txt
+++ b/src/ui/CMakeLists.txt
@@ -90,7 +90,6 @@ set(ui_SRC
dialog/clonetiler.cpp
dialog/color-item.cpp
dialog/attrdialog.cpp
- dialog/cssdialog.cpp
dialog/debug.cpp
dialog/desktop-tracker.cpp
dialog/dialog-manager.cpp
@@ -134,6 +133,7 @@ set(ui_SRC
dialog/print-colors-preview-dialog.cpp
dialog/print.cpp
dialog/prototype.cpp
+ dialog/selectordialog.cpp
dialog/spellcheck.cpp
dialog/styledialog.cpp
dialog/svg-fonts-dialog.cpp
@@ -255,7 +255,6 @@ set(ui_SRC
dialog/clonetiler.h
dialog/color-item.h
dialog/attrdialog.h
- dialog/cssdialog.h
dialog/debug.h
dialog/desktop-tracker.h
dialog/dialog-manager.h
@@ -301,6 +300,7 @@ set(ui_SRC
dialog/print-colors-preview-dialog.h
dialog/print.h
dialog/prototype.h
+ dialog/selectordialog.h
dialog/spellcheck.h
dialog/styledialog.h
dialog/svg-fonts-dialog.h
diff --git a/src/ui/dialog/cssdialog.cpp b/src/ui/dialog/cssdialog.cpp
deleted file mode 100644
index 06dfc9348..000000000
--- a/src/ui/dialog/cssdialog.cpp
+++ /dev/null
@@ -1,367 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0-or-later
-/** @file
- * @brief A dialog for CSS selectors
- */
-/* Authors:
- * Kamalpreet Kaur Grewal
- * Tavmjong Bah
- *
- * Copyright (C) Kamalpreet Kaur Grewal 2016 <grewalkamal005@gmail.com>
- * Copyright (C) Tavmjong Bah 2017 <tavmjong@free.fr>
- *
- * Released under GNU GPL v2+, read the file 'COPYING' for more information.
- */
-
-#include "cssdialog.h"
-
-#include "message-context.h"
-#include "message-stack.h"
-#include "selection.h"
-#include "style-internal.h"
-#include "style.h"
-
-#include "ui/icon-loader.h"
-#include "ui/widget/iconrenderer.h"
-#include "verbs.h"
-
-#include "xml/attribute-record.h"
-#include "xml/node-event-vector.h"
-#include <glibmm/i18n.h>
-
-#include <glibmm/i18n.h>
-
-static void on_attr_changed(Inkscape::XML::Node *repr, const gchar *name, const gchar * /*old_value*/,
- const gchar *new_value, bool /*is_interactive*/, gpointer data)
-{
- CSS_DIALOG(data)->onAttrChanged(repr, name, new_value);
-}
-
-Inkscape::XML::NodeEventVector css_repr_events = {
- nullptr, /* child_added */
- nullptr, /* child_removed */
- on_attr_changed, nullptr, /* content_changed */
- nullptr /* order_changed */
-};
-
-namespace Inkscape {
-namespace UI {
-namespace Dialog {
-
-/**
- * Constructor
- * A treeview whose each row corresponds to a CSS property of selector selected.
- * New CSS property can be added by clicking '+' at bottom of the CSS pane. '-'
- * in front of the CSS property row can be clicked to delete the CSS property.
- * Besides clicking on an already selected property row makes the property editable
- * and clicking 'Enter' updates the property with changes reflected in the
- * drawing.
- */
-CssDialog::CssDialog()
- : UI::Widget::Panel("/dialogs/css", SP_VERB_DIALOG_CSS)
- , _desktop(nullptr)
- , _repr(nullptr)
-{
- set_size_request(20, 15);
- _treeView.set_headers_visible(true);
- auto _scrolledWindow = new Gtk::ScrolledWindow();
- _scrolledWindow->add(_treeView);
- _scrolledWindow->set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC);
-
- _store = Gtk::ListStore::create(_cssColumns);
- _treeView.set_model(_store);
-
- Inkscape::UI::Widget::IconRenderer *addRenderer = manage(new Inkscape::UI::Widget::IconRenderer());
- addRenderer->add_icon("edit-delete");
- addRenderer->signal_activated().connect(sigc::mem_fun(*this, &CssDialog::onPropertyDelete));
-
- _message_stack = std::make_shared<Inkscape::MessageStack>();
- _message_context = std::unique_ptr<Inkscape::MessageContext>(new Inkscape::MessageContext(_message_stack));
- _message_changed_connection =
- _message_stack->connectChanged(sigc::bind(sigc::ptr_fun(_set_status_message), GTK_WIDGET(status.gobj())));
-
- int addCol = _treeView.append_column("", *addRenderer) - 1;
- Gtk::TreeViewColumn *col = _treeView.get_column(addCol);
- if (col) {
- col->add_attribute(addRenderer->property_visible(), _cssColumns.deleteButton);
- col->set_sort_column(_cssColumns.deleteButton);
-
- Gtk::Image *add_icon = Gtk::manage(sp_get_icon_image("list-add", Gtk::ICON_SIZE_SMALL_TOOLBAR));
- col->set_clickable(true);
- col->set_widget(*add_icon);
- add_icon->set_tooltip_text(_("Add a new style property"));
- add_icon->show();
- // This gets the GtkButton inside the GtkBox, inside the GtkAlignment, inside the GtkImage icon.
- auto button = add_icon->get_parent()->get_parent()->get_parent();
- // Assign the button event so that create happens BEFORE delete. If this code
- // isn't in this exact way, the onAttrDelete is called when the header lines are pressed.
- button->signal_button_release_event().connect(sigc::mem_fun(*this, &CssDialog::onPropertyCreate), false);
- }
-
- Gtk::CellRendererText *renderer = Gtk::manage(new Gtk::CellRendererText());
- _treeView.set_reorderable(false);
- renderer->property_editable() = true;
- int nameColNum = _treeView.append_column("Property", *renderer) - 1;
- _propCol = _treeView.get_column(nameColNum);
- if (_propCol) {
- _propCol->add_attribute(renderer->property_text(), _cssColumns.label);
- _propCol->add_attribute(renderer->property_foreground_rgba(), _cssColumns.label_color);
- _propCol->set_sort_column(_cssColumns.label);
- }
-
- renderer = Gtk::manage(new Gtk::CellRendererText());
- renderer->property_editable() = false;
- int attrColNum = _treeView.append_column("Set", *renderer) - 1;
- _attrCol = _treeView.get_column(attrColNum);
- if (_attrCol) {
- _attrCol->add_attribute(renderer->property_text(), _cssColumns._styleAttrVal);
- _attrCol->add_attribute(renderer->property_foreground_rgba(), _cssColumns.attr_color);
- _attrCol->add_attribute(renderer->property_strikethrough(), _cssColumns.attr_strike);
- _attrCol->add_attribute(renderer->property_editable(), _cssColumns.editable);
- _attrCol->set_sort_column(_cssColumns._styleAttrVal);
- }
-
- renderer = Gtk::manage(new Gtk::CellRendererText());
- renderer->property_editable() = true;
- int sheetColNum = _treeView.append_column("Actual", *renderer) - 1;
- _sheetCol = _treeView.get_column(sheetColNum);
- if (_sheetCol) {
- _sheetCol->add_attribute(renderer->property_text(), _cssColumns._styleSheetVal);
- _sheetCol->add_attribute(renderer->property_foreground_rgba(), _cssColumns.label_color);
- _sheetCol->set_sort_column(_cssColumns._styleSheetVal);
- }
-
- // Set the initial sort column (and direction) to place real attributes at the top.
- _store->set_sort_column(_cssColumns.deleteButton, Gtk::SORT_DESCENDING);
-
- _getContents()->pack_start(*_scrolledWindow, Gtk::PACK_EXPAND_WIDGET);
-
- css_reset_context(0);
- setDesktop(getDesktop());
-}
-
-/**
- * @brief CssDialog::~CssDialog
- * Class destructor
- */
-CssDialog::~CssDialog()
-{
- setDesktop(nullptr);
- _repr = nullptr;
- _message_changed_connection.disconnect();
- _message_context = nullptr;
- _message_stack = nullptr;
- _message_changed_connection.~connection();
-}
-
-void CssDialog::_set_status_message(Inkscape::MessageType /*type*/, const gchar *message, GtkWidget *widget)
-{
- if (widget) {
- gtk_label_set_markup(GTK_LABEL(widget), message ? message : "");
- }
-}
-
-
-/**
- * @brief CssDialog::setDesktop
- * @param desktop
- * This function sets the 'desktop' for the CSS pane.
- */
-void CssDialog::setDesktop(SPDesktop* desktop)
-{
- _desktop = desktop;
-}
-
-/**
- * @brief CssDialog::setRepr
- *
- * Set the internal xml object that I'm working on right now.
- */
-void CssDialog::setRepr(Inkscape::XML::Node *repr)
-{
- if (repr == _repr)
- return;
- if (_repr) {
- _store->clear();
- _repr->removeListenerByData(this);
- Inkscape::GC::release(_repr);
- _repr = nullptr;
- }
- _repr = repr;
- if (repr) {
- Inkscape::GC::anchor(_repr);
- _repr->addListener(&css_repr_events, this);
- _repr->synthesizeEvents(&css_repr_events, this);
- }
-}
-
-/**
- * @brief CssDialog::parseStyle
- *
- * Convert a style string into a vector map. This should be moved to style.cpp
- *
- */
-std::map<Glib::ustring, Glib::ustring> CssDialog::parseStyle(Glib::ustring style_string)
-{
- std::map<Glib::ustring, Glib::ustring> ret;
-
- REMOVE_SPACES(style_string); // We'd use const, but we need to trip spaces
- std::vector<Glib::ustring> props = r_props->split(style_string);
-
- for (auto const token : props) {
- if (token.empty())
- break;
- std::vector<Glib::ustring> pair = r_pair->split(token);
-
- if (pair.size() > 1) {
- ret[pair[0]] = pair[1];
- }
- }
- return ret;
-}
-
-/**
- * @brief CssDialog::compileStyle
- *
- * Turn a vector map back into a style string.
- *
- */
-Glib::ustring CssDialog::compileStyle(std::map<Glib::ustring, Glib::ustring> props)
-{
- auto ret = Glib::ustring("");
- for (auto const pair : props) {
- if (!pair.first.empty() && !pair.second.empty()) {
- ret += pair.first;
- ret += ":";
- ret += pair.second;
- ret += ";";
- }
- }
- return ret;
-}
-
-
-/**
- * @brief CssDialog::onAttrChanged
- *
- * This is called when the XML has an updated attribute (we only care about style)
- */
-void CssDialog::onAttrChanged(Inkscape::XML::Node *repr, const gchar *name, const gchar *new_value)
-{
- if (strcmp(name, "style") != 0)
- return;
-
- // Clear the list and return if the new_value is empty
- _store->clear();
- if (!new_value || new_value[0] == 0)
- return;
-
- // Get the object's style attribute and it's calculated properties
- SPDocument *document = this->_desktop->doc();
- SPObject *obj = document->getObjectByRepr(repr);
- // std::vector<SPIBase *> calc_prop = obj->style->properties();
-
- // Get a dictionary lookup of the style in the attribute
- std::map<Glib::ustring, Glib::ustring> attr_prop = parseStyle(new_value);
-
- for (auto iter : obj->style->properties()) {
- if (iter->style && iter->style_src != SP_STYLE_SRC_UNSET) {
- Gtk::TreeModel::Row row = *(_store->append());
- // Delete is available to attribute properties only in attr mode.
- row[_cssColumns.deleteButton] = iter->style_src == SP_STYLE_SRC_ATTRIBUTE;
- row[_cssColumns.label] = iter->name;
- if (attr_prop.count(iter->name)) {
- row[_cssColumns._styleAttrVal] = attr_prop[iter->name];
- if (attr_prop[iter->name] != iter->get_value()) {
- row[_cssColumns._styleSheetVal] = iter->get_value();
- row[_cssColumns.attr_color] = Gdk::RGBA("gray");
- row[_cssColumns.attr_strike] = true;
- }
- row[_cssColumns.deleteButton] = true;
- } else {
- row[_cssColumns._styleSheetVal] = iter->get_value();
- row[_cssColumns.label_color] = Gdk::RGBA("gray");
- row[_cssColumns.attr_color] = Gdk::RGBA("gray");
- row[_cssColumns.deleteButton] = false;
- }
- }
- }
-}
-
-/*
- * Sets the CSSDialog status bar, depending on which attr is selected.
- */
-void CssDialog::css_reset_context(gint css)
-{
- if (css == 0) {
- _message_context->set(Inkscape::NORMAL_MESSAGE, _("<b>Click</b> CSS property to edit."));
- } else {
- const gchar *name = g_quark_to_string(css);
- _message_context->setF(
- Inkscape::NORMAL_MESSAGE,
- _("Property <b>%s</b> selected. Press <b>Ctrl+Enter</b> when done editing to commit changes."), name);
- }
-}
-
-/**
- * @brief CssDialog::setStyleProperty
- *
- * Set or delete a single property in the style attribute.
- */
-bool CssDialog::setStyleProperty(Glib::ustring name, Glib::ustring value)
-{
- auto original = this->_repr->attribute("style");
- std::map<Glib::ustring, Glib::ustring> properties = parseStyle(original);
-
- bool updated = false;
- if (!value.empty()) {
- if (properties[name] != value) {
- // Set value (create or update)
- properties[name] = value;
- updated = true;
- }
- } else if (properties.count(name)) {
- // Delete value
- properties.erase(name);
- updated = true;
- }
-
- if (updated) {
- auto new_styles = this->compileStyle(properties);
- this->_repr->setAttribute("style", new_styles, false);
- // this->setUndo(_("Delete style property"));
- }
- return updated;
-}
-
-/**
- * @brief CssDialog::onPropertyDelete
- *
- * This function is a slot to signal_activated for '-' button panel.
- */
-void CssDialog::onPropertyDelete(Glib::ustring path)
-{
- Gtk::TreeModel::Row row = *_store->get_iter(path);
- if (row) {
- this->setStyleProperty(row[_cssColumns.label], "");
- }
-}
-
-/**
- * @brief CssDialog::onPropertyCreate
- * This function is a slot to signal_clicked for '+' button panel.
- */
-bool CssDialog::onPropertyCreate(GdkEventButton *event)
-{
- if (event->type == GDK_BUTTON_RELEASE && event->button == 1 && this->_repr) {
- Gtk::TreeIter iter = _store->append();
- Gtk::TreeModel::Path path = (Gtk::TreeModel::Path)iter;
- _treeView.set_cursor(path, *_propCol, true);
- grab_focus();
- return true;
- }
- return false;
-}
-
-} // namespace Dialog
-} // namespace UI
-} // namespace Inkscape
diff --git a/src/ui/dialog/cssdialog.h b/src/ui/dialog/cssdialog.h
deleted file mode 100644
index 6c5a0fe4c..000000000
--- a/src/ui/dialog/cssdialog.h
+++ /dev/null
@@ -1,136 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0-or-later
-/** @file
- * @brief A dialog for CSS selectors
- */
-/* Authors:
- * Kamalpreet Kaur Grewal
- * Tavmjong Bah
- *
- * Copyright (C) Kamalpreet Kaur Grewal 2016 <grewalkamal005@gmail.com>
- * Copyright (C) Tavmjong Bah 2017 <tavmjong@free.fr>
- *
- * Released under GNU GPL v2+, read the file 'COPYING' for more information.
- */
-
-#ifndef SEEN_UI_DIALOGS_CSSDIALOG_H
-#define SEEN_UI_DIALOGS_CSSDIALOG_H
-
-#include "desktop.h"
-#include "message.h"
-
-#include <glibmm/regex.h>
-#include <gtkmm/dialog.h>
-#include <gtkmm/liststore.h>
-#include <gtkmm/scrolledwindow.h>
-#include <gtkmm/treeview.h>
-#include <ui/widget/panel.h>
-
-#define CSS_DIALOG(obj) (dynamic_cast<Inkscape::UI::Dialog::CssDialog *>((Inkscape::UI::Dialog::CssDialog *)obj))
-#define REMOVE_SPACES(x) \
- x.erase(0, x.find_first_not_of(' ')); \
- x.erase(x.find_last_not_of(' ') + 1);
-
-namespace Inkscape {
-class MessageStack;
-class MessageContext;
-namespace UI {
-namespace Dialog {
-
-/**
- * @brief The CssDialog class
- * This dialog allows to add, delete and modify CSS properties for selectors
- * created in Style Dialog. Double clicking any selector in Style dialog, a list
- * of CSS properties will show up in this dialog (if any exist), else new properties
- * can be added and each new property forms a new row in this pane.
- */
-class CssDialog : public UI::Widget::Panel
-{
-public:
- CssDialog();
- ~CssDialog() override;
-
- static CssDialog &getInstance() { return *new CssDialog(); }
-
- // Data structure
- class CssColumns : public Gtk::TreeModel::ColumnRecord {
- public:
- CssColumns() {
- add(deleteButton);
- add(label);
- add(_styleSheetVal);
- add(_styleAttrVal);
- add(label_color);
- add(attr_color);
- add(attr_strike);
- add(editable);
- }
- Gtk::TreeModelColumn<bool> deleteButton;
- Gtk::TreeModelColumn<Glib::ustring> label;
- Gtk::TreeModelColumn<Glib::ustring> _styleAttrVal;
- Gtk::TreeModelColumn<Glib::ustring> _styleSheetVal;
- Gtk::TreeModelColumn<Gdk::RGBA> label_color;
- Gtk::TreeModelColumn<Gdk::RGBA> attr_color;
- Gtk::TreeModelColumn<bool> attr_strike;
- Gtk::TreeModelColumn<bool> editable;
- };
- CssColumns _cssColumns;
-
- /**
- * Status bar
- */
- std::shared_ptr<Inkscape::MessageStack> _message_stack;
- std::unique_ptr<Inkscape::MessageContext> _message_context;
-
- // TreeView
- Gtk::TreeView _treeView;
- Glib::RefPtr<Gtk::ListStore> _store;
- Gtk::TreeModel::Row _propRow;
- Gtk::TreeViewColumn *_propCol;
- Gtk::TreeViewColumn *_sheetCol;
- Gtk::TreeViewColumn *_attrCol;
- Gtk::HBox status_box;
- Gtk::Label status;
-
- /**
- * Sets the XML status bar, depending on which attr is selected.
- */
- void css_reset_context(gint css);
- static void _set_status_message(Inkscape::MessageType type, const gchar *message, GtkWidget *dialog);
-
-
- // Variables - Inkscape
- SPDesktop* _desktop;
- Inkscape::XML::Node *_repr;
-
- // Helper functions
- void setDesktop(SPDesktop* desktop) override;
- void setRepr(Inkscape::XML::Node *repr);
-
- // Parsing functions
- std::map<Glib::ustring, Glib::ustring> parseStyle(Glib::ustring style_string);
- Glib::ustring compileStyle(std::map<Glib::ustring, Glib::ustring> props);
-
- // Signal handlers
- void onAttrChanged(Inkscape::XML::Node *repr, const gchar *name, const gchar *new_value);
-
- private:
- Glib::RefPtr<Glib::Regex> r_props = Glib::Regex::create("\\s*;\\s*");
- Glib::RefPtr<Glib::Regex> r_pair = Glib::Regex::create("\\s*:\\s*");
-
- bool onPropertyCreate(GdkEventButton *event);
- void onPropertyDelete(Glib::ustring path);
- bool setStyleProperty(Glib::ustring name, Glib::ustring value);
-
- /**
- * Signal handlers
- */
- sigc::connection _message_changed_connection;
- bool _addProperty(GdkEventButton *event);
-};
-
-
-} // namespace Dialog
-} // namespace UI
-} // namespace Inkscape
-
-#endif // CSSDIALOG_H
diff --git a/src/ui/dialog/dialog-manager.cpp b/src/ui/dialog/dialog-manager.cpp
index dcfa6fe9a..00bcedd62 100644
--- a/src/ui/dialog/dialog-manager.cpp
+++ b/src/ui/dialog/dialog-manager.cpp
@@ -55,8 +55,8 @@
#include "ui/dialog/clonetiler.h"
#include "ui/dialog/svg-fonts-dialog.h"
#include "ui/dialog/objects.h"
+#include "ui/dialog/selectordialog.h"
#include "ui/dialog/styledialog.h"
-#include "ui/dialog/cssdialog.h"
namespace Inkscape {
namespace UI {
@@ -126,7 +126,7 @@ DialogManager::DialogManager() {
registerFactory("TileDialog", &create<ArrangeDialog, FloatingBehavior>);
registerFactory("Symbols", &create<SymbolsDialog, FloatingBehavior>);
registerFactory("StyleDialog", &create<StyleDialog, FloatingBehavior>);
- registerFactory("CssDialog", &create<CssDialog, FloatingBehavior>);
+ registerFactory("SelectorDialog", &create<SelectorDialog, FloatingBehavior>);
#if HAVE_POTRACE
registerFactory("Trace", &create<TraceDialog, FloatingBehavior>);
@@ -168,7 +168,7 @@ DialogManager::DialogManager() {
registerFactory("TileDialog", &create<ArrangeDialog, DockBehavior>);
registerFactory("Symbols", &create<SymbolsDialog, DockBehavior>);
registerFactory("StyleDialog", &create<StyleDialog, DockBehavior>);
- registerFactory("CssDialog", &create<CssDialog, DockBehavior>);
+ registerFactory("SelectorDialog", &create<SelectorDialog, DockBehavior>);
#if HAVE_POTRACE
registerFactory("Trace", &create<TraceDialog, DockBehavior>);
diff --git a/src/ui/dialog/selectordialog.cpp b/src/ui/dialog/selectordialog.cpp
new file mode 100644
index 000000000..d6f77b7c1
--- /dev/null
+++ b/src/ui/dialog/selectordialog.cpp
@@ -0,0 +1,1243 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * @brief A dialog for CSS selectors
+ */
+/* Authors:
+ * Kamalpreet Kaur Grewal
+ * Tavmjong Bah
+ *
+ * Copyright (C) Kamalpreet Kaur Grewal 2016 <grewalkamal005@gmail.com>
+ * Copyright (C) Tavmjong Bah 2017 <tavmjong@free.fr>
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "selectordialog.h"
+#include "verbs.h"
+#include "selection.h"
+#include "attribute-rel-svg.h"
+#include "inkscape.h"
+#include "document-undo.h"
+
+#include "ui/icon-loader.h"
+#include "ui/widget/iconrenderer.h"
+
+#include "xml/attribute-record.h"
+#include "xml/node-observer.h"
+
+#include <glibmm/i18n.h>
+#include <glibmm/regex.h>
+
+#include <map>
+#include <utility>
+
+//#define DEBUG_SELECTORDIALOG
+//#define G_LOG_DOMAIN "SELECTORDIALOG"
+
+using Inkscape::DocumentUndo;
+using Inkscape::Util::List;
+using Inkscape::XML::AttributeRecord;
+
+/**
+ * This macro is used to remove spaces around selectors or any strings when
+ * parsing is done to update XML style element or row labels in this dialog.
+ */
+#define REMOVE_SPACES(x) x.erase(0, x.find_first_not_of(' ')); \
+ x.erase(x.find_last_not_of(' ') + 1);
+
+namespace Inkscape {
+namespace UI {
+namespace Dialog {
+
+// Keeps a watch on style element
+class SelectorDialog::NodeObserver : public Inkscape::XML::NodeObserver {
+public:
+ NodeObserver(SelectorDialog* styleDialog) :
+ _styleDialog(styleDialog)
+ {
+ g_debug("SelectorDialog::NodeObserver: Constructor");
+ };
+
+ void notifyContentChanged(Inkscape::XML::Node &node,
+ Inkscape::Util::ptr_shared old_content,
+ Inkscape::Util::ptr_shared new_content) override;
+
+ SelectorDialog * _styleDialog;
+};
+
+
+void
+SelectorDialog::NodeObserver::notifyContentChanged(
+ Inkscape::XML::Node &/*node*/,
+ Inkscape::Util::ptr_shared /*old_content*/,
+ Inkscape::Util::ptr_shared /*new_content*/ ) {
+
+ g_debug("SelectorDialog::NodeObserver::notifyContentChanged");
+ _styleDialog->_updating = false;
+ _styleDialog->_readStyleElement();
+ _styleDialog->_selectRow();
+}
+
+
+// Keeps a watch for new/removed/changed nodes
+// (Must update objects that selectors match.)
+class SelectorDialog::NodeWatcher : public Inkscape::XML::NodeObserver {
+public:
+ NodeWatcher(SelectorDialog* styleDialog, Inkscape::XML::Node *repr) :
+ _styleDialog(styleDialog),
+ _repr(repr)
+ {
+ g_debug("SelectorDialog::NodeWatcher: Constructor");
+ };
+
+ void notifyChildAdded( Inkscape::XML::Node &/*node*/,
+ Inkscape::XML::Node &child,
+ Inkscape::XML::Node */*prev*/ ) override
+ {
+ if ( _styleDialog && _repr ) {
+ _styleDialog->_nodeAdded( child );
+ }
+ }
+
+ void notifyChildRemoved( Inkscape::XML::Node &/*node*/,
+ Inkscape::XML::Node &child,
+ Inkscape::XML::Node */*prev*/ ) override
+ {
+ if ( _styleDialog && _repr ) {
+ _styleDialog->_nodeRemoved( child );
+ }
+ }
+
+ void notifyAttributeChanged( Inkscape::XML::Node &node,
+ GQuark qname,
+ Util::ptr_shared /*old_value*/,
+ Util::ptr_shared /*new_value*/ ) override {
+ if ( _styleDialog && _repr ) {
+
+ // For the moment only care about attributes that are directly used in selectors.
+ const gchar * cname = g_quark_to_string (qname );
+ Glib::ustring name;
+ if (cname) {
+ name = cname;
+ }
+
+ if ( name == "id" || name == "class" ) {
+ _styleDialog->_nodeChanged( node );
+ }
+ }
+ }
+
+ SelectorDialog * _styleDialog;
+ Inkscape::XML::Node * _repr; // Need to track if document changes.
+};
+
+void
+SelectorDialog::_nodeAdded( Inkscape::XML::Node &node ) {
+
+ SelectorDialog::NodeWatcher *w = new SelectorDialog::NodeWatcher (this, &node);
+ node.addObserver (*w);
+ _nodeWatchers.push_back(w);
+
+ _readStyleElement();
+ _selectRow();
+}
+
+void
+SelectorDialog::_nodeRemoved( Inkscape::XML::Node &repr ) {
+
+ for (auto it = _nodeWatchers.begin(); it != _nodeWatchers.end(); ++it) {
+ if ( (*it)->_repr == &repr ) {
+ (*it)->_repr->removeObserver (**it);
+ _nodeWatchers.erase( it );
+ break;
+ }
+ }
+
+ _readStyleElement();
+ _selectRow();
+}
+
+void
+SelectorDialog::_nodeChanged( Inkscape::XML::Node &object ) {
+
+ _readStyleElement();
+ _selectRow();
+}
+
+SelectorDialog::TreeStore::TreeStore()
+= default;
+
+
+/**
+ * Allow dragging only selectors.
+ */
+bool
+SelectorDialog::TreeStore::row_draggable_vfunc(const Gtk::TreeModel::Path& path) const
+{
+ g_debug("SelectorDialog::TreeStore::row_draggable_vfunc");
+
+ auto unconstThis = const_cast<SelectorDialog::TreeStore*>(this);
+ const_iterator iter = unconstThis->get_iter(path);
+ if (iter) {
+ Gtk::TreeModel::Row row = *iter;
+ bool is_draggable =
+ row[_selectordialog->_mColumns._colType] == SELECTOR || row[_selectordialog->_mColumns._colType] == UNHANDLED;
+ return is_draggable;
+ }
+ return Gtk::TreeStore::row_draggable_vfunc(path);
+}
+
+
+/**
+ * Allow dropping only in between other selectors.
+ */
+bool
+SelectorDialog::TreeStore::row_drop_possible_vfunc(const Gtk::TreeModel::Path& dest,
+ const Gtk::SelectionData& selection_data) const
+{
+ g_debug("SelectorDialog::TreeStore::row_drop_possible_vfunc");
+
+ Gtk::TreeModel::Path dest_parent = dest;
+ dest_parent.up();
+ return dest_parent.empty();
+}
+
+
+// This is only here to handle updating style element after a drag and drop.
+void
+SelectorDialog::TreeStore::on_row_deleted(const TreeModel::Path& path)
+{
+ if (_selectordialog->_updating) return; // Don't write if we deleted row (other than from DND)
+
+ g_debug("on_row_deleted");
+
+ _selectordialog->_writeStyleElement();
+}
+
+
+Glib::RefPtr<SelectorDialog::TreeStore> SelectorDialog::TreeStore::create(SelectorDialog *selectordialog)
+{
+ SelectorDialog::TreeStore * store = new SelectorDialog::TreeStore();
+ store->_selectordialog = selectordialog;
+ store->set_column_types( store->_selectordialog->_mColumns );
+ return Glib::RefPtr<SelectorDialog::TreeStore>( store );
+}
+
+/**
+ * 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.
+ */
+SelectorDialog::SelectorDialog() :
+ UI::Widget::Panel("/dialogs/style", SP_VERB_DIALOG_STYLE),
+ _updating(false),
+ _textNode(nullptr),
+ _desktopTracker()
+{
+ g_debug("SelectorDialog::SelectorDialog");
+
+ // Tree
+ Inkscape::UI::Widget::IconRenderer * addRenderer = manage(
+ new Inkscape::UI::Widget::IconRenderer() );
+ addRenderer->add_icon("edit-delete");
+ addRenderer->add_icon("list-add");
+ addRenderer->add_icon("object-locked");
+
+ _store = TreeStore::create(this);
+ _treeView.set_model(_store);
+ _treeView.set_headers_visible(true);
+ _treeView.enable_model_drag_source();
+ _treeView.enable_model_drag_dest( Gdk::ACTION_MOVE );
+ int addCol = _treeView.append_column("", *addRenderer) - 1;
+ Gtk::TreeViewColumn *col = _treeView.get_column(addCol);
+ if ( col ) {
+ col->add_attribute(addRenderer->property_icon(), _mColumns._colType);
+ }
+ _treeView.append_column("CSS Selector", _mColumns._colSelector);
+ _treeView.set_expander_column(*(_treeView.get_column(1)));
+
+ // Pack widgets
+ _paned.set_orientation(Gtk::ORIENTATION_VERTICAL);
+ _paned.pack1(_mainBox, Gtk::SHRINK);
+ _mainBox.set_orientation(Gtk::ORIENTATION_VERTICAL);
+ _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, &SelectorDialog::_addSelector));
+
+ del = manage( new Gtk::Button() );
+ _styleButton(*del, "list-remove", "Remove a CSS Selector");
+ del->signal_clicked().connect(sigc::mem_fun(*this, &SelectorDialog::_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);
+
+
+ // Signal handlers
+ _treeView.signal_button_release_event().connect( // Needs to be release, not press.
+ sigc::mem_fun(*this, &SelectorDialog::_handleButtonEvent),
+ false);
+
+ _treeView.signal_button_release_event().connect_notify(
+ sigc::mem_fun(*this, &SelectorDialog::_buttonEventsSelectObjs),
+ false);
+
+ _treeView.signal_row_expanded().connect(sigc::mem_fun(*this, &SelectorDialog::_rowExpand));
+
+ _treeView.signal_row_collapsed().connect(sigc::mem_fun(*this, &SelectorDialog::_rowCollapse));
+
+ // Document & Desktop
+ _desktop_changed_connection = _desktopTracker.connectDesktopChanged(
+ sigc::mem_fun(*this, &SelectorDialog::_handleDesktopChanged) );
+ _desktopTracker.connect(GTK_WIDGET(gobj()));
+
+ _document_replaced_connection = getDesktop()->connectDocumentReplaced(
+ sigc::mem_fun(this, &SelectorDialog::_handleDocumentReplaced));
+
+ _selection_changed_connection = getDesktop()->getSelection()->connectChanged(
+ sigc::hide(sigc::mem_fun(this, &SelectorDialog::_handleSelectionChanged)));
+
+ // Add watchers
+ _updateWatchers();
+
+ // Load tree
+ _readStyleElement();
+ _selectRow();
+
+ if (!_store->children().empty()) {
+ del->set_sensitive(true);
+ }
+
+}
+
+
+/**
+ * @brief SelectorDialog::~SelectorDialog
+ * Class destructor
+ */
+SelectorDialog::~SelectorDialog()
+{
+ g_debug("SelectorDialog::~SelectorDialog");
+ _desktop_changed_connection.disconnect();
+ _document_replaced_connection.disconnect();
+ _selection_changed_connection.disconnect();
+}
+
+
+/**
+ * @brief SelectorDialog::_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.
+ */
+Inkscape::XML::Node* SelectorDialog::_getStyleTextNode()
+{
+
+ Inkscape::XML::Node *styleNode = nullptr;
+ Inkscape::XML::Node *textNode = nullptr;
+
+ Inkscape::XML::Node *root = SP_ACTIVE_DOCUMENT->getReprRoot();
+ for (unsigned i = 0; i < root->childCount(); ++i) {
+ if (Glib::ustring(root->nthChild(i)->name()) == "svg:style") {
+
+ styleNode = root->nthChild(i);
+
+ for (unsigned j = 0; j < styleNode->childCount(); ++j) {
+ if (styleNode->nthChild(j)->type() == Inkscape::XML::TEXT_NODE) {
+ textNode = styleNode->nthChild(j);
+ }
+ }
+
+ if (textNode == nullptr) {
+ // Style element found but does not contain text node!
+ std::cerr << "SelectorDialog::_getStyleTextNode(): No text node!" << std::endl;
+ textNode = SP_ACTIVE_DOCUMENT->getReprDoc()->createTextNode("");
+ styleNode->appendChild(textNode);
+ Inkscape::GC::release(textNode);
+ }
+ }
+ }
+
+ if (styleNode == nullptr) {
+ // Style element not found, create one
+ styleNode = SP_ACTIVE_DOCUMENT->getReprDoc()->createElement("svg:style");
+ textNode = SP_ACTIVE_DOCUMENT->getReprDoc()->createTextNode("");
+
+ styleNode->appendChild(textNode);
+ Inkscape::GC::release(textNode);
+
+ root->addChild(styleNode, nullptr);
+ Inkscape::GC::release(styleNode);
+ }
+
+ if (_textNode != textNode) {
+ _textNode = textNode;
+ NodeObserver *no = new NodeObserver(this);
+ textNode->addObserver(*no);
+ }
+
+ return textNode;
+}
+
+
+/**
+ * @brief SelectorDialog::_readStyleElement
+ * Fill the Gtk::TreeStore from the svg:style element.
+ */
+void SelectorDialog::_readStyleElement()
+{
+ g_debug("SelectorDialog::_readStyleElement: updating %s", (_updating ? "true" : "false"));
+
+ if (_updating) return; // Don't read if we wrote style element.
+ _updating = true;
+
+ Inkscape::XML::Node * textNode = _getStyleTextNode();
+ if (textNode == nullptr) {
+ std::cerr << "SelectorDialog::_readStyleElement: No text node!" << std::endl;
+ }
+
+ // Get content from style text node.
+ std::string content = (textNode->content() ? textNode->content() : "");
+
+ // Remove end-of-lines (check it works on Windoze).
+ content.erase(std::remove(content.begin(), content.end(), '\n'), content.end());
+
+ // Remove comments (/* xxx */)
+ while(content.find("/*") != std::string::npos) {
+ size_t start = content.find("/*");
+ content.erase(start, (content.find("*/", start) - start) +2);
+ }
+
+ // First split into selector/value chunks.
+ // An attempt to use Glib::Regex failed. A C++11 version worked but
+ // reportedly has problems on Windows. Using split_simple() is simpler
+ // and probably faster.
+ //
+ // Glib::RefPtr<Glib::Regex> regex1 =
+ // Glib::Regex::create("([^\\{]+)\\{([^\\{]+)\\}");
+ //
+ // Glib::MatchInfo minfo;
+ // regex1->match(content, minfo);
+
+ // Split on curly brackets. Even tokens are selectors, odd are values.
+ std::vector<Glib::ustring> tokens = Glib::Regex::split_simple("[}{]", content);
+
+ // If text node is empty, return (avoids problem with negative below).
+ if (tokens.size() == 0) {
+ _updating = false;
+ return;
+ }
+ std::vector<std::pair<Glib::ustring, bool>> expanderstatus;
+ for (unsigned i = 0; i < tokens.size() - 1; i += 2) {
+ Glib::ustring selector = tokens[i];
+ REMOVE_SPACES(selector); // Remove leading/trailing spaces
+ for (auto &row : _store->children()) {
+ Glib::ustring selectorold = row[_mColumns._colSelector];
+ if (selectorold == selector) {
+ expanderstatus.emplace_back(selector, row[_mColumns._colExpand]);
+ }
+ }
+ }
+ _store->clear();
+
+ for (unsigned i = 0; i < tokens.size()-1; i += 2) {
+
+ Glib::ustring selector = tokens[i];
+ REMOVE_SPACES(selector); // Remove leading/trailing spaces
+ std::vector<Glib::ustring> tokensplus = Glib::Regex::split_simple("[,]+", selector);
+ coltype colType = SELECTOR;
+ for (auto tok : tokensplus) {
+ REMOVE_SPACES(tok);
+ if (tok.find(" ") != -1 || tok.erase(0, 1).find(".") != -1) {
+ colType = UNHANDLED;
+ }
+ }
+ // Get list of objects selector matches
+ std::vector<SPObject *> objVec = _getObjVec( selector );
+
+ Glib::ustring properties;
+ // Check to make sure we do have a value to match selector.
+ if ((i+1) < tokens.size()) {
+ properties = tokens[i+1];
+ } else {
+ std::cerr << "SelectorDialog::_readStyleElement: Missing values "
+ "for last selector!" << std::endl;
+ }
+ REMOVE_SPACES(properties);
+ bool colExpand = false;
+ for (auto rowstatus : expanderstatus) {
+ if (selector == rowstatus.first) {
+ colExpand = rowstatus.second;
+ }
+ }
+ Gtk::TreeModel::Row row = *(_store->append());
+ row[_mColumns._colSelector] = selector;
+ row[_mColumns._colExpand] = colExpand;
+ row[_mColumns._colType] = colType;
+ row[_mColumns._colObj] = objVec;
+ row[_mColumns._colProperties] = properties;
+ if (colType == SELECTOR) {
+ // 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._colExpand] = false;
+ childrow[_mColumns._colType] = OBJECT;
+ childrow[_mColumns._colObj] = std::vector<SPObject *>(1, obj);
+ childrow[_mColumns._colProperties] = ""; // Unused
+ }
+ }
+ }
+ _updating = false;
+}
+
+void SelectorDialog::_rowExpand(const Gtk::TreeModel::iterator &iter, const Gtk::TreeModel::Path &path)
+{
+ g_debug("SelectorDialog::_row_expand()");
+ Gtk::TreeModel::Row row = *iter;
+ row[_mColumns._colExpand] = true;
+}
+
+void SelectorDialog::_rowCollapse(const Gtk::TreeModel::iterator &iter, const Gtk::TreeModel::Path &path)
+{
+ g_debug("SelectorDialog::_row_collapse()");
+ Gtk::TreeModel::Row row = *iter;
+ row[_mColumns._colExpand] = false;
+}
+/**
+ * @brief SelectorDialog::_writeStyleElement
+ * Update the content of the style element as selectors (or objects) are added/removed.
+ */
+void SelectorDialog::_writeStyleElement()
+{
+ if (_updating) {
+ return;
+ }
+ _updating = true;
+
+ Glib::ustring styleContent;
+ for (auto& row: _store->children()) {
+ Glib::ustring selector = row[_mColumns._colSelector];
+ /*
+ REMOVE_SPACES(selector);
+ /* size_t len = selector.size();
+ if(selector[len-1] == ','){
+ selector.erase(len-1);
+ }
+ row[_mColumns._colSelector] = selector; */
+ styleContent = styleContent + selector + " { " + row[_mColumns._colProperties] + " }\n";
+ }
+ // We could test if styleContent is empty and then delete the style node here but there is no
+ // harm in keeping it around ...
+
+ Inkscape::XML::Node *textNode = _getStyleTextNode();
+ textNode->setContent(styleContent.c_str());
+
+ DocumentUndo::done(SP_ACTIVE_DOCUMENT, SP_VERB_DIALOG_STYLE, _("Edited style element."));
+
+ _updating = false;
+ g_debug("SelectorDialog::_writeStyleElement(): | %s |", styleContent.c_str());
+}
+
+
+void SelectorDialog::_addWatcherRecursive(Inkscape::XML::Node *node) {
+
+ g_debug("SelectorDialog::_addWatcherRecursive()");
+
+ SelectorDialog::NodeWatcher *w = new SelectorDialog::NodeWatcher(this, node);
+ node->addObserver(*w);
+ _nodeWatchers.push_back(w);
+
+ for (unsigned i = 0; i < node->childCount(); ++i) {
+ _addWatcherRecursive(node->nthChild(i));
+ }
+}
+
+/**
+ * @brief SelectorDialog::_updateWatchers
+ * Update the watchers on objects.
+ */
+void SelectorDialog::_updateWatchers()
+{
+ _updating = true;
+
+ // Remove old document watchers
+ while (!_nodeWatchers.empty()) {
+ SelectorDialog::NodeWatcher *w = _nodeWatchers.back();
+ w->_repr->removeObserver(*w);
+ _nodeWatchers.pop_back();
+ delete w;
+ }
+
+ // Recursively add new watchers
+ Inkscape::XML::Node *root = SP_ACTIVE_DOCUMENT->getReprRoot();
+ _addWatcherRecursive(root);
+
+ g_debug("SelectorDialog::_updateWatchers(): %d", (int)_nodeWatchers.size());
+
+ _updating = false;
+}
+
+
+/**
+ * @brief SelectorDialog::_addToSelector
+ * @param row
+ * Add selected objects on the desktop to the selector corresponding to 'row'.
+ */
+void SelectorDialog::_addToSelector(Gtk::TreeModel::Row row)
+{
+ g_debug("SelectorDialog::_addToSelector: Entrance");
+ if (*row) {
+
+ Glib::ustring selector = row[_mColumns._colSelector];
+
+ if (selector[0] == '#') {
+ // 'id' selector... add selected object's id's to list.
+ Inkscape::Selection* selection = getDesktop()->getSelection();
+ for (auto& obj: selection->objects()) {
+
+ Glib::ustring id = (obj->getId()?obj->getId():"");
+
+ std::vector<SPObject *> objVec = row[_mColumns._colObj];
+ bool found = false;
+ for (auto& obj: objVec) {
+ if (id == obj->getId()) {
+ found = true;
+ break;
+ }
+ }
+
+ 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 );
+ row[_mColumns._colExpand] = true;
+ // Add child row
+ Gtk::TreeModel::Row childrow = *(_store->append(row->children()));
+ childrow[_mColumns._colSelector] = "#" + Glib::ustring(obj->getId());
+ childrow[_mColumns._colType] = OBJECT;
+ childrow[_mColumns._colObj] = std::vector<SPObject *>(1, obj);
+ childrow[_mColumns._colProperties] = ""; // Unused
+ }
+ }
+ }
+
+ else if (selector[0] == '.') {
+ // 'class' selector... add value to class attribute of selected objects.
+
+ // Get first class (split on white space or comma)
+ std::vector<Glib::ustring> tokens = Glib::Regex::split_simple("[,\\s]+", selector);
+ Glib::ustring className = tokens[0];
+ className.erase(0,1);
+ Inkscape::Selection* selection = getDesktop()->getSelection();
+ std::vector<SPObject *> sel_obj(selection->objects().begin(), selection->objects().end());
+ _insertClass(sel_obj, className);
+ std::vector<SPObject *> objVec = _getObjVec(selector);
+ ;
+ for (auto &obj : sel_obj) {
+
+ Glib::ustring id = (obj->getId() ? obj->getId() : "");
+ bool found = false;
+ for (auto &obj : objVec) {
+ if (id == obj->getId()) {
+ found = true;
+ break;
+ }
+ }
+
+ if (!found) {
+ // Update row
+ objVec.push_back(obj); // Adding to copy so need to update tree
+ row[_mColumns._colObj] = objVec;
+ row[_mColumns._colExpand] = true;
+
+ // Update row
+ Gtk::TreeModel::Row childrow = *(_store->append(row->children()));
+ childrow[_mColumns._colSelector] = "#" + Glib::ustring(obj->getId());
+ childrow[_mColumns._colExpand] = false;
+ childrow[_mColumns._colType] = OBJECT;
+ childrow[_mColumns._colObj] = std::vector<SPObject *>(1, obj);
+ childrow[_mColumns._colProperties] = ""; // Unused
+ }
+ }
+ }
+
+ else {
+ // Do nothing for element selectors.
+ // std::cout << " Element selector... doing nothing!" << std::endl;
+ }
+ }
+
+ _writeStyleElement();
+}
+
+
+/**
+ * @brief SelectorDialog::_removeFromSelector
+ * @param row
+ * Remove the object corresponding to 'row' from the parent selector.
+ */
+void SelectorDialog::_removeFromSelector(Gtk::TreeModel::Row row)
+{
+ g_debug("SelectorDialog::_removeFromSelector: Entrance");
+ 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];
+ REMOVE_SPACES(selector);
+ 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
+ REMOVE_SPACES(selector);
+ if (i != Glib::ustring::npos && selector[i] == ',') {
+ selector.erase(i, 1);
+ }
+ if (i != Glib::ustring::npos && selector[i] == ' ') {
+ selector.erase(i, 1);
+ }
+ REMOVE_SPACES(selector);
+ if (selector[selector.size() - 1] == ',') {
+ selector.erase(selector.size() - 1, 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 );
+ parent[_mColumns._colExpand] = true;
+ _store->erase(row);
+ }
+ }
+
+ else if (selector[0] == '.') {
+ // 'class' selector... remove value to class attribute of selected objects.
+
+ std::vector<SPObject *> objVec = row[_mColumns._colObj]; // Just one
+ // Get first class (split on white space or comma)
+ std::vector<Glib::ustring> tokens = Glib::Regex::split_simple("[,\\s]+", selector);
+ Glib::ustring className = tokens[0];
+ className.erase(0, 1);
+ // Erase 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);
+ }
+ _store->erase(row);
+ objVec[0]->getRepr()->setAttribute("class", classAttr);
+ parent[_mColumns._colExpand] = true;
+ } else {
+ // Do nothing for element selectors.
+ // std::cout << " Element selector... doing nothing!" << std::endl;
+ }
+ }
+ }
+ _writeStyleElement();
+}
+
+
+/**
+ * @brief SelectorDialog::_getIdList
+ * @param sel
+ * @return This function returns a comma separated list of ids for objects in input vector.
+ * It is used in creating an 'id' selector. It relies on objects having 'id's.
+ */
+Glib::ustring SelectorDialog::_getIdList(std::vector<SPObject*> sel)
+{
+ Glib::ustring str;
+ for (auto& obj: sel) {
+ str += "#" + Glib::ustring(obj->getId()) + ", ";
+ }
+ if (!str.empty()) {
+ str.erase(str.size()-1); // Remove space at end. c++11 has pop_back() but not ustring.
+ str.erase(str.size()-1); // Remove comma at end.
+ }
+ return str;
+}
+
+/**
+ * @brief SelectorDialog::_getObjVec
+ * @param selector: a valid CSS selector string.
+ * @return objVec: a vector of pointers to SPObject's the selector matches.
+ * Return a vector of all objects that selector matches.
+ */
+std::vector<SPObject *> SelectorDialog::_getObjVec(Glib::ustring selector) {
+
+ std::vector<SPObject *> objVec = SP_ACTIVE_DOCUMENT->getObjectsBySelector( selector );
+
+ g_debug("SelectorDialog::_getObjVec: | %s |", selector.c_str());
+ for (auto& obj: objVec) {
+ g_debug(" %s", obj->getId() ? obj->getId() : "null");
+ }
+
+ return objVec;
+}
+
+
+/**
+ * @brief SelectorDialog::_insertClass
+ * @param objs: list of objects to insert class
+ * @param class: class to insert
+ * Insert a class name into objects' 'class' attribute.
+ */
+void SelectorDialog::_insertClass(const std::vector<SPObject *>& objVec, const Glib::ustring& className) {
+
+ for (auto& obj: objVec) {
+
+ if (!obj->getRepr()->attribute("class")) {
+ // 'class' attribute does not exist, create it.
+ obj->getRepr()->setAttribute("class", className);
+ } else {
+ // 'class' attribute exists, append.
+ Glib::ustring classAttr = obj->getRepr()->attribute("class");
+
+ // Split on white space.
+ std::vector<Glib::ustring> tokens = Glib::Regex::split_simple("\\s+", classAttr);
+ bool add = true;
+ for (auto& token: tokens) {
+ if (token == className) {
+ add = false; // Might be useful to still add...
+ break;
+ }
+ }
+ if (add) {
+ obj->getRepr()->setAttribute("class", classAttr + " " + className );
+ }
+ }
+ }
+ }
+
+
+/**
+ * @brief SelectorDialog::_selectObjects
+ * @param eventX
+ * @param eventY
+ * This function selects objects in the drawing corresponding to the selector
+ * selected in the treeview.
+ */
+void SelectorDialog::_selectObjects(int eventX, int eventY)
+{
+ g_debug("SelectorDialog::_selectObjects: %d, %d", eventX, eventY);
+
+ getDesktop()->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();
+ if (children.empty()) {
+ del->set_sensitive(true);
+ }
+ std::vector<SPObject *> objVec = row[_mColumns._colObj];
+
+ for (auto obj : objVec) {
+ getDesktop()->selection->add(obj);
+ }
+ }
+ }
+ }
+}
+
+
+/**
+ * @brief SelectorDialog::_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.
+ */
+void SelectorDialog::_addSelector()
+{
+ g_debug("SelectorDialog::_addSelector: Entrance");
+
+ // Store list of selected elements on desktop (not to be confused with selector).
+ Inkscape::Selection* selection = getDesktop()->getSelection();
+ std::vector<SPObject *> objVec( selection->objects().begin(),
+ selection->objects().end() );
+
+ // ==== Create popup dialog ====
+ Gtk::Dialog *textDialogPtr = new Gtk::Dialog();
+ textDialogPtr->add_button(_("Cancel"), Gtk::RESPONSE_CANCEL);
+ textDialogPtr->add_button(_("Add"), Gtk::RESPONSE_OK);
+
+ Gtk::Entry *textEditPtr = manage ( new Gtk::Entry() );
+ textEditPtr->signal_activate().connect(
+ sigc::bind<Gtk::Dialog *>(sigc::mem_fun(*this, &SelectorDialog::_closeDialog), textDialogPtr));
+ textDialogPtr->get_content_area()->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_content_area()->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 (getDesktop()->getSelection()->isEmpty()) {
+ textEditPtr->set_text(".Class1");
+ } else {
+ textEditPtr->set_text(_getIdList(objVec));
+ }
+
+ Gtk::Requisition sreq1, sreq2;
+ textDialogPtr->get_preferred_size(sreq1, sreq2);
+ int minWidth = 200;
+ int minHeight = 100;
+ minWidth = (sreq2.width > minWidth ? sreq2.width : minWidth );
+ minHeight = (sreq2.height > minHeight ? sreq2.height : minHeight);
+ textDialogPtr->set_size_request(minWidth, minHeight);
+ textEditPtr->show();
+ textLabelPtr->hide();
+ textDialogPtr->show();
+
+
+ // ==== Get response ====
+ int result = -1;
+ bool invalid = true;
+ Glib::ustring selectorValue;
+ bool handled = true;
+ while (invalid) {
+ result = textDialogPtr->run();
+ if (result != Gtk::RESPONSE_OK) { // Cancel, close dialog, etc.
+ textDialogPtr->hide();
+ delete textDialogPtr;
+ return;
+ }
+ /**
+ * @brief selectorName
+ * This string stores selector name. The text from entrybox is saved as name
+ * for selector. If the entrybox is empty, the text (thus selectorName) is
+ * set to ".Class1"
+ */
+ selectorValue = textEditPtr->get_text();
+ Glib::ustring firstWord = selectorValue.substr(0, selectorValue.find_first_of(" >+~"));
+ if (firstWord != selectorValue) {
+ handled = false;
+ }
+ del->set_sensitive(true);
+
+ if (selectorValue[0] == '.' || selectorValue[0] == '#' || selectorValue[0] == '*' ||
+ SPAttributeRelSVG::isSVGElement(selectorValue)) {
+ invalid = false;
+ } else {
+ textLabelPtr->show();
+ }
+ }
+ delete textDialogPtr;
+ // ==== Handle response ====
+
+ // If class selector, add selector name to class attribute for each object
+ if (selectorValue[0] == '.' && handled) {
+ std::vector<Glib::ustring> tokens = Glib::Regex::split_simple("[,\\s]+", selectorValue);
+ Glib::ustring originClassName = tokens[0];
+ originClassName.erase(0, 1);
+ std::vector<Glib::ustring> classes = Glib::Regex::split_simple("[\\.]+", originClassName);
+ if (classes.size() == 1) {
+ _insertClass(objVec, classes[0]);
+ } else {
+ handled = false;
+ }
+ }
+
+ // Generate a new object vector (we could have an element selector,
+ // the user could have edited the id selector list, etc.).
+ objVec = _getObjVec( selectorValue );
+
+ // Add entry to GUI tree
+ Gtk::TreeModel::Row row = *(_store->append());
+ row[_mColumns._colSelector] = selectorValue;
+ row[_mColumns._colExpand] = true;
+ row[_mColumns._colType] = handled ? SELECTOR : UNHANDLED;
+ row[_mColumns._colObj] = objVec;
+
+ // Add as children objects that match selector.
+ if (handled) {
+ for (auto &obj : objVec) {
+ Gtk::TreeModel::Row childrow = *(_store->append(row->children()));
+ childrow[_mColumns._colSelector] = "#" + Glib::ustring(obj->getId());
+ childrow[_mColumns._colExpand] = false;
+ childrow[_mColumns._colType] = OBJECT;
+ childrow[_mColumns._colObj] = std::vector<SPObject *>(1, obj);
+ }
+ }
+
+ // Add entry to style element
+ _writeStyleElement();
+}
+
+void SelectorDialog::_closeDialog(Gtk::Dialog *textDialogPtr) { textDialogPtr->response(Gtk::RESPONSE_OK); }
+
+/**
+ * @brief SelectorDialog::_delSelector
+ * This function deletes selector when '-' at the bottom is clicked.
+ * Note: If deleting a class selector, class attributes are NOT changed.
+ */
+void SelectorDialog::_delSelector()
+{
+ g_debug("SelectorDialog::_delSelector");
+
+ Glib::RefPtr<Gtk::TreeSelection> refTreeSelection = _treeView.get_selection();
+ _treeView.get_selection()->set_mode(Gtk::SELECTION_SINGLE);
+ Gtk::TreeModel::iterator iter = refTreeSelection->get_selected();
+ if (iter) {
+ Gtk::TreeModel::Row row = *iter;
+ if (!row.children().empty()) {
+ return;
+ }
+ _updating = true;
+ _store->erase(iter);
+ _updating = false;
+ _writeStyleElement();
+ del->set_sensitive(false);
+ }
+}
+
+/**
+ * @brief SelectorDialog::_handleButtonEvent
+ * @param event
+ * @return
+ * 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 SelectorDialog::_handleButtonEvent(GdkEventButton *event)
+{
+ g_debug("SelectorDialog::_handleButtonEvent: Entrance");
+ if (event->type == GDK_BUTTON_RELEASE && event->button == 1) {
+ Gtk::TreeViewColumn *col = nullptr;
+ 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)) {
+ bool remove_parent = false;
+ Gtk::TreeModel::iterator iter = _store->get_iter(path);
+ Gtk::TreeModel::Row row = *iter;
+ Glib::RefPtr<Gtk::TreeSelection> sel = _treeView.get_selection();
+ sel->select(row);
+ // Add or remove objects from a
+ Gtk::TreeModel::Row row_to_sel;
+ if (!row.parent()) {
+ row_to_sel = row;
+ _addToSelector(row);
+ } else {
+ row_to_sel = *row.parent();
+ _removeFromSelector(row);
+ }
+ }
+ }
+ }
+ return false;
+}
+
+// -------------------------------------------------------------------
+
+class PropertyData
+{
+public:
+ PropertyData() = default;;
+ PropertyData(Glib::ustring name) : _name(std::move(name)) {};
+
+ void _setSheetValue(Glib::ustring value) { _sheetValue = value; };
+ void _setAttrValue(Glib::ustring value) { _attrValue = value; };
+ Glib::ustring _getName() { return _name; };
+ Glib::ustring _getSheetValue() { return _sheetValue; };
+ Glib::ustring _getAttrValue() { return _attrValue; };
+
+private:
+ Glib::ustring _name;
+ Glib::ustring _sheetValue;
+ Glib::ustring _attrValue;
+};
+
+// -------------------------------------------------------------------
+
+
+/**
+ * Handle document replaced. (Happens when a default document is immediately replaced by another
+ * document in a new window.)
+ */
+void
+SelectorDialog::_handleDocumentReplaced(SPDesktop *desktop, SPDocument * /* document */)
+{
+ g_debug("SelectorDialog::handleDocumentReplaced()");
+
+ _selection_changed_connection.disconnect();
+
+ _selection_changed_connection = desktop->getSelection()->connectChanged(
+ sigc::hide(sigc::mem_fun(this, &SelectorDialog::_handleSelectionChanged)));
+
+ _updateWatchers();
+ _readStyleElement();
+ _selectRow();
+}
+
+
+/*
+ * When a dialog is floating, it is connected to the active desktop.
+ */
+void
+SelectorDialog::_handleDesktopChanged(SPDesktop* desktop) {
+ g_debug("SelectorDialog::handleDesktopReplaced()");
+
+ if (getDesktop() == desktop) {
+ // This will happen after construction of dialog. We've already
+ // set up signals so just return.
+ return;
+ }
+
+ _selection_changed_connection.disconnect();
+ _document_replaced_connection.disconnect();
+
+ setDesktop( desktop );
+
+ _selection_changed_connection = desktop->getSelection()->connectChanged(
+ sigc::hide(sigc::mem_fun(this, &SelectorDialog::_handleSelectionChanged)));
+ _document_replaced_connection = desktop->connectDocumentReplaced(
+ sigc::mem_fun(this, &SelectorDialog::_handleDocumentReplaced));
+
+ _updateWatchers();
+ _readStyleElement();
+ _selectRow();
+}
+
+
+/*
+ * Handle a change in which objects are selected in a document.
+ */
+void
+SelectorDialog::_handleSelectionChanged() {
+ g_debug("SelectorDialog::_handleSelectionChanged()");
+ _treeView.get_selection()->set_mode(Gtk::SELECTION_MULTIPLE);
+ _selectRow();
+}
+
+
+/**
+ * @brief SelectorDialog::_buttonEventsSelectObjs
+ * @param event
+ * 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 SelectorDialog::_buttonEventsSelectObjs(GdkEventButton* event )
+{
+ g_debug("SelectorDialog::_buttonEventsSelectObjs");
+ _treeView.get_selection()->set_mode(Gtk::SELECTION_SINGLE);
+ _updating = true;
+ del->set_sensitive(true);
+ if (event->type == GDK_BUTTON_RELEASE && event->button == 1) {
+ int x = static_cast<int>(event->x);
+ int y = static_cast<int>(event->y);
+ _selectObjects(x, y);
+ }
+ _updating = false;
+}
+
+
+/**
+ * @brief SelectorDialog::_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 SelectorDialog::_selectRow()
+{
+ g_debug("SelectorDialog::_selectRow: updating: %s", (_updating ? "true" : "false"));
+ del->set_sensitive(false);
+ if (_updating || !getDesktop()) return; // Avoid updating if we have set row via dialog.
+ if (SP_ACTIVE_DESKTOP != getDesktop()) {
+ std::cerr << "SelectorDialog::_selectRow: SP_ACTIVE_DESKTOP != getDesktop()" << std::endl;
+ return;
+ }
+ _treeView.get_selection()->unselect_all();
+ Gtk::TreeModel::Children children = _store->children();
+ Inkscape::Selection* selection = getDesktop()->getSelection();
+ SPObject *obj = nullptr;
+ if (!selection->isEmpty()) {
+ obj = selection->objects().back();
+ }
+ for (auto row : children) {
+ std::vector<SPObject *> objVec = row[_mColumns._colObj];
+ if (obj) {
+ for (auto & i : objVec) {
+ if (obj->getId() == i->getId()) {
+ _treeView.get_selection()->select(row);
+ }
+ }
+ }
+ if (row[_mColumns._colExpand]) {
+ _treeView.expand_to_path(Gtk::TreePath(row));
+ }
+ }
+}
+
+
+/**
+ * @brief SelectorDialog::_styleButton
+ * @param btn
+ * @param iconName
+ * @param tooltip
+ * Set the style of '+' and '-' buttons at the bottom of dialog.
+ */
+void SelectorDialog::_styleButton(Gtk::Button& btn, char const* iconName,
+ char const* tooltip)
+{
+ GtkWidget *child = sp_get_icon_image(iconName, GTK_ICON_SIZE_SMALL_TOOLBAR);
+ gtk_widget_show(child);
+ btn.add(*manage(Glib::wrap(child)));
+ btn.set_relief(Gtk::RELIEF_NONE);
+ btn.set_tooltip_text (tooltip);
+}
+
+
+} // 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/selectordialog.h b/src/ui/dialog/selectordialog.h
new file mode 100644
index 000000000..306169611
--- /dev/null
+++ b/src/ui/dialog/selectordialog.h
@@ -0,0 +1,182 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * @brief A dialog for CSS selectors
+ */
+/* Authors:
+ * Kamalpreet Kaur Grewal
+ * Tavmjong Bah
+ *
+ * Copyright (C) Kamalpreet Kaur Grewal 2016 <grewalkamal005@gmail.com>
+ * Copyright (C) Tavmjong Bah 2017 <tavmjong@free.fr>
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#ifndef SELECTORDIALOG_H
+#define SELECTORDIALOG_H
+
+#include <ui/widget/panel.h>
+#include <gtkmm/treeview.h>
+#include <gtkmm/treestore.h>
+#include <gtkmm/scrolledwindow.h>
+#include <gtkmm/dialog.h>
+#include <gtkmm/treeselection.h>
+#include <gtkmm/paned.h>
+
+#include "ui/dialog/desktop-tracker.h"
+
+#include "xml/helper-observer.h"
+
+namespace Inkscape {
+namespace UI {
+namespace Dialog {
+
+/**
+ * @brief The SelectorDialog class
+ * 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. (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.
+ */
+class SelectorDialog : public Widget::Panel {
+
+public:
+ ~SelectorDialog() override;
+ // No default constructor, noncopyable, nonassignable
+ SelectorDialog();
+ SelectorDialog(SelectorDialog const &d) = delete;
+ SelectorDialog operator=(SelectorDialog const &d) = delete;
+
+ static SelectorDialog &getInstance() { return *new SelectorDialog(); }
+
+ private:
+ // Monitor <style> element for changes.
+ class NodeObserver;
+
+ // Monitor all objects for addition/removal/attribute change
+ class NodeWatcher;
+
+ std::vector<SelectorDialog::NodeWatcher*> _nodeWatchers;
+ void _nodeAdded( Inkscape::XML::Node &repr );
+ void _nodeRemoved( Inkscape::XML::Node &repr );
+ void _nodeChanged( Inkscape::XML::Node &repr );
+ // Data structure
+ enum coltype { OBJECT, SELECTOR, UNHANDLED };
+ class ModelColumns : public Gtk::TreeModel::ColumnRecord {
+ public:
+ ModelColumns() {
+ add(_colSelector);
+ add(_colExpand);
+ add(_colType);
+ add(_colObj);
+ add(_colProperties);
+ }
+ Gtk::TreeModelColumn<Glib::ustring> _colSelector; // Selector or matching object id.
+ Gtk::TreeModelColumn<bool> _colExpand; // Open/Close store row.
+ Gtk::TreeModelColumn<gint> _colType; // Selector row or child object row.
+ Gtk::TreeModelColumn<std::vector<SPObject *> > _colObj; // List of matching objects.
+ Gtk::TreeModelColumn<Glib::ustring> _colProperties; // List of properties.
+ };
+ ModelColumns _mColumns;
+
+ // Override Gtk::TreeStore to control drag-n-drop (only allow dragging and dropping of selectors).
+ // See: https://developer.gnome.org/gtkmm-tutorial/stable/sec-treeview-examples.html.en
+ //
+ // TreeStore implements simple drag and drop (DND) but there appears no way to know when a DND
+ // has been completed (other than doing the whole DND ourselves). As a hack, we use
+ // on_row_deleted to trigger write of style element.
+ class TreeStore : public Gtk::TreeStore {
+ protected:
+ TreeStore();
+ bool row_draggable_vfunc(const Gtk::TreeModel::Path& path) const override;
+ bool row_drop_possible_vfunc(const Gtk::TreeModel::Path& path,
+ const Gtk::SelectionData& selection_data) const override;
+ void on_row_deleted(const TreeModel::Path& path) override;
+
+ public:
+ static Glib::RefPtr<SelectorDialog::TreeStore> create(SelectorDialog *selectordialog);
+
+ private:
+ SelectorDialog *_selectordialog;
+ };
+
+ // TreeView
+ Gtk::TreeView _treeView;
+ Glib::RefPtr<TreeStore> _store;
+
+ // Widgets
+ Gtk::Paned _paned;
+ Gtk::Box _mainBox;
+ Gtk::Box _buttonBox;
+ Gtk::ScrolledWindow _scrolledWindow;
+ Gtk::Button* del;
+ Gtk::Button* create;
+
+ // Reading and writing the style element.
+ Inkscape::XML::Node *_getStyleTextNode();
+ void _readStyleElement();
+ void _writeStyleElement();
+
+ // Update watchers
+ void _addWatcherRecursive(Inkscape::XML::Node *node);
+ void _updateWatchers();
+
+ // Manipulate Tree
+ void _addToSelector(Gtk::TreeModel::Row row);
+ void _removeFromSelector(Gtk::TreeModel::Row row);
+ Glib::ustring _getIdList(std::vector<SPObject *>);
+ std::vector<SPObject *> _getObjVec(Glib::ustring selector);
+ void _insertClass(const std::vector<SPObject *>& objVec, const Glib::ustring& className);
+ void _selectObjects(int, int);
+
+ // Variables
+ bool _updating; // Prevent cyclic actions: read <-> write, select via dialog <-> via desktop
+ Inkscape::XML::Node *_textNode; // Track so we know when to add a NodeObserver.
+
+ // Signals and handlers - External
+ sigc::connection _document_replaced_connection;
+ sigc::connection _desktop_changed_connection;
+ sigc::connection _selection_changed_connection;
+
+ void _handleDocumentReplaced(SPDesktop* desktop, SPDocument *document);
+ void _handleDesktopChanged(SPDesktop* desktop);
+ void _handleSelectionChanged();
+ void _rowExpand(const Gtk::TreeModel::iterator &iter, const Gtk::TreeModel::Path &path);
+ void _rowCollapse(const Gtk::TreeModel::iterator &iter, const Gtk::TreeModel::Path &path);
+ void _closeDialog(Gtk::Dialog *textDialogPtr);
+
+ DesktopTracker _desktopTracker;
+
+ Inkscape::XML::SignalObserver _objObserver; // Track object in selected row (for style change).
+
+ // Signal and handlers - Internal
+ void _addSelector();
+ void _delSelector();
+ bool _handleButtonEvent(GdkEventButton *event);
+ void _buttonEventsSelectObjs(GdkEventButton *event);
+ void _selectRow(); // Select row in tree when selection changed.
+
+ // GUI
+ void _styleButton(Gtk::Button& btn, char const* iconName, char const* tooltip);
+};
+
+} // namespace Dialogc
+} // namespace UI
+} // namespace Inkscape
+
+#endif // SELECTORDIALOG_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 :
diff --git a/src/ui/dialog/styledialog.cpp b/src/ui/dialog/styledialog.cpp
index 338ca6df9..93f699ec2 100644
--- a/src/ui/dialog/styledialog.cpp
+++ b/src/ui/dialog/styledialog.cpp
@@ -13,1231 +13,355 @@
*/
#include "styledialog.h"
-#include "verbs.h"
+
+#include "message-context.h"
+#include "message-stack.h"
#include "selection.h"
-#include "attribute-rel-svg.h"
-#include "inkscape.h"
-#include "document-undo.h"
+#include "style-internal.h"
+#include "style.h"
#include "ui/icon-loader.h"
#include "ui/widget/iconrenderer.h"
+#include "verbs.h"
#include "xml/attribute-record.h"
-#include "xml/node-observer.h"
-
+#include "xml/node-event-vector.h"
#include <glibmm/i18n.h>
-#include <glibmm/regex.h>
-
-#include <map>
-#include <utility>
-//#define DEBUG_STYLEDIALOG
-//#define G_LOG_DOMAIN "STYLEDIALOG"
+#include <glibmm/i18n.h>
-using Inkscape::DocumentUndo;
-using Inkscape::Util::List;
-using Inkscape::XML::AttributeRecord;
+static void on_attr_changed(Inkscape::XML::Node *repr, const gchar *name, const gchar * /*old_value*/,
+ const gchar *new_value, bool /*is_interactive*/, gpointer data)
+{
+ STYLE_DIALOG(data)->onAttrChanged(repr, name, new_value);
+}
-/**
- * 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);
+Inkscape::XML::NodeEventVector css_repr_events = {
+ nullptr, /* child_added */
+ nullptr, /* child_removed */
+ on_attr_changed, nullptr, /* content_changed */
+ nullptr /* order_changed */
+};
namespace Inkscape {
namespace UI {
namespace Dialog {
-// Keeps a watch on style element
-class StyleDialog::NodeObserver : public Inkscape::XML::NodeObserver {
-public:
- NodeObserver(StyleDialog* styleDialog) :
- _styleDialog(styleDialog)
- {
- g_debug("StyleDialog::NodeObserver: Constructor");
- };
-
- void notifyContentChanged(Inkscape::XML::Node &node,
- Inkscape::Util::ptr_shared old_content,
- Inkscape::Util::ptr_shared new_content) override;
-
- StyleDialog * _styleDialog;
-};
-
-
-void
-StyleDialog::NodeObserver::notifyContentChanged(
- Inkscape::XML::Node &/*node*/,
- Inkscape::Util::ptr_shared /*old_content*/,
- Inkscape::Util::ptr_shared /*new_content*/ ) {
-
- g_debug("StyleDialog::NodeObserver::notifyContentChanged");
- _styleDialog->_updating = false;
- _styleDialog->_readStyleElement();
- _styleDialog->_selectRow();
-}
-
-
-// Keeps a watch for new/removed/changed nodes
-// (Must update objects that selectors match.)
-class StyleDialog::NodeWatcher : public Inkscape::XML::NodeObserver {
-public:
- NodeWatcher(StyleDialog* styleDialog, Inkscape::XML::Node *repr) :
- _styleDialog(styleDialog),
- _repr(repr)
- {
- g_debug("StyleDialog::NodeWatcher: Constructor");
- };
-
- void notifyChildAdded( Inkscape::XML::Node &/*node*/,
- Inkscape::XML::Node &child,
- Inkscape::XML::Node */*prev*/ ) override
- {
- if ( _styleDialog && _repr ) {
- _styleDialog->_nodeAdded( child );
- }
- }
-
- void notifyChildRemoved( Inkscape::XML::Node &/*node*/,
- Inkscape::XML::Node &child,
- Inkscape::XML::Node */*prev*/ ) override
- {
- if ( _styleDialog && _repr ) {
- _styleDialog->_nodeRemoved( child );
- }
- }
-
- void notifyAttributeChanged( Inkscape::XML::Node &node,
- GQuark qname,
- Util::ptr_shared /*old_value*/,
- Util::ptr_shared /*new_value*/ ) override {
- if ( _styleDialog && _repr ) {
-
- // For the moment only care about attributes that are directly used in selectors.
- const gchar * cname = g_quark_to_string (qname );
- Glib::ustring name;
- if (cname) {
- name = cname;
- }
-
- if ( name == "id" || name == "class" ) {
- _styleDialog->_nodeChanged( node );
- }
- }
- }
-
- StyleDialog * _styleDialog;
- Inkscape::XML::Node * _repr; // Need to track if document changes.
-};
-
-void
-StyleDialog::_nodeAdded( Inkscape::XML::Node &node ) {
-
- StyleDialog::NodeWatcher *w = new StyleDialog::NodeWatcher (this, &node);
- node.addObserver (*w);
- _nodeWatchers.push_back(w);
-
- _readStyleElement();
- _selectRow();
-}
-
-void
-StyleDialog::_nodeRemoved( Inkscape::XML::Node &repr ) {
-
- for (auto it = _nodeWatchers.begin(); it != _nodeWatchers.end(); ++it) {
- if ( (*it)->_repr == &repr ) {
- (*it)->_repr->removeObserver (**it);
- _nodeWatchers.erase( it );
- break;
- }
- }
-
- _readStyleElement();
- _selectRow();
-}
-
-void
-StyleDialog::_nodeChanged( Inkscape::XML::Node &object ) {
-
- _readStyleElement();
- _selectRow();
-}
-
-StyleDialog::TreeStore::TreeStore()
-= default;
-
-
-/**
- * Allow dragging only selectors.
- */
-bool
-StyleDialog::TreeStore::row_draggable_vfunc(const Gtk::TreeModel::Path& path) const
-{
- g_debug("StyleDialog::TreeStore::row_draggable_vfunc");
-
- auto unconstThis = const_cast<StyleDialog::TreeStore*>(this);
- const_iterator iter = unconstThis->get_iter(path);
- if (iter) {
- Gtk::TreeModel::Row row = *iter;
- bool is_draggable =
- row[_styledialog->_mColumns._colType] == SELECTOR || row[_styledialog->_mColumns._colType] == UNHANDLED;
- return is_draggable;
- }
- return Gtk::TreeStore::row_draggable_vfunc(path);
-}
-
-
-/**
- * Allow dropping only in between other selectors.
- */
-bool
-StyleDialog::TreeStore::row_drop_possible_vfunc(const Gtk::TreeModel::Path& dest,
- const Gtk::SelectionData& selection_data) const
-{
- g_debug("StyleDialog::TreeStore::row_drop_possible_vfunc");
-
- Gtk::TreeModel::Path dest_parent = dest;
- dest_parent.up();
- return dest_parent.empty();
-}
-
-
-// This is only here to handle updating style element after a drag and drop.
-void
-StyleDialog::TreeStore::on_row_deleted(const TreeModel::Path& path)
-{
- if (_styledialog->_updating) return; // Don't write if we deleted row (other than from DND)
-
- g_debug("on_row_deleted");
-
- _styledialog->_writeStyleElement();
-}
-
-
-Glib::RefPtr<StyleDialog::TreeStore> StyleDialog::TreeStore::create(StyleDialog *styledialog)
-{
- StyleDialog::TreeStore * store = new StyleDialog::TreeStore();
- store->_styledialog = styledialog;
- store->set_column_types( store->_styledialog->_mColumns );
- return Glib::RefPtr<StyleDialog::TreeStore>( store );
-}
-
/**
* 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.
+ * A treeview whose each row corresponds to a CSS property of selector selected.
+ * New CSS property can be added by clicking '+' at bottom of the CSS pane. '-'
+ * in front of the CSS property row can be clicked to delete the CSS property.
+ * Besides clicking on an already selected property row makes the property editable
+ * and clicking 'Enter' updates the property with changes reflected in the
+ * drawing.
*/
-StyleDialog::StyleDialog() :
- UI::Widget::Panel("/dialogs/style", SP_VERB_DIALOG_STYLE),
- _updating(false),
- _textNode(nullptr),
- _desktopTracker()
+StyleDialog::StyleDialog()
+ : UI::Widget::Panel("/dialogs/css", SP_VERB_DIALOG_CSS)
+ , _desktop(nullptr)
+ , _repr(nullptr)
{
- g_debug("StyleDialog::StyleDialog");
-
- // Tree
- Inkscape::UI::Widget::IconRenderer * addRenderer = manage(
- new Inkscape::UI::Widget::IconRenderer() );
- addRenderer->add_icon("edit-delete");
- addRenderer->add_icon("list-add");
- addRenderer->add_icon("object-locked");
-
- _store = TreeStore::create(this);
- _treeView.set_model(_store);
+ set_size_request(20, 15);
_treeView.set_headers_visible(true);
- _treeView.enable_model_drag_source();
- _treeView.enable_model_drag_dest( Gdk::ACTION_MOVE );
- int addCol = _treeView.append_column("", *addRenderer) - 1;
- Gtk::TreeViewColumn *col = _treeView.get_column(addCol);
- if ( col ) {
- col->add_attribute(addRenderer->property_icon(), _mColumns._colType);
- }
- _treeView.append_column("CSS Selector", _mColumns._colSelector);
- _treeView.set_expander_column(*(_treeView.get_column(1)));
-
- // Pack widgets
- _paned.set_orientation(Gtk::ORIENTATION_VERTICAL);
- _paned.pack1(_mainBox, Gtk::SHRINK);
- _mainBox.set_orientation(Gtk::ORIENTATION_VERTICAL);
- _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));
-
- 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);
+ auto _scrolledWindow = new Gtk::ScrolledWindow();
+ _scrolledWindow->add(_treeView);
+ _scrolledWindow->set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC);
- _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);
-
-
- // 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.signal_row_expanded().connect(sigc::mem_fun(*this, &StyleDialog::_rowExpand));
-
- _treeView.signal_row_collapsed().connect(sigc::mem_fun(*this, &StyleDialog::_rowCollapse));
-
- // Document & Desktop
- _desktop_changed_connection = _desktopTracker.connectDesktopChanged(
- sigc::mem_fun(*this, &StyleDialog::_handleDesktopChanged) );
- _desktopTracker.connect(GTK_WIDGET(gobj()));
-
- _document_replaced_connection = getDesktop()->connectDocumentReplaced(
- sigc::mem_fun(this, &StyleDialog::_handleDocumentReplaced));
-
- _selection_changed_connection = getDesktop()->getSelection()->connectChanged(
- sigc::hide(sigc::mem_fun(this, &StyleDialog::_handleSelectionChanged)));
-
- // Add watchers
- _updateWatchers();
+ _store = Gtk::ListStore::create(_cssColumns);
+ _treeView.set_model(_store);
- // Load tree
- _readStyleElement();
- _selectRow();
+ Inkscape::UI::Widget::IconRenderer *addRenderer = manage(new Inkscape::UI::Widget::IconRenderer());
+ addRenderer->add_icon("edit-delete");
+ addRenderer->signal_activated().connect(sigc::mem_fun(*this, &StyleDialog::onPropertyDelete));
- if (!_store->children().empty()) {
- del->set_sensitive(true);
- }
+ _message_stack = std::make_shared<Inkscape::MessageStack>();
+ _message_context = std::unique_ptr<Inkscape::MessageContext>(new Inkscape::MessageContext(_message_stack));
+ _message_changed_connection =
+ _message_stack->connectChanged(sigc::bind(sigc::ptr_fun(_set_status_message), GTK_WIDGET(status.gobj())));
+ int addCol = _treeView.append_column("", *addRenderer) - 1;
+ Gtk::TreeViewColumn *col = _treeView.get_column(addCol);
+ if (col) {
+ col->add_attribute(addRenderer->property_visible(), _cssColumns.deleteButton);
+ col->set_sort_column(_cssColumns.deleteButton);
+
+ Gtk::Image *add_icon = Gtk::manage(sp_get_icon_image("list-add", Gtk::ICON_SIZE_SMALL_TOOLBAR));
+ col->set_clickable(true);
+ col->set_widget(*add_icon);
+ add_icon->set_tooltip_text(_("Add a new style property"));
+ add_icon->show();
+ // This gets the GtkButton inside the GtkBox, inside the GtkAlignment, inside the GtkImage icon.
+ auto button = add_icon->get_parent()->get_parent()->get_parent();
+ // Assign the button event so that create happens BEFORE delete. If this code
+ // isn't in this exact way, the onAttrDelete is called when the header lines are pressed.
+ button->signal_button_release_event().connect(sigc::mem_fun(*this, &StyleDialog::onPropertyCreate), false);
+ }
+
+ Gtk::CellRendererText *renderer = Gtk::manage(new Gtk::CellRendererText());
+ _treeView.set_reorderable(false);
+ renderer->property_editable() = true;
+ int nameColNum = _treeView.append_column("Property", *renderer) - 1;
+ _propCol = _treeView.get_column(nameColNum);
+ if (_propCol) {
+ _propCol->add_attribute(renderer->property_text(), _cssColumns.label);
+ _propCol->add_attribute(renderer->property_foreground_rgba(), _cssColumns.label_color);
+ _propCol->set_sort_column(_cssColumns.label);
+ }
+
+ renderer = Gtk::manage(new Gtk::CellRendererText());
+ renderer->property_editable() = false;
+ int attrColNum = _treeView.append_column("Set", *renderer) - 1;
+ _attrCol = _treeView.get_column(attrColNum);
+ if (_attrCol) {
+ _attrCol->add_attribute(renderer->property_text(), _cssColumns._styleAttrVal);
+ _attrCol->add_attribute(renderer->property_foreground_rgba(), _cssColumns.attr_color);
+ _attrCol->add_attribute(renderer->property_strikethrough(), _cssColumns.attr_strike);
+ _attrCol->add_attribute(renderer->property_editable(), _cssColumns.editable);
+ _attrCol->set_sort_column(_cssColumns._styleAttrVal);
+ }
+
+ renderer = Gtk::manage(new Gtk::CellRendererText());
+ renderer->property_editable() = true;
+ int sheetColNum = _treeView.append_column("Actual", *renderer) - 1;
+ _sheetCol = _treeView.get_column(sheetColNum);
+ if (_sheetCol) {
+ _sheetCol->add_attribute(renderer->property_text(), _cssColumns._styleSheetVal);
+ _sheetCol->add_attribute(renderer->property_foreground_rgba(), _cssColumns.label_color);
+ _sheetCol->set_sort_column(_cssColumns._styleSheetVal);
+ }
+
+ // Set the initial sort column (and direction) to place real attributes at the top.
+ _store->set_sort_column(_cssColumns.deleteButton, Gtk::SORT_DESCENDING);
+
+ _getContents()->pack_start(*_scrolledWindow, Gtk::PACK_EXPAND_WIDGET);
+
+ css_reset_context(0);
+ setDesktop(getDesktop());
}
-
/**
* @brief StyleDialog::~StyleDialog
* Class destructor
*/
StyleDialog::~StyleDialog()
{
- g_debug("StyleDialog::~StyleDialog");
- _desktop_changed_connection.disconnect();
- _document_replaced_connection.disconnect();
- _selection_changed_connection.disconnect();
+ setDesktop(nullptr);
+ _repr = nullptr;
+ _message_changed_connection.disconnect();
+ _message_context = nullptr;
+ _message_stack = nullptr;
+ _message_changed_connection.~connection();
}
-
-/**
- * @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.
- */
-Inkscape::XML::Node* StyleDialog::_getStyleTextNode()
+void StyleDialog::_set_status_message(Inkscape::MessageType /*type*/, const gchar *message, GtkWidget *widget)
{
-
- Inkscape::XML::Node *styleNode = nullptr;
- Inkscape::XML::Node *textNode = nullptr;
-
- Inkscape::XML::Node *root = SP_ACTIVE_DOCUMENT->getReprRoot();
- for (unsigned i = 0; i < root->childCount(); ++i) {
- if (Glib::ustring(root->nthChild(i)->name()) == "svg:style") {
-
- styleNode = root->nthChild(i);
-
- for (unsigned j = 0; j < styleNode->childCount(); ++j) {
- if (styleNode->nthChild(j)->type() == Inkscape::XML::TEXT_NODE) {
- textNode = styleNode->nthChild(j);
- }
- }
-
- if (textNode == nullptr) {
- // Style element found but does not contain text node!
- std::cerr << "StyleDialog::_getStyleTextNode(): No text node!" << std::endl;
- textNode = SP_ACTIVE_DOCUMENT->getReprDoc()->createTextNode("");
- styleNode->appendChild(textNode);
- Inkscape::GC::release(textNode);
- }
- }
- }
-
- if (styleNode == nullptr) {
- // Style element not found, create one
- styleNode = SP_ACTIVE_DOCUMENT->getReprDoc()->createElement("svg:style");
- textNode = SP_ACTIVE_DOCUMENT->getReprDoc()->createTextNode("");
-
- styleNode->appendChild(textNode);
- Inkscape::GC::release(textNode);
-
- root->addChild(styleNode, nullptr);
- Inkscape::GC::release(styleNode);
+ if (widget) {
+ gtk_label_set_markup(GTK_LABEL(widget), message ? message : "");
}
-
- if (_textNode != textNode) {
- _textNode = textNode;
- NodeObserver *no = new NodeObserver(this);
- textNode->addObserver(*no);
- }
-
- return textNode;
}
/**
- * @brief StyleDialog::_readStyleElement
- * Fill the Gtk::TreeStore from the svg:style element.
+ * @brief StyleDialog::setDesktop
+ * @param desktop
+ * This function sets the 'desktop' for the CSS pane.
*/
-void StyleDialog::_readStyleElement()
+void StyleDialog::setDesktop(SPDesktop* desktop)
{
- g_debug("StyleDialog::_readStyleElement: updating %s", (_updating ? "true" : "false"));
-
- if (_updating) return; // Don't read if we wrote style element.
- _updating = true;
-
- Inkscape::XML::Node * textNode = _getStyleTextNode();
- if (textNode == nullptr) {
- std::cerr << "StyleDialog::_readStyleElement: No text node!" << std::endl;
- }
-
- // Get content from style text node.
- std::string content = (textNode->content() ? textNode->content() : "");
-
- // Remove end-of-lines (check it works on Windoze).
- content.erase(std::remove(content.begin(), content.end(), '\n'), content.end());
-
- // Remove comments (/* xxx */)
- while(content.find("/*") != std::string::npos) {
- size_t start = content.find("/*");
- content.erase(start, (content.find("*/", start) - start) +2);
- }
-
- // First split into selector/value chunks.
- // An attempt to use Glib::Regex failed. A C++11 version worked but
- // reportedly has problems on Windows. Using split_simple() is simpler
- // and probably faster.
- //
- // Glib::RefPtr<Glib::Regex> regex1 =
- // Glib::Regex::create("([^\\{]+)\\{([^\\{]+)\\}");
- //
- // Glib::MatchInfo minfo;
- // regex1->match(content, minfo);
-
- // Split on curly brackets. Even tokens are selectors, odd are values.
- std::vector<Glib::ustring> tokens = Glib::Regex::split_simple("[}{]", content);
-
- // If text node is empty, return (avoids problem with negative below).
- if (tokens.size() == 0) {
- _updating = false;
- return;
- }
- std::vector<std::pair<Glib::ustring, bool>> expanderstatus;
- for (unsigned i = 0; i < tokens.size() - 1; i += 2) {
- Glib::ustring selector = tokens[i];
- REMOVE_SPACES(selector); // Remove leading/trailing spaces
- for (auto &row : _store->children()) {
- Glib::ustring selectorold = row[_mColumns._colSelector];
- if (selectorold == selector) {
- expanderstatus.emplace_back(selector, row[_mColumns._colExpand]);
- }
- }
- }
- _store->clear();
-
- for (unsigned i = 0; i < tokens.size()-1; i += 2) {
-
- Glib::ustring selector = tokens[i];
- REMOVE_SPACES(selector); // Remove leading/trailing spaces
- std::vector<Glib::ustring> tokensplus = Glib::Regex::split_simple("[,]+", selector);
- coltype colType = SELECTOR;
- for (auto tok : tokensplus) {
- REMOVE_SPACES(tok);
- if (tok.find(" ") != -1 || tok.erase(0, 1).find(".") != -1) {
- colType = UNHANDLED;
- }
- }
- // Get list of objects selector matches
- std::vector<SPObject *> objVec = _getObjVec( selector );
-
- Glib::ustring properties;
- // Check to make sure we do have a value to match selector.
- if ((i+1) < tokens.size()) {
- properties = tokens[i+1];
- } else {
- std::cerr << "StyleDialog::_readStyleElement: Missing values "
- "for last selector!" << std::endl;
- }
- REMOVE_SPACES(properties);
- bool colExpand = false;
- for (auto rowstatus : expanderstatus) {
- if (selector == rowstatus.first) {
- colExpand = rowstatus.second;
- }
- }
- Gtk::TreeModel::Row row = *(_store->append());
- row[_mColumns._colSelector] = selector;
- row[_mColumns._colExpand] = colExpand;
- row[_mColumns._colType] = colType;
- row[_mColumns._colObj] = objVec;
- row[_mColumns._colProperties] = properties;
- if (colType == SELECTOR) {
- // 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._colExpand] = false;
- childrow[_mColumns._colType] = OBJECT;
- childrow[_mColumns._colObj] = std::vector<SPObject *>(1, obj);
- childrow[_mColumns._colProperties] = ""; // Unused
- }
- }
- }
- _updating = false;
-}
-
-void StyleDialog::_rowExpand(const Gtk::TreeModel::iterator &iter, const Gtk::TreeModel::Path &path)
-{
- g_debug("StyleDialog::_row_expand()");
- Gtk::TreeModel::Row row = *iter;
- row[_mColumns._colExpand] = true;
+ _desktop = desktop;
}
-void StyleDialog::_rowCollapse(const Gtk::TreeModel::iterator &iter, const Gtk::TreeModel::Path &path)
-{
- g_debug("StyleDialog::_row_collapse()");
- Gtk::TreeModel::Row row = *iter;
- row[_mColumns._colExpand] = false;
-}
/**
- * @brief StyleDialog::_writeStyleElement
- * Update the content of the style element as selectors (or objects) are added/removed.
+ * @brief StyleDialog::setRepr
+ *
+ * Set the internal xml object that I'm working on right now.
*/
-void StyleDialog::_writeStyleElement()
+void StyleDialog::setRepr(Inkscape::XML::Node *repr)
{
- if (_updating) {
+ if (repr == _repr)
return;
+ if (_repr) {
+ _store->clear();
+ _repr->removeListenerByData(this);
+ Inkscape::GC::release(_repr);
+ _repr = nullptr;
+ }
+ _repr = repr;
+ if (repr) {
+ Inkscape::GC::anchor(_repr);
+ _repr->addListener(&css_repr_events, this);
+ _repr->synthesizeEvents(&css_repr_events, this);
}
- _updating = true;
-
- Glib::ustring styleContent;
- for (auto& row: _store->children()) {
- Glib::ustring selector = row[_mColumns._colSelector];
- /*
- REMOVE_SPACES(selector);
- /* size_t len = selector.size();
- if(selector[len-1] == ','){
- selector.erase(len-1);
- }
- row[_mColumns._colSelector] = selector; */
- styleContent = styleContent + selector + " { " + row[_mColumns._colProperties] + " }\n";
- }
- // We could test if styleContent is empty and then delete the style node here but there is no
- // harm in keeping it around ...
-
- Inkscape::XML::Node *textNode = _getStyleTextNode();
- textNode->setContent(styleContent.c_str());
-
- DocumentUndo::done(SP_ACTIVE_DOCUMENT, SP_VERB_DIALOG_STYLE, _("Edited style element."));
-
- _updating = false;
- g_debug("StyleDialog::_writeStyleElement(): | %s |", styleContent.c_str());
-}
-
-
-void StyleDialog::_addWatcherRecursive(Inkscape::XML::Node *node) {
-
- g_debug("StyleDialog::_addWatcherRecursive()");
-
- StyleDialog::NodeWatcher *w = new StyleDialog::NodeWatcher(this, node);
- node->addObserver(*w);
- _nodeWatchers.push_back(w);
-
- for (unsigned i = 0; i < node->childCount(); ++i) {
- _addWatcherRecursive(node->nthChild(i));
- }
-}
-
-/**
- * @brief StyleDialog::_updateWatchers
- * Update the watchers on objects.
- */
-void StyleDialog::_updateWatchers()
-{
- _updating = true;
-
- // Remove old document watchers
- while (!_nodeWatchers.empty()) {
- StyleDialog::NodeWatcher *w = _nodeWatchers.back();
- w->_repr->removeObserver(*w);
- _nodeWatchers.pop_back();
- delete w;
- }
-
- // Recursively add new watchers
- Inkscape::XML::Node *root = SP_ACTIVE_DOCUMENT->getReprRoot();
- _addWatcherRecursive(root);
-
- g_debug("StyleDialog::_updateWatchers(): %d", (int)_nodeWatchers.size());
-
- _updating = false;
}
-
/**
- * @brief StyleDialog::_addToSelector
- * @param row
- * Add selected objects on the desktop to the selector corresponding to 'row'.
+ * @brief StyleDialog::parseStyle
+ *
+ * Convert a style string into a vector map. This should be moved to style.cpp
+ *
*/
-void StyleDialog::_addToSelector(Gtk::TreeModel::Row row)
+std::map<Glib::ustring, Glib::ustring> StyleDialog::parseStyle(Glib::ustring style_string)
{
- g_debug("StyleDialog::_addToSelector: Entrance");
- if (*row) {
-
- Glib::ustring selector = row[_mColumns._colSelector];
+ std::map<Glib::ustring, Glib::ustring> ret;
- if (selector[0] == '#') {
- // 'id' selector... add selected object's id's to list.
- Inkscape::Selection* selection = getDesktop()->getSelection();
- for (auto& obj: selection->objects()) {
-
- Glib::ustring id = (obj->getId()?obj->getId():"");
-
- std::vector<SPObject *> objVec = row[_mColumns._colObj];
- bool found = false;
- for (auto& obj: objVec) {
- if (id == obj->getId()) {
- found = true;
- break;
- }
- }
-
- 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 );
- row[_mColumns._colExpand] = true;
- // Add child row
- Gtk::TreeModel::Row childrow = *(_store->append(row->children()));
- childrow[_mColumns._colSelector] = "#" + Glib::ustring(obj->getId());
- childrow[_mColumns._colType] = OBJECT;
- childrow[_mColumns._colObj] = std::vector<SPObject *>(1, obj);
- childrow[_mColumns._colProperties] = ""; // Unused
- }
- }
- }
-
- else if (selector[0] == '.') {
- // 'class' selector... add value to class attribute of selected objects.
-
- // Get first class (split on white space or comma)
- std::vector<Glib::ustring> tokens = Glib::Regex::split_simple("[,\\s]+", selector);
- Glib::ustring className = tokens[0];
- className.erase(0,1);
- Inkscape::Selection* selection = getDesktop()->getSelection();
- std::vector<SPObject *> sel_obj(selection->objects().begin(), selection->objects().end());
- _insertClass(sel_obj, className);
- std::vector<SPObject *> objVec = _getObjVec(selector);
- ;
- for (auto &obj : sel_obj) {
-
- Glib::ustring id = (obj->getId() ? obj->getId() : "");
- bool found = false;
- for (auto &obj : objVec) {
- if (id == obj->getId()) {
- found = true;
- break;
- }
- }
+ REMOVE_SPACES(style_string); // We'd use const, but we need to trip spaces
+ std::vector<Glib::ustring> props = r_props->split(style_string);
- if (!found) {
- // Update row
- objVec.push_back(obj); // Adding to copy so need to update tree
- row[_mColumns._colObj] = objVec;
- row[_mColumns._colExpand] = true;
-
- // Update row
- Gtk::TreeModel::Row childrow = *(_store->append(row->children()));
- childrow[_mColumns._colSelector] = "#" + Glib::ustring(obj->getId());
- childrow[_mColumns._colExpand] = false;
- childrow[_mColumns._colType] = OBJECT;
- childrow[_mColumns._colObj] = std::vector<SPObject *>(1, obj);
- childrow[_mColumns._colProperties] = ""; // Unused
- }
- }
- }
+ for (auto const token : props) {
+ if (token.empty())
+ break;
+ std::vector<Glib::ustring> pair = r_pair->split(token);
- else {
- // Do nothing for element selectors.
- // std::cout << " Element selector... doing nothing!" << std::endl;
+ if (pair.size() > 1) {
+ ret[pair[0]] = pair[1];
}
}
-
- _writeStyleElement();
+ return ret;
}
-
/**
- * @brief StyleDialog::_removeFromSelector
- * @param row
- * Remove the object corresponding to 'row' from the parent selector.
+ * @brief StyleDialog::compileStyle
+ *
+ * Turn a vector map back into a style string.
+ *
*/
-void StyleDialog::_removeFromSelector(Gtk::TreeModel::Row row)
+Glib::ustring StyleDialog::compileStyle(std::map<Glib::ustring, Glib::ustring> props)
{
- g_debug("StyleDialog::_removeFromSelector: Entrance");
- 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];
- REMOVE_SPACES(selector);
- 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
- REMOVE_SPACES(selector);
- if (i != Glib::ustring::npos && selector[i] == ',') {
- selector.erase(i, 1);
- }
- if (i != Glib::ustring::npos && selector[i] == ' ') {
- selector.erase(i, 1);
- }
- REMOVE_SPACES(selector);
- if (selector[selector.size() - 1] == ',') {
- selector.erase(selector.size() - 1, 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 );
- parent[_mColumns._colExpand] = true;
- _store->erase(row);
- }
- }
-
- else if (selector[0] == '.') {
- // 'class' selector... remove value to class attribute of selected objects.
-
- std::vector<SPObject *> objVec = row[_mColumns._colObj]; // Just one
- // Get first class (split on white space or comma)
- std::vector<Glib::ustring> tokens = Glib::Regex::split_simple("[,\\s]+", selector);
- Glib::ustring className = tokens[0];
- className.erase(0, 1);
- // Erase 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);
- }
- _store->erase(row);
- objVec[0]->getRepr()->setAttribute("class", classAttr);
- parent[_mColumns._colExpand] = true;
- } else {
- // Do nothing for element selectors.
- // std::cout << " Element selector... doing nothing!" << std::endl;
- }
+ auto ret = Glib::ustring("");
+ for (auto const pair : props) {
+ if (!pair.first.empty() && !pair.second.empty()) {
+ ret += pair.first;
+ ret += ":";
+ ret += pair.second;
+ ret += ";";
}
}
- _writeStyleElement();
-}
-
-
-/**
- * @brief StyleDialog::_getIdList
- * @param sel
- * @return This function returns a comma separated list of ids for objects in input vector.
- * It is used in creating an 'id' selector. It relies on objects having 'id's.
- */
-Glib::ustring StyleDialog::_getIdList(std::vector<SPObject*> sel)
-{
- Glib::ustring str;
- for (auto& obj: sel) {
- str += "#" + Glib::ustring(obj->getId()) + ", ";
- }
- if (!str.empty()) {
- str.erase(str.size()-1); // Remove space at end. c++11 has pop_back() but not ustring.
- str.erase(str.size()-1); // Remove comma at end.
- }
- return str;
-}
-
-/**
- * @brief StyleDialog::_getObjVec
- * @param selector: a valid CSS selector string.
- * @return objVec: a vector of pointers to SPObject's the selector matches.
- * Return a vector of all objects that selector matches.
- */
-std::vector<SPObject *> StyleDialog::_getObjVec(Glib::ustring selector) {
-
- std::vector<SPObject *> objVec = SP_ACTIVE_DOCUMENT->getObjectsBySelector( selector );
-
- g_debug("StyleDialog::_getObjVec: | %s |", selector.c_str());
- for (auto& obj: objVec) {
- g_debug(" %s", obj->getId() ? obj->getId() : "null");
- }
-
- return objVec;
+ return ret;
}
/**
- * @brief StyleDialog::_insertClass
- * @param objs: list of objects to insert class
- * @param class: class to insert
- * Insert a class name into objects' 'class' attribute.
- */
-void StyleDialog::_insertClass(const std::vector<SPObject *>& objVec, const Glib::ustring& className) {
-
- for (auto& obj: objVec) {
-
- if (!obj->getRepr()->attribute("class")) {
- // 'class' attribute does not exist, create it.
- obj->getRepr()->setAttribute("class", className);
- } else {
- // 'class' attribute exists, append.
- Glib::ustring classAttr = obj->getRepr()->attribute("class");
-
- // Split on white space.
- std::vector<Glib::ustring> tokens = Glib::Regex::split_simple("\\s+", classAttr);
- bool add = true;
- for (auto& token: tokens) {
- if (token == className) {
- add = false; // Might be useful to still add...
- break;
- }
- }
- if (add) {
- obj->getRepr()->setAttribute("class", classAttr + " " + className );
- }
- }
- }
- }
-
-
-/**
- * @brief StyleDialog::_selectObjects
- * @param eventX
- * @param eventY
- * This function selects objects in the drawing corresponding to the selector
- * selected in the treeview.
+ * @brief StyleDialog::onAttrChanged
+ *
+ * This is called when the XML has an updated attribute (we only care about style)
*/
-void StyleDialog::_selectObjects(int eventX, int eventY)
+void StyleDialog::onAttrChanged(Inkscape::XML::Node *repr, const gchar *name, const gchar *new_value)
{
- g_debug("StyleDialog::_selectObjects: %d, %d", eventX, eventY);
+ if (strcmp(name, "style") != 0)
+ return;
- getDesktop()->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();
- if (children.empty()) {
- del->set_sensitive(true);
- }
- std::vector<SPObject *> objVec = row[_mColumns._colObj];
+ // Clear the list and return if the new_value is empty
+ _store->clear();
+ if (!new_value || new_value[0] == 0)
+ return;
- for (auto obj : objVec) {
- getDesktop()->selection->add(obj);
+ // Get the object's style attribute and it's calculated properties
+ SPDocument *document = this->_desktop->doc();
+ SPObject *obj = document->getObjectByRepr(repr);
+ // std::vector<SPIBase *> calc_prop = obj->style->properties();
+
+ // Get a dictionary lookup of the style in the attribute
+ std::map<Glib::ustring, Glib::ustring> attr_prop = parseStyle(new_value);
+
+ for (auto iter : obj->style->properties()) {
+ if (iter->style && iter->style_src != SP_STYLE_SRC_UNSET) {
+ Gtk::TreeModel::Row row = *(_store->append());
+ // Delete is available to attribute properties only in attr mode.
+ row[_cssColumns.deleteButton] = iter->style_src == SP_STYLE_SRC_ATTRIBUTE;
+ row[_cssColumns.label] = iter->name;
+ if (attr_prop.count(iter->name)) {
+ row[_cssColumns._styleAttrVal] = attr_prop[iter->name];
+ if (attr_prop[iter->name] != iter->get_value()) {
+ row[_cssColumns._styleSheetVal] = iter->get_value();
+ row[_cssColumns.attr_color] = Gdk::RGBA("gray");
+ row[_cssColumns.attr_strike] = true;
}
+ row[_cssColumns.deleteButton] = true;
+ } else {
+ row[_cssColumns._styleSheetVal] = iter->get_value();
+ row[_cssColumns.label_color] = Gdk::RGBA("gray");
+ row[_cssColumns.attr_color] = Gdk::RGBA("gray");
+ row[_cssColumns.deleteButton] = false;
}
}
}
}
-
-/**
- * @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.
+/*
+ * Sets the CSSDialog status bar, depending on which attr is selected.
*/
-void StyleDialog::_addSelector()
+void StyleDialog::css_reset_context(gint css)
{
- g_debug("StyleDialog::_addSelector: Entrance");
-
- // Store list of selected elements on desktop (not to be confused with selector).
- Inkscape::Selection* selection = getDesktop()->getSelection();
- std::vector<SPObject *> objVec( selection->objects().begin(),
- selection->objects().end() );
-
- // ==== Create popup dialog ====
- Gtk::Dialog *textDialogPtr = new Gtk::Dialog();
- textDialogPtr->add_button(_("Cancel"), Gtk::RESPONSE_CANCEL);
- textDialogPtr->add_button(_("Add"), Gtk::RESPONSE_OK);
-
- Gtk::Entry *textEditPtr = manage ( new Gtk::Entry() );
- textEditPtr->signal_activate().connect(
- sigc::bind<Gtk::Dialog *>(sigc::mem_fun(*this, &StyleDialog::_closeDialog), textDialogPtr));
- textDialogPtr->get_content_area()->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_content_area()->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 (getDesktop()->getSelection()->isEmpty()) {
- textEditPtr->set_text(".Class1");
+ if (css == 0) {
+ _message_context->set(Inkscape::NORMAL_MESSAGE, _("<b>Click</b> CSS property to edit."));
} else {
- textEditPtr->set_text(_getIdList(objVec));
- }
-
- Gtk::Requisition sreq1, sreq2;
- textDialogPtr->get_preferred_size(sreq1, sreq2);
- int minWidth = 200;
- int minHeight = 100;
- minWidth = (sreq2.width > minWidth ? sreq2.width : minWidth );
- minHeight = (sreq2.height > minHeight ? sreq2.height : minHeight);
- textDialogPtr->set_size_request(minWidth, minHeight);
- textEditPtr->show();
- textLabelPtr->hide();
- textDialogPtr->show();
-
-
- // ==== Get response ====
- int result = -1;
- bool invalid = true;
- Glib::ustring selectorValue;
- bool handled = true;
- while (invalid) {
- result = textDialogPtr->run();
- if (result != Gtk::RESPONSE_OK) { // Cancel, close dialog, etc.
- textDialogPtr->hide();
- delete textDialogPtr;
- return;
- }
- /**
- * @brief selectorName
- * This string stores selector name. The text from entrybox is saved as name
- * for selector. If the entrybox is empty, the text (thus selectorName) is
- * set to ".Class1"
- */
- selectorValue = textEditPtr->get_text();
- Glib::ustring firstWord = selectorValue.substr(0, selectorValue.find_first_of(" >+~"));
- if (firstWord != selectorValue) {
- handled = false;
- }
- del->set_sensitive(true);
-
- if (selectorValue[0] == '.' || selectorValue[0] == '#' || selectorValue[0] == '*' ||
- SPAttributeRelSVG::isSVGElement(selectorValue)) {
- invalid = false;
- } else {
- textLabelPtr->show();
- }
+ const gchar *name = g_quark_to_string(css);
+ _message_context->setF(
+ Inkscape::NORMAL_MESSAGE,
+ _("Property <b>%s</b> selected. Press <b>Ctrl+Enter</b> when done editing to commit changes."), name);
}
- delete textDialogPtr;
- // ==== Handle response ====
-
- // If class selector, add selector name to class attribute for each object
- if (selectorValue[0] == '.' && handled) {
- std::vector<Glib::ustring> tokens = Glib::Regex::split_simple("[,\\s]+", selectorValue);
- Glib::ustring originClassName = tokens[0];
- originClassName.erase(0, 1);
- std::vector<Glib::ustring> classes = Glib::Regex::split_simple("[\\.]+", originClassName);
- if (classes.size() == 1) {
- _insertClass(objVec, classes[0]);
- } else {
- handled = false;
- }
- }
-
- // Generate a new object vector (we could have an element selector,
- // the user could have edited the id selector list, etc.).
- objVec = _getObjVec( selectorValue );
-
- // Add entry to GUI tree
- Gtk::TreeModel::Row row = *(_store->append());
- row[_mColumns._colSelector] = selectorValue;
- row[_mColumns._colExpand] = true;
- row[_mColumns._colType] = handled ? SELECTOR : UNHANDLED;
- row[_mColumns._colObj] = objVec;
-
- // Add as children objects that match selector.
- if (handled) {
- for (auto &obj : objVec) {
- Gtk::TreeModel::Row childrow = *(_store->append(row->children()));
- childrow[_mColumns._colSelector] = "#" + Glib::ustring(obj->getId());
- childrow[_mColumns._colExpand] = false;
- childrow[_mColumns._colType] = OBJECT;
- childrow[_mColumns._colObj] = std::vector<SPObject *>(1, obj);
- }
- }
-
- // Add entry to style element
- _writeStyleElement();
}
-void StyleDialog::_closeDialog(Gtk::Dialog *textDialogPtr) { textDialogPtr->response(Gtk::RESPONSE_OK); }
-
/**
- * @brief StyleDialog::_delSelector
- * This function deletes selector when '-' at the bottom is clicked.
- * Note: If deleting a class selector, class attributes are NOT changed.
+ * @brief StyleDialog::setStyleProperty
+ *
+ * Set or delete a single property in the style attribute.
*/
-void StyleDialog::_delSelector()
+bool StyleDialog::setStyleProperty(Glib::ustring name, Glib::ustring value)
{
- g_debug("StyleDialog::_delSelector");
-
- Glib::RefPtr<Gtk::TreeSelection> refTreeSelection = _treeView.get_selection();
- _treeView.get_selection()->set_mode(Gtk::SELECTION_SINGLE);
- Gtk::TreeModel::iterator iter = refTreeSelection->get_selected();
- if (iter) {
- Gtk::TreeModel::Row row = *iter;
- if (!row.children().empty()) {
- return;
+ auto original = this->_repr->attribute("style");
+ std::map<Glib::ustring, Glib::ustring> properties = parseStyle(original);
+
+ bool updated = false;
+ if (!value.empty()) {
+ if (properties[name] != value) {
+ // Set value (create or update)
+ properties[name] = value;
+ updated = true;
}
- _updating = true;
- _store->erase(iter);
- _updating = false;
- _writeStyleElement();
- del->set_sensitive(false);
+ } else if (properties.count(name)) {
+ // Delete value
+ properties.erase(name);
+ updated = true;
}
-}
-
-/**
- * @brief StyleDialog::_handleButtonEvent
- * @param event
- * @return
- * 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)
-{
- g_debug("StyleDialog::_handleButtonEvent: Entrance");
- if (event->type == GDK_BUTTON_RELEASE && event->button == 1) {
- Gtk::TreeViewColumn *col = nullptr;
- 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)) {
- bool remove_parent = false;
- Gtk::TreeModel::iterator iter = _store->get_iter(path);
- Gtk::TreeModel::Row row = *iter;
- Glib::RefPtr<Gtk::TreeSelection> sel = _treeView.get_selection();
- sel->select(row);
- // Add or remove objects from a
- Gtk::TreeModel::Row row_to_sel;
- if (!row.parent()) {
- row_to_sel = row;
- _addToSelector(row);
- } else {
- row_to_sel = *row.parent();
- _removeFromSelector(row);
- }
- }
- }
+ if (updated) {
+ auto new_styles = this->compileStyle(properties);
+ this->_repr->setAttribute("style", new_styles, false);
+ // this->setUndo(_("Delete style property"));
}
- return false;
+ return updated;
}
-// -------------------------------------------------------------------
-
-class PropertyData
-{
-public:
- PropertyData() = default;;
- PropertyData(Glib::ustring name) : _name(std::move(name)) {};
-
- void _setSheetValue(Glib::ustring value) { _sheetValue = value; };
- void _setAttrValue(Glib::ustring value) { _attrValue = value; };
- Glib::ustring _getName() { return _name; };
- Glib::ustring _getSheetValue() { return _sheetValue; };
- Glib::ustring _getAttrValue() { return _attrValue; };
-
-private:
- Glib::ustring _name;
- Glib::ustring _sheetValue;
- Glib::ustring _attrValue;
-};
-
-// -------------------------------------------------------------------
-
-
/**
- * Handle document replaced. (Happens when a default document is immediately replaced by another
- * document in a new window.)
- */
-void
-StyleDialog::_handleDocumentReplaced(SPDesktop *desktop, SPDocument * /* document */)
-{
- g_debug("StyleDialog::handleDocumentReplaced()");
-
- _selection_changed_connection.disconnect();
-
- _selection_changed_connection = desktop->getSelection()->connectChanged(
- sigc::hide(sigc::mem_fun(this, &StyleDialog::_handleSelectionChanged)));
-
- _updateWatchers();
- _readStyleElement();
- _selectRow();
-}
-
-
-/*
- * When a dialog is floating, it is connected to the active desktop.
- */
-void
-StyleDialog::_handleDesktopChanged(SPDesktop* desktop) {
- g_debug("StyleDialog::handleDesktopReplaced()");
-
- if (getDesktop() == desktop) {
- // This will happen after construction of dialog. We've already
- // set up signals so just return.
- return;
- }
-
- _selection_changed_connection.disconnect();
- _document_replaced_connection.disconnect();
-
- setDesktop( desktop );
-
- _selection_changed_connection = desktop->getSelection()->connectChanged(
- sigc::hide(sigc::mem_fun(this, &StyleDialog::_handleSelectionChanged)));
- _document_replaced_connection = desktop->connectDocumentReplaced(
- sigc::mem_fun(this, &StyleDialog::_handleDocumentReplaced));
-
- _updateWatchers();
- _readStyleElement();
- _selectRow();
-}
-
-
-/*
- * Handle a change in which objects are selected in a document.
- */
-void
-StyleDialog::_handleSelectionChanged() {
- g_debug("StyleDialog::_handleSelectionChanged()");
- _treeView.get_selection()->set_mode(Gtk::SELECTION_MULTIPLE);
- _selectRow();
-}
-
-
-/**
- * @brief StyleDialog::_buttonEventsSelectObjs
- * @param event
- * 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.
+ * @brief StyleDialog::onPropertyDelete
+ *
+ * This function is a slot to signal_activated for '-' button panel.
*/
-void StyleDialog::_buttonEventsSelectObjs(GdkEventButton* event )
+void StyleDialog::onPropertyDelete(Glib::ustring path)
{
- g_debug("StyleDialog::_buttonEventsSelectObjs");
- _treeView.get_selection()->set_mode(Gtk::SELECTION_SINGLE);
- _updating = true;
- del->set_sensitive(true);
- if (event->type == GDK_BUTTON_RELEASE && event->button == 1) {
- int x = static_cast<int>(event->x);
- int y = static_cast<int>(event->y);
- _selectObjects(x, y);
+ Gtk::TreeModel::Row row = *_store->get_iter(path);
+ if (row) {
+ this->setStyleProperty(row[_cssColumns.label], "");
}
- _updating = false;
}
-
/**
- * @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.
+ * @brief StyleDialog::onPropertyCreate
+ * This function is a slot to signal_clicked for '+' button panel.
*/
-void StyleDialog::_selectRow()
+bool StyleDialog::onPropertyCreate(GdkEventButton *event)
{
- g_debug("StyleDialog::_selectRow: updating: %s", (_updating ? "true" : "false"));
- del->set_sensitive(false);
- if (_updating || !getDesktop()) return; // Avoid updating if we have set row via dialog.
- if (SP_ACTIVE_DESKTOP != getDesktop()) {
- std::cerr << "StyleDialog::_selectRow: SP_ACTIVE_DESKTOP != getDesktop()" << std::endl;
- return;
- }
- _treeView.get_selection()->unselect_all();
- Gtk::TreeModel::Children children = _store->children();
- Inkscape::Selection* selection = getDesktop()->getSelection();
- SPObject *obj = nullptr;
- if (!selection->isEmpty()) {
- obj = selection->objects().back();
- }
- for (auto row : children) {
- std::vector<SPObject *> objVec = row[_mColumns._colObj];
- if (obj) {
- for (auto & i : objVec) {
- if (obj->getId() == i->getId()) {
- _treeView.get_selection()->select(row);
- }
- }
- }
- if (row[_mColumns._colExpand]) {
- _treeView.expand_to_path(Gtk::TreePath(row));
- }
+ if (event->type == GDK_BUTTON_RELEASE && event->button == 1 && this->_repr) {
+ Gtk::TreeIter iter = _store->append();
+ Gtk::TreeModel::Path path = (Gtk::TreeModel::Path)iter;
+ _treeView.set_cursor(path, *_propCol, true);
+ grab_focus();
+ return true;
}
+ return false;
}
-
-/**
- * @brief StyleDialog::_styleButton
- * @param btn
- * @param iconName
- * @param tooltip
- * Set 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_get_icon_image(iconName, GTK_ICON_SIZE_SMALL_TOOLBAR);
- gtk_widget_show(child);
- btn.add(*manage(Glib::wrap(child)));
- btn.set_relief(Gtk::RELIEF_NONE);
- btn.set_tooltip_text (tooltip);
-}
-
-
} // 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 c9c5e4b25..07d409ded 100644
--- a/src/ui/dialog/styledialog.h
+++ b/src/ui/dialog/styledialog.h
@@ -12,171 +12,125 @@
* Released under GNU GPL v2+, read the file 'COPYING' for more information.
*/
-#ifndef STYLEDIALOG_H
-#define STYLEDIALOG_H
+#ifndef SEEN_UI_DIALOGS_STYLEDIALOG_H
+#define SEEN_UI_DIALOGS_STYLEDIALOG_H
-#include <ui/widget/panel.h>
-#include <gtkmm/treeview.h>
-#include <gtkmm/treestore.h>
-#include <gtkmm/scrolledwindow.h>
-#include <gtkmm/dialog.h>
-#include <gtkmm/treeselection.h>
-#include <gtkmm/paned.h>
+#include "desktop.h"
+#include "message.h"
-#include "ui/dialog/desktop-tracker.h"
+#include <glibmm/regex.h>
+#include <gtkmm/dialog.h>
+#include <gtkmm/liststore.h>
+#include <gtkmm/scrolledwindow.h>
+#include <gtkmm/treeview.h>
+#include <ui/widget/panel.h>
-#include "xml/helper-observer.h"
+#define STYLE_DIALOG(obj) (dynamic_cast<Inkscape::UI::Dialog::StyleDialog *>((Inkscape::UI::Dialog::StyleDialog *)obj))
+#define REMOVE_SPACES(x) \
+ x.erase(0, x.find_first_not_of(' ')); \
+ x.erase(x.find_last_not_of(' ') + 1);
namespace Inkscape {
+class MessageStack;
+class MessageContext;
namespace UI {
namespace Dialog {
/**
* @brief The StyleDialog class
- * 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. (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.
+ * This dialog allows to add, delete and modify CSS properties for selectors
+ * created in Style Dialog. Double clicking any selector in Style dialog, a list
+ * of CSS properties will show up in this dialog (if any exist), else new properties
+ * can be added and each new property forms a new row in this pane.
*/
-class StyleDialog : public Widget::Panel {
-
+class StyleDialog : public UI::Widget::Panel
+{
public:
- ~StyleDialog() override;
- // No default constructor, noncopyable, nonassignable
StyleDialog();
- StyleDialog(StyleDialog const &d) = delete;
- StyleDialog operator=(StyleDialog const &d) = delete;
+ ~StyleDialog() override;
static StyleDialog &getInstance() { return *new StyleDialog(); }
- private:
- // Monitor <style> element for changes.
- class NodeObserver;
-
- // Monitor all objects for addition/removal/attribute change
- class NodeWatcher;
-
- std::vector<StyleDialog::NodeWatcher*> _nodeWatchers;
- void _nodeAdded( Inkscape::XML::Node &repr );
- void _nodeRemoved( Inkscape::XML::Node &repr );
- void _nodeChanged( Inkscape::XML::Node &repr );
// Data structure
- enum coltype { OBJECT, SELECTOR, UNHANDLED };
- class ModelColumns : public Gtk::TreeModel::ColumnRecord {
+ class CssColumns : public Gtk::TreeModel::ColumnRecord {
public:
- ModelColumns() {
- add(_colSelector);
- add(_colExpand);
- add(_colType);
- add(_colObj);
- add(_colProperties);
+ CssColumns() {
+ add(deleteButton);
+ add(label);
+ add(_styleSheetVal);
+ add(_styleAttrVal);
+ add(label_color);
+ add(attr_color);
+ add(attr_strike);
+ add(editable);
}
- Gtk::TreeModelColumn<Glib::ustring> _colSelector; // Selector or matching object id.
- Gtk::TreeModelColumn<bool> _colExpand; // Open/Close store row.
- Gtk::TreeModelColumn<gint> _colType; // Selector row or child object row.
- Gtk::TreeModelColumn<std::vector<SPObject *> > _colObj; // List of matching objects.
- Gtk::TreeModelColumn<Glib::ustring> _colProperties; // List of properties.
+ Gtk::TreeModelColumn<bool> deleteButton;
+ Gtk::TreeModelColumn<Glib::ustring> label;
+ Gtk::TreeModelColumn<Glib::ustring> _styleAttrVal;
+ Gtk::TreeModelColumn<Glib::ustring> _styleSheetVal;
+ Gtk::TreeModelColumn<Gdk::RGBA> label_color;
+ Gtk::TreeModelColumn<Gdk::RGBA> attr_color;
+ Gtk::TreeModelColumn<bool> attr_strike;
+ Gtk::TreeModelColumn<bool> editable;
};
- ModelColumns _mColumns;
-
- // Override Gtk::TreeStore to control drag-n-drop (only allow dragging and dropping of selectors).
- // See: https://developer.gnome.org/gtkmm-tutorial/stable/sec-treeview-examples.html.en
- //
- // TreeStore implements simple drag and drop (DND) but there appears no way to know when a DND
- // has been completed (other than doing the whole DND ourselves). As a hack, we use
- // on_row_deleted to trigger write of style element.
- class TreeStore : public Gtk::TreeStore {
- protected:
- TreeStore();
- bool row_draggable_vfunc(const Gtk::TreeModel::Path& path) const override;
- bool row_drop_possible_vfunc(const Gtk::TreeModel::Path& path,
- const Gtk::SelectionData& selection_data) const override;
- void on_row_deleted(const TreeModel::Path& path) override;
-
- public:
- static Glib::RefPtr<StyleDialog::TreeStore> create(StyleDialog *styledialog);
+ CssColumns _cssColumns;
- private:
- StyleDialog *_styledialog;
- };
+ /**
+ * Status bar
+ */
+ std::shared_ptr<Inkscape::MessageStack> _message_stack;
+ std::unique_ptr<Inkscape::MessageContext> _message_context;
// TreeView
Gtk::TreeView _treeView;
- Glib::RefPtr<TreeStore> _store;
-
- // Widgets
- Gtk::Paned _paned;
- Gtk::Box _mainBox;
- Gtk::Box _buttonBox;
- Gtk::ScrolledWindow _scrolledWindow;
- Gtk::Button* del;
- Gtk::Button* create;
-
- // Reading and writing the style element.
- Inkscape::XML::Node *_getStyleTextNode();
- void _readStyleElement();
- void _writeStyleElement();
-
- // Update watchers
- void _addWatcherRecursive(Inkscape::XML::Node *node);
- void _updateWatchers();
-
- // Manipulate Tree
- void _addToSelector(Gtk::TreeModel::Row row);
- void _removeFromSelector(Gtk::TreeModel::Row row);
- Glib::ustring _getIdList(std::vector<SPObject *>);
- std::vector<SPObject *> _getObjVec(Glib::ustring selector);
- void _insertClass(const std::vector<SPObject *>& objVec, const Glib::ustring& className);
- void _selectObjects(int, int);
-
- // Variables
- bool _updating; // Prevent cyclic actions: read <-> write, select via dialog <-> via desktop
- Inkscape::XML::Node *_textNode; // Track so we know when to add a NodeObserver.
-
- // Signals and handlers - External
- sigc::connection _document_replaced_connection;
- sigc::connection _desktop_changed_connection;
- sigc::connection _selection_changed_connection;
-
- void _handleDocumentReplaced(SPDesktop* desktop, SPDocument *document);
- void _handleDesktopChanged(SPDesktop* desktop);
- void _handleSelectionChanged();
- void _rowExpand(const Gtk::TreeModel::iterator &iter, const Gtk::TreeModel::Path &path);
- void _rowCollapse(const Gtk::TreeModel::iterator &iter, const Gtk::TreeModel::Path &path);
- void _closeDialog(Gtk::Dialog *textDialogPtr);
-
- DesktopTracker _desktopTracker;
-
- Inkscape::XML::SignalObserver _objObserver; // Track object in selected row (for style change).
-
- // Signal and handlers - Internal
- void _addSelector();
- void _delSelector();
- bool _handleButtonEvent(GdkEventButton *event);
- void _buttonEventsSelectObjs(GdkEventButton *event);
- void _selectRow(); // Select row in tree when selection changed.
-
- // GUI
- void _styleButton(Gtk::Button& btn, char const* iconName, char const* tooltip);
+ Glib::RefPtr<Gtk::ListStore> _store;
+ Gtk::TreeModel::Row _propRow;
+ Gtk::TreeViewColumn *_propCol;
+ Gtk::TreeViewColumn *_sheetCol;
+ Gtk::TreeViewColumn *_attrCol;
+ Gtk::HBox status_box;
+ Gtk::Label status;
+
+ /**
+ * Sets the XML status bar, depending on which attr is selected.
+ */
+ void css_reset_context(gint css);
+ static void _set_status_message(Inkscape::MessageType type, const gchar *message, GtkWidget *dialog);
+
+
+ // Variables - Inkscape
+ SPDesktop* _desktop;
+ Inkscape::XML::Node *_repr;
+
+ // Helper functions
+ void setDesktop(SPDesktop* desktop) override;
+ void setRepr(Inkscape::XML::Node *repr);
+
+ // Parsing functions
+ std::map<Glib::ustring, Glib::ustring> parseStyle(Glib::ustring style_string);
+ Glib::ustring compileStyle(std::map<Glib::ustring, Glib::ustring> props);
+
+ // Signal handlers
+ void onAttrChanged(Inkscape::XML::Node *repr, const gchar *name, const gchar *new_value);
+
+ private:
+ Glib::RefPtr<Glib::Regex> r_props = Glib::Regex::create("\\s*;\\s*");
+ Glib::RefPtr<Glib::Regex> r_pair = Glib::Regex::create("\\s*:\\s*");
+
+ bool onPropertyCreate(GdkEventButton *event);
+ void onPropertyDelete(Glib::ustring path);
+ bool setStyleProperty(Glib::ustring name, Glib::ustring value);
+
+ /**
+ * Signal handlers
+ */
+ sigc::connection _message_changed_connection;
+ bool _addProperty(GdkEventButton *event);
};
-} // namespace Dialogc
+
+} // namespace Dialog
} // namespace UI
} // 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 :