summaryrefslogtreecommitdiffstats
path: root/src/ui
diff options
context:
space:
mode:
authorShlomi Fish <shlomif@shlomifish.org>2017-02-06 16:50:07 +0000
committerShlomi Fish <shlomif@shlomifish.org>2017-02-06 16:50:07 +0000
commit1232596134bcba8d19f2809ffdc84e3b5c33d3b3 (patch)
tree2fcb91d6fe9ef47a85ba3f73be10dc5dc7ee10a4 /src/ui
parentMerged. (diff)
parentRemove some unneeded < C++11 fallback code (diff)
downloadinkscape-1232596134bcba8d19f2809ffdc84e3b5c33d3b3.tar.gz
inkscape-1232596134bcba8d19f2809ffdc84e3b5c33d3b3.zip
Merged.
(bzr r15369.1.18)
Diffstat (limited to 'src/ui')
-rw-r--r--src/ui/CMakeLists.txt4
-rw-r--r--src/ui/clipboard.cpp40
-rw-r--r--src/ui/dialog/aboutbox.cpp8
-rw-r--r--src/ui/dialog/cssdialog.cpp125
-rw-r--r--src/ui/dialog/cssdialog.h75
-rw-r--r--src/ui/dialog/dialog-manager.cpp6
-rw-r--r--src/ui/dialog/export.cpp9
-rw-r--r--src/ui/dialog/polar-arrange-tab.cpp4
-rw-r--r--src/ui/dialog/styledialog.cpp1116
-rw-r--r--src/ui/dialog/styledialog.h117
-rw-r--r--src/ui/tool/multi-path-manipulator.h15
-rw-r--r--src/ui/tool/node.h5
-rw-r--r--src/ui/tool/transform-handle-set.cpp5
-rw-r--r--src/ui/tools/flood-tool.cpp59
-rw-r--r--src/ui/tools/mesh-tool.cpp2
-rw-r--r--src/ui/tools/tool-base.cpp862
-rw-r--r--src/ui/tools/tool-base.h1
-rw-r--r--src/ui/widget/selected-style.cpp57
18 files changed, 2018 insertions, 492 deletions
diff --git a/src/ui/CMakeLists.txt b/src/ui/CMakeLists.txt
index f2a256698..11a0c351f 100644
--- a/src/ui/CMakeLists.txt
+++ b/src/ui/CMakeLists.txt
@@ -57,6 +57,7 @@ set(ui_SRC
dialog/calligraphic-profile-rename.cpp
dialog/clonetiler.cpp
dialog/color-item.cpp
+ dialog/cssdialog.cpp
dialog/debug.cpp
dialog/desktop-tracker.cpp
dialog/dialog-manager.cpp
@@ -99,6 +100,7 @@ set(ui_SRC
dialog/print-colors-preview-dialog.cpp
dialog/print.cpp
dialog/spellcheck.cpp
+ dialog/styledialog.cpp
dialog/svg-fonts-dialog.cpp
dialog/swatches.cpp
dialog/symbols.cpp
@@ -195,6 +197,7 @@ set(ui_SRC
dialog/calligraphic-profile-rename.h
dialog/clonetiler.h
dialog/color-item.h
+ dialog/cssdialog.h
dialog/debug.h
dialog/desktop-tracker.h
dialog/dialog-manager.h
@@ -239,6 +242,7 @@ set(ui_SRC
dialog/print-colors-preview-dialog.h
dialog/print.h
dialog/spellcheck.h
+ dialog/styledialog.h
dialog/svg-fonts-dialog.h
dialog/swatches.h
dialog/symbols.h
diff --git a/src/ui/clipboard.cpp b/src/ui/clipboard.cpp
index 73b632a2c..e719f8d63 100644
--- a/src/ui/clipboard.cpp
+++ b/src/ui/clipboard.cpp
@@ -644,7 +644,6 @@ Glib::ustring ClipboardManagerImpl::getShapeOrTextObjectId(SPDesktop *desktop)
return svgd;
}
-
/**
* Iterate over a list of items and copy them to the clipboard.
*/
@@ -690,25 +689,6 @@ void ClipboardManagerImpl::_copySelection(ObjectSet *selection)
else
obj_copy = _copyNode(obj, _doc, _clipnode);
- // For lpe items, copy lpe stack if applicable
- SPLPEItem *lpeitem = dynamic_cast<SPLPEItem *>(item);
- if (lpeitem) {
- Inkscape::SVGOStringStream os;
- if (lpeitem->hasPathEffect()) {
- for (PathEffectList::iterator it = lpeitem->path_effect_list->begin(); it != lpeitem->path_effect_list->end(); ++it)
- {
- LivePathEffectObject *lpeobj = (*it)->lpeobject;
- if (lpeobj) {
- Inkscape::XML::Node * lpeobjcopy = _copyNode(lpeobj->getRepr(), _doc, _defs);
- gchar *new_conflict_id = sp_object_get_unique_id(lpeobj, lpeobj->getAttribute("id"));
- lpeobjcopy->setAttribute("id", new_conflict_id);
- g_free(new_conflict_id);
- os << "#" << lpeobjcopy->attribute("id") << ";";
- }
- }
- }
- obj_copy->setAttribute("inkscape:path-effect", os.str().c_str());
- }
// copy complete inherited style
SPCSSAttr *css = sp_repr_css_attr_inherited(obj, "style");
sp_repr_css_set(obj_copy, css, "style");
@@ -740,6 +720,13 @@ void ClipboardManagerImpl::_copySelection(ObjectSet *selection)
sp_repr_css_set(_clipnode, style, "style");
sp_repr_css_attr_unref(style);
}
+ // copy path effect from the first path
+ if (object) {
+ gchar const *effect =object->getRepr()->attribute("inkscape:path-effect");
+ if (effect) {
+ _clipnode->setAttribute("inkscape:path-effect", effect);
+ }
+ }
}
Geom::OptRect size = selection->visualBounds();
@@ -842,6 +829,19 @@ void ClipboardManagerImpl::_copyUsedDefs(SPItem *item)
}
}
+ // For lpe items, copy lpe stack if applicable
+ SPLPEItem *lpeitem = dynamic_cast<SPLPEItem *>(item);
+ if (lpeitem) {
+ if (lpeitem->hasPathEffect()) {
+ for (PathEffectList::iterator it = lpeitem->path_effect_list->begin(); it != lpeitem->path_effect_list->end(); ++it){
+ LivePathEffectObject *lpeobj = (*it)->lpeobject;
+ if (lpeobj) {
+ _copyNode(lpeobj->getRepr(), _doc, _defs);
+ }
+ }
+ }
+ }
+
// recurse
for(auto& o: item->children) {
SPItem *childItem = dynamic_cast<SPItem *>(&o);
diff --git a/src/ui/dialog/aboutbox.cpp b/src/ui/dialog/aboutbox.cpp
index 8f0545e96..40b948a23 100644
--- a/src/ui/dialog/aboutbox.cpp
+++ b/src/ui/dialog/aboutbox.cpp
@@ -152,16 +152,16 @@ Gtk::Widget *build_splash_widget() {
the `screens' directory. Thus the translation of "about.svg" should be
the filename of its translated version, e.g. about.zh.svg for Chinese.
- N.B. about.svg changes once per release. (We should probably rename
- the original to about-0.40.svg etc. as soon as we have a translation.
- If we do so, then add an item to release-checklist saying that the
- string here should be changed.) */
+ Please don't translate the filename unless the translated picture exists. */
// FIXME? INKSCAPE_SCREENSDIR and "about.svg" are in UTF-8, not the
// native filename encoding... and the filename passed to sp_document_new
// should be in UTF-*8..
char *about=g_build_filename(INKSCAPE_SCREENSDIR, _("about.svg"), NULL);
+ if (!g_file_test (about, G_FILE_TEST_EXISTS)) {
+ about=g_build_filename(INKSCAPE_SCREENSDIR, "about.svg", NULL);
+ }
SPDocument *doc=SPDocument::createNewDoc (about, TRUE);
g_free(about);
g_return_val_if_fail(doc != NULL, NULL);
diff --git a/src/ui/dialog/cssdialog.cpp b/src/ui/dialog/cssdialog.cpp
new file mode 100644
index 000000000..fa266b012
--- /dev/null
+++ b/src/ui/dialog/cssdialog.cpp
@@ -0,0 +1,125 @@
+/** @file
+ * @brief A dialog for CSS selectors
+ */
+/* Authors:
+ * Kamalpreet Kaur Grewal
+ *
+ * Copyright (C) Kamalpreet Kaur Grewal 2016 <grewalkamal005@gmail.com>
+ *
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ */
+
+#include "cssdialog.h"
+#include "ui/widget/addtoicon.h"
+#include "widgets/icon.h"
+#include "verbs.h"
+#include "sp-object.h"
+#include "selection.h"
+#include "xml/attribute-record.h"
+
+namespace Inkscape {
+namespace UI {
+namespace Dialog {
+
+/**
+ * @brief CssDialog::_styleButton
+ * @param btn
+ * @param iconName
+ * @param tooltip
+ * This function sets the style of '+'button at the bottom of dialog.
+ */
+void CssDialog::_styleButton(Gtk::Button& btn, char const* iconName,
+ char const* tooltip)
+{
+ GtkWidget *child = sp_icon_new(Inkscape::ICON_SIZE_SMALL_TOOLBAR, iconName);
+ gtk_widget_show(child);
+ btn.add(*manage(Glib::wrap(child)));
+ btn.set_relief(Gtk::RELIEF_NONE);
+ btn.set_tooltip_text(tooltip);
+}
+
+/**
+ * Constructor
+ * A treeview whose each row corresponds to a CSS property of selector selected.
+ * 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(0)
+{
+ set_size_request(20, 15);
+ _mainBox.pack_start(_scrolledWindow, Gtk::PACK_EXPAND_WIDGET);
+ _treeView.set_headers_visible(false);
+ _scrolledWindow.add(_treeView);
+ _scrolledWindow.set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC);
+
+ _store = Gtk::ListStore::create(_cssColumns);
+ _treeView.set_model(_store);
+
+ Inkscape::UI::Widget::AddToIcon * addRenderer = manage(new Inkscape::UI::
+ Widget::AddToIcon());
+ addRenderer->property_active() = false;
+
+ int addCol = _treeView.append_column("Unset Property", *addRenderer) - 1;
+ Gtk::TreeViewColumn *col = _treeView.get_column(addCol);
+ if (col) {
+ col->add_attribute(addRenderer->property_active(), _cssColumns._colUnsetProp);
+ }
+ _textRenderer = Gtk::manage(new Gtk::CellRendererText());
+ _textRenderer->property_editable() = true;
+
+ int nameColNum = _treeView.append_column("Property", *_textRenderer) - 1;
+ _propCol = _treeView.get_column(nameColNum);
+
+ Gtk::Button* create = manage(new Gtk::Button());
+ _styleButton(*create, "list-add", "Add a new property");
+
+ _mainBox.pack_end(_buttonBox, Gtk::PACK_SHRINK);
+ _buttonBox.pack_start(*create, Gtk::PACK_SHRINK);
+
+ _getContents()->pack_start(_mainBox, Gtk::PACK_EXPAND_WIDGET);
+
+ _targetDesktop = getDesktop();
+ setDesktop(_targetDesktop);
+
+ create->signal_clicked().connect(sigc::mem_fun(*this, &CssDialog::_addProperty));
+}
+
+/**
+ * @brief CssDialog::~CssDialog
+ * Class destructor
+ */
+CssDialog::~CssDialog()
+{
+ setDesktop(NULL);
+}
+
+/**
+ * @brief CssDialog::setDesktop
+ * @param desktop
+ * This function sets the 'desktop' for the CSS pane.
+ */
+void CssDialog::setDesktop(SPDesktop* desktop)
+{
+ _desktop = desktop;
+}
+
+/**
+ * @brief CssDialog::_addProperty
+ * This function is a slot to signal_clicked for '+' button at the bottom of CSS
+ * panel. A new row is added, double clicking which text for new property can be
+ * added. _newProperty is set to true in which case the value is appended.
+ */
+void CssDialog::_addProperty()
+{
+ _propRow = *(_store->append());
+ _newProperty = true;
+}
+
+} // namespace Dialog
+} // namespace UI
+} // namespace Inkscape
diff --git a/src/ui/dialog/cssdialog.h b/src/ui/dialog/cssdialog.h
new file mode 100644
index 000000000..3bbab5031
--- /dev/null
+++ b/src/ui/dialog/cssdialog.h
@@ -0,0 +1,75 @@
+/** @file
+ * @brief A dialog for CSS selectors
+ */
+/* Authors:
+ * Kamalpreet Kaur Grewal
+ *
+ * Copyright (C) Kamalpreet Kaur Grewal 2016 <grewalkamal005@gmail.com>
+ *
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ */
+
+#ifndef CSSDIALOG_H
+#define CSSDIALOG_H
+
+#include <gtkmm/treeview.h>
+#include <gtkmm/liststore.h>
+#include <gtkmm/scrolledwindow.h>
+#include <gtkmm/dialog.h>
+#include <ui/widget/panel.h>
+
+#include "desktop.h"
+
+namespace Inkscape {
+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();
+
+ static CssDialog &getInstance() { return *new CssDialog(); }
+ void setDesktop(SPDesktop* desktop);
+
+ class CssColumns : public Gtk::TreeModel::ColumnRecord
+ {
+ public:
+ CssColumns()
+ { add(_colUnsetProp); add(_propertyLabel); }
+ Gtk::TreeModelColumn<bool> _colUnsetProp;
+ Gtk::TreeModelColumn<Glib::ustring> _propertyLabel;
+ };
+
+ SPDesktop* _desktop;
+ SPDesktop* _targetDesktop;
+ CssColumns _cssColumns;
+ Gtk::VBox _mainBox;
+ Gtk::HBox _buttonBox;
+ Gtk::TreeView _treeView;
+ Glib::RefPtr<Gtk::ListStore> _store;
+ Gtk::ScrolledWindow _scrolledWindow;
+ Gtk::TreeModel::Row _propRow;
+ Gtk::CellRendererText *_textRenderer;
+ Gtk::TreeViewColumn *_propCol;
+ Glib::ustring _editedProp;
+ bool _newProperty;
+
+ void _styleButton(Gtk::Button& btn, char const* iconName, char const* tooltip);
+ void _addProperty();
+};
+
+
+} // 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 c53112656..b407f8200 100644
--- a/src/ui/dialog/dialog-manager.cpp
+++ b/src/ui/dialog/dialog-manager.cpp
@@ -57,6 +57,8 @@
#include "ui/dialog/svg-fonts-dialog.h"
#include "ui/dialog/objects.h"
#include "ui/dialog/tags.h"
+#include "ui/dialog/styledialog.h"
+#include "ui/dialog/cssdialog.h"
namespace Inkscape {
namespace UI {
@@ -125,6 +127,8 @@ DialogManager::DialogManager() {
registerFactory("Swatches", &create<SwatchesPanel, FloatingBehavior>);
registerFactory("TileDialog", &create<ArrangeDialog, FloatingBehavior>);
registerFactory("Symbols", &create<SymbolsDialog, FloatingBehavior>);
+ registerFactory("StyleDialog", &create<StyleDialog, FloatingBehavior>);
+ registerFactory("CssDialog", &create<CssDialog, FloatingBehavior>);
#if HAVE_POTRACE
registerFactory("Trace", &create<TraceDialog, FloatingBehavior>);
@@ -164,6 +168,8 @@ DialogManager::DialogManager() {
registerFactory("Swatches", &create<SwatchesPanel, DockBehavior>);
registerFactory("TileDialog", &create<ArrangeDialog, DockBehavior>);
registerFactory("Symbols", &create<SymbolsDialog, DockBehavior>);
+ registerFactory("StyleDialog", &create<StyleDialog, DockBehavior>);
+ registerFactory("CssDialog", &create<CssDialog, DockBehavior>);
#if HAVE_POTRACE
registerFactory("Trace", &create<TraceDialog, DockBehavior>);
diff --git a/src/ui/dialog/export.cpp b/src/ui/dialog/export.cpp
index 1bb952de4..b7207fd50 100644
--- a/src/ui/dialog/export.cpp
+++ b/src/ui/dialog/export.cpp
@@ -978,7 +978,9 @@ void Export::onExport ()
SPNamedView *nv = desktop->getNamedView();
SPDocument *doc = desktop->getDocument();
-
+ Geom::Affine rot = doc->getRoot()->c2p;
+ doc->getRoot()->c2p = doc->getRoot()->rotation.inverse() * doc->getRoot()->c2p;
+ doc->ensureUpToDate();
bool exportSuccessful = false;
bool hide = hide_export.get_active ();
@@ -1003,6 +1005,7 @@ void Export::onExport ()
if (num < 1) {
desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("No items selected."));
+ doc->getRoot()->c2p *= doc->getRoot()->rotation;
return;
}
@@ -1094,6 +1097,7 @@ void Export::onExport ()
if (filename.empty()) {
desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("You have to enter a filename."));
sp_ui_error_dialog(_("You have to enter a filename"));
+ doc->getRoot()->c2p *= doc->getRoot()->rotation;
return;
}
@@ -1110,6 +1114,7 @@ void Export::onExport ()
if (!((x1 > x0) && (y1 > y0) && (width > 0) && (height > 0))) {
desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("The chosen area to be exported is invalid."));
sp_ui_error_dialog(_("The chosen area to be exported is invalid"));
+ doc->getRoot()->c2p *= doc->getRoot()->rotation;
return;
}
@@ -1132,6 +1137,7 @@ void Export::onExport ()
g_free(safeDir);
g_free(error);
+ doc->getRoot()->c2p *= doc->getRoot()->rotation;
return;
}
@@ -1281,6 +1287,7 @@ void Export::onExport ()
}
}
}
+ doc->getRoot()->c2p *= doc->getRoot()->rotation;
} // end of sp_export_export_clicked()
/// Called when Browse button is clicked
diff --git a/src/ui/dialog/polar-arrange-tab.cpp b/src/ui/dialog/polar-arrange-tab.cpp
index c51881a96..9485b6ba3 100644
--- a/src/ui/dialog/polar-arrange-tab.cpp
+++ b/src/ui/dialog/polar-arrange-tab.cpp
@@ -43,14 +43,14 @@ PolarArrangeTab::PolarArrangeTab(ArrangeDialog *parent_)
anchorPointLabel.set_alignment(Gtk::ALIGN_START);
pack_start(anchorPointLabel, false, false);
- anchorBoundingBoxRadio.set_label(C_("Polar arrange tab", "Object's bounding box:"));
+ anchorBoundingBoxRadio.set_label(C_("Polar arrange tab", "Objects' bounding boxes:"));
anchorRadioGroup = anchorBoundingBoxRadio.get_group();
anchorBoundingBoxRadio.signal_toggled().connect(sigc::mem_fun(*this, &PolarArrangeTab::on_anchor_radio_changed));
pack_start(anchorBoundingBoxRadio, false, false);
pack_start(anchorSelector, false, false);
- anchorObjectPivotRadio.set_label(C_("Polar arrange tab", "Object's rotational center"));
+ anchorObjectPivotRadio.set_label(C_("Polar arrange tab", "Objects' rotational centers"));
anchorObjectPivotRadio.set_group(anchorRadioGroup);
anchorObjectPivotRadio.signal_toggled().connect(sigc::mem_fun(*this, &PolarArrangeTab::on_anchor_radio_changed));
pack_start(anchorObjectPivotRadio, false, false);
diff --git a/src/ui/dialog/styledialog.cpp b/src/ui/dialog/styledialog.cpp
new file mode 100644
index 000000000..5246290b4
--- /dev/null
+++ b/src/ui/dialog/styledialog.cpp
@@ -0,0 +1,1116 @@
+/** @file
+ * @brief A dialog for CSS selectors
+ */
+/* Authors:
+ * Kamalpreet Kaur Grewal
+ *
+ * Copyright (C) Kamalpreet Kaur Grewal 2016 <grewalkamal005@gmail.com>
+ *
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ */
+
+#include "styledialog.h"
+#include "ui/widget/addtoicon.h"
+#include "widgets/icon.h"
+#include "verbs.h"
+#include "sp-object.h"
+#include "selection.h"
+#include "xml/attribute-record.h"
+#include <glibmm/regex.h>
+
+using Inkscape::Util::List;
+using Inkscape::XML::AttributeRecord;
+
+/**
+ * This macro is used to remove spaces around selectors or any strings when
+ * parsing is done to update XML style element or row labels in this dialog.
+ */
+#define REMOVE_SPACES(x) x.erase(0, x.find_first_not_of(' ')); \
+ x.erase(x.find_last_not_of(' ') + 1);
+
+namespace Inkscape {
+namespace UI {
+namespace Dialog {
+
+/**
+ * @brief StyleDialog::_styleButton
+ * @param btn
+ * @param iconName
+ * @param tooltip
+ * This function sets the style of '+' and '-' buttons at the bottom of dialog.
+ */
+void StyleDialog::_styleButton(Gtk::Button& btn, char const* iconName,
+ char const* tooltip)
+{
+ GtkWidget *child = sp_icon_new(Inkscape::ICON_SIZE_SMALL_TOOLBAR, iconName);
+ gtk_widget_show(child);
+ btn.add(*manage(Glib::wrap(child)));
+ btn.set_relief(Gtk::RELIEF_NONE);
+ btn.set_tooltip_text (tooltip);
+}
+
+/**
+ * Constructor
+ * A treeview and a set of two buttons are added to the dialog. _addSelector
+ * adds selectors to treeview. _delSelector deletes the selector from the dialog.
+ * Any addition/deletion of the selectors updates XML style element accordingly.
+ */
+StyleDialog::StyleDialog() :
+ UI::Widget::Panel("", "/dialogs/style", SP_VERB_DIALOG_STYLE),
+ _desktop(0)
+{
+ set_size_request(200, 200);
+
+ _paned.pack1(_mainBox, Gtk::SHRINK);
+ _mainBox.pack_start(_scrolledWindow, Gtk::PACK_EXPAND_WIDGET);
+ _treeView.set_headers_visible(false);
+ _scrolledWindow.add(_treeView);
+ _scrolledWindow.set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC);
+
+ _store = Gtk::TreeStore::create(_mColumns);
+ _treeView.set_model(_store);
+
+ Inkscape::UI::Widget::AddToIcon * addRenderer = manage(
+ new Inkscape::UI::Widget::AddToIcon() );
+ addRenderer->property_active() = true;
+
+ int addCol = _treeView.append_column("type", *addRenderer) - 1;
+
+ Gtk::TreeViewColumn *col = _treeView.get_column(addCol);
+ if ( col ) {
+ col->add_attribute( addRenderer->property_active(), _mColumns._colAddRemove );
+ }
+
+ _treeView.append_column("Selector Name", _mColumns._selectorLabel);
+ _treeView.set_expander_column(*(_treeView.get_column(1)));
+
+ create = manage( new Gtk::Button() );
+ _styleButton(*create, "list-add", "Add a new CSS Selector");
+ create->signal_clicked().connect(sigc::mem_fun(*this,
+ &StyleDialog::_addSelector));
+
+ del = manage( new Gtk::Button() );
+ _styleButton(*del, "list-remove", "Remove a CSS Selector");
+ del->signal_clicked().connect(sigc::mem_fun(*this,
+ &StyleDialog::_delSelector));
+ del->set_sensitive(false);
+
+ _mainBox.pack_end(_buttonBox, Gtk::PACK_SHRINK);
+
+ _buttonBox.pack_start(*create, Gtk::PACK_SHRINK);
+ _buttonBox.pack_start(*del, Gtk::PACK_SHRINK);
+
+ _getContents()->pack_start(_paned, Gtk::PACK_EXPAND_WIDGET);
+
+ _targetDesktop = getDesktop();
+ setDesktop(_targetDesktop);
+
+ /**
+ * @brief document
+ * If an existing document is opened, its XML representation is obtained
+ * and is then used to populate the treeview with the already existing
+ * selectors in the style element.
+ */
+ _styleExists = false;
+ _document = _targetDesktop->doc();
+ _selectorValue = _populateTree(_getSelectorVec());
+
+ _treeView.signal_button_press_event().connect(sigc::mem_fun(*this,
+ &StyleDialog::
+ _handleButtonEvent),
+ false);
+
+ _treeView.signal_button_press_event().connect_notify(sigc::mem_fun
+ (*this, &StyleDialog::
+ _buttonEventsSelectObjs),
+ false);
+
+ _cssPane = new CssDialog;
+
+ _treeView.get_selection()->signal_changed().connect(sigc::mem_fun(*this,
+ &StyleDialog::
+ _selChanged));
+}
+
+/**
+ * @brief StyleDialog::~StyleDialog
+ * Class destructor
+ */
+StyleDialog::~StyleDialog()
+{
+ setDesktop(NULL);
+}
+
+/**
+ * @brief StyleDialog::setDesktop
+ * @param desktop
+ * This function sets the 'desktop' for the Style Dialog.
+ */
+void StyleDialog::setDesktop( SPDesktop* desktop )
+{
+ Panel::setDesktop(desktop);
+ _desktop = Panel::getDesktop();
+ _desktop->getSelection()->connectChanged(sigc::mem_fun(*this, &StyleDialog::
+ _selectRow));
+}
+
+/**
+ * @brief StyleDialog::_addSelector
+ * This function is the slot to the signal emitted when '+' at the bottom of
+ * the dialog is clicked.
+ */
+void StyleDialog::_addSelector()
+{
+ Gtk::TreeModel::Row row = *(_store->append());
+
+ /**
+ * On clicking '+' button, an entrybox with default text opens up. If an
+ * object is already selected, a selector with value in the entry
+ * is added to a new style element.
+ */
+ Gtk::Dialog *textDialogPtr = new Gtk::Dialog();
+ Gtk::Entry *textEditPtr = manage ( new Gtk::Entry() );
+ textDialogPtr->add_button("Add", Gtk::RESPONSE_OK);
+ textDialogPtr->get_vbox()->pack_start(*textEditPtr, Gtk::PACK_SHRINK);
+
+ /**
+ * By default, the entrybox contains 'Class1' as text. However, if object(s)
+ * is(are) selected and user clicks '+' at the bottom of dialog, the
+ * entrybox will have the id(s) of the selected objects as text.
+ */
+ if (_desktop->getSelection()->isEmpty()) {
+ textEditPtr->set_text("Class1");
+ }
+ else {
+ Inkscape::Selection* selection = _desktop->getSelection();
+ std::vector<SPObject*> selected = std::vector<SPObject *>(selection
+ ->objects().begin(),
+ selection->
+ objects().end());
+ textEditPtr->set_text(_setClassAttribute(selected));
+ }
+
+ textDialogPtr->set_size_request(200, 100);
+ textDialogPtr->show_all();
+ int result = textDialogPtr->run();
+
+ /**
+ * @brief selectorName
+ * This string stores selector name. The text from entrybox is saved as name
+ * for selector. If the entrybox is empty, the text (thus selectorName) is
+ * set to ".Class1"
+ */
+ if (!textEditPtr->get_text().empty()) {
+ _selectorName = textEditPtr->get_text();
+ }
+ else {
+ _selectorName = ".Class1";
+ }
+
+ del->set_sensitive(true);
+
+ /**
+ * The selector name objects is set to the text that the user sets in the
+ * entrybox. If the attribute does not exist, it is
+ * created. In case the attribute already has a value, the new value entered
+ * is appended to the values. If a style attribute does not exist, it is
+ * created with an empty value. Also if a class selector is added, then
+ * class attribute for the selected object is set too.
+ */
+ std::vector<SPObject *> objVec;
+
+ bool objExists = false;
+ if (!_desktop->getSelection()->isEmpty()) {
+ for (auto& obj: _desktop->getSelection()->objects()) {
+ objExists = true;
+ if (!obj->getRepr()->attribute("style")) {
+ obj->getRepr()->setAttribute("style", NULL);
+ }
+
+ if (obj->getAttribute("style") == NULL) {
+ _selectorValue = _selectorName + "{" + "}" + "\n";
+ }
+ else {
+ _selectorValue = _selectorName + "{"
+ + obj->getAttribute("style") + "}" + "\n";
+ }
+
+ if (_selectorName[0] == '.') {
+ if (!obj->getRepr()->attribute("class")) {
+ obj->getRepr()->setAttribute("class", textEditPtr->get_text()
+ .erase(0,1));
+ }
+ else {
+ obj->getRepr()->setAttribute("class", std::string(obj->
+ getRepr()->
+ attribute("class"))
+ + " " + textEditPtr->get_text()
+ .erase(0,1));
+ }
+ }
+ }
+ }
+ else {
+ _selectorValue = _selectorName + "{" + "}" + "\n";
+ objExists = false;
+ }
+
+ switch (result) {
+ case Gtk::RESPONSE_OK:
+ textDialogPtr->hide();
+ row[_mColumns._selectorLabel] = _selectorName;
+ row[_mColumns._colAddRemove] = true;
+ if (objExists) {
+ Inkscape::Selection* selection = _desktop->getSelection();
+ row[_mColumns._colObj] = std::vector<SPObject *>(selection->objects()
+ .begin(), selection
+ ->objects().end());
+ objVec = row[_mColumns._colObj];
+ }
+ break;
+ default:
+ break;
+ }
+
+ /**
+ * A new style element is added to the document with value obtained
+ * from selectorValue above. If style element already exists, then
+ * the new selector's content is appended to its previous content.
+ */
+ inkSelector._selector = _selectorName;
+ inkSelector._matchingObjs = objVec;
+ inkSelector._xmlContent = _selectorValue;
+ _selectorVec.push_back(inkSelector);
+
+ if (_styleElementNode()) {
+ _styleChild = _styleElementNode();
+ _updateStyleContent();
+ }
+ else if (_styleExists && !_newDrawing) {
+ _updateStyleContent();
+ }
+ else if (!_styleExists) {
+ Inkscape::XML::Node *root = _document->getReprDoc()->root();
+ Inkscape::XML::Node *newChild = _document->getReprDoc()
+ ->createElement("svg:style");
+ Inkscape::XML::Node *smallChildren = _document->getReprDoc()
+ ->createTextNode(_selectorValue.c_str());
+
+ newChild->appendChild(smallChildren);
+ Inkscape::GC::release(smallChildren);
+
+ root->addChild(newChild, NULL);
+ Inkscape::GC::release(newChild);
+ _styleChild = newChild;
+ }
+ _selAdd(row);
+}
+
+/**
+ * @brief StyleDialog::_updateStyleContent
+ * This function updates the content in style element as new selectors (or
+ * objects) are added/removed.
+ */
+void StyleDialog::_updateStyleContent()
+{
+ std::string styleContent = "";
+ for (unsigned i = 0; i < _selectorVec.size(); ++i) {
+ styleContent = styleContent + _selectorVec[i]._xmlContent;
+ }
+ _styleChild->firstChild()->setContent(styleContent.c_str());
+}
+
+/**
+ * @brief StyleDialog::_delSelector
+ * This function deletes selector when '-' at the bottom is clicked. The index
+ * of selected row is obtained and the corresponding selector and its values are
+ * deleted from the selector vector. If a row has no parent, it is directly
+ * erased from the vector along with its child rows. The style element is updated
+ * accordingly.
+ */
+void StyleDialog::_delSelector()
+{
+ Glib::RefPtr<Gtk::TreeSelection> refTreeSelection = _treeView.get_selection();
+ Gtk::TreeModel::iterator iter = refTreeSelection->get_selected();
+ if (iter) {
+ Gtk::TreeModel::Row row = *iter;
+ std::string sel, key, value;
+ std::vector<InkSelector>::iterator it;
+ for (it = _selectorVec.begin(); it != _selectorVec.end();) {
+ sel = (*it)._xmlContent;
+ REMOVE_SPACES(sel);
+ if (!sel.empty()) {
+ key = strtok((char*)sel.c_str(), "{");
+ REMOVE_SPACES(key);
+ char *temp = strtok(NULL, "}");
+ if (strtok(temp, "}") != NULL) {
+ value = strtok(temp, "}");
+ }
+ }
+
+ Glib::ustring selectedRowLabel = row[_mColumns._selectorLabel];
+ std::string matchSelector = selectedRowLabel;
+ REMOVE_SPACES(matchSelector);
+ if (key == matchSelector) {
+ it = _selectorVec.erase(it);
+ _store->erase(row);
+ }
+ else {
+ ++it;
+ }
+
+ /**
+ * The _stylechild is obtained which contains the style element and
+ * the content in style element is updated. If _selectorVec is
+ * empty, the style element is removed from the XML repr else
+ * the content is updated simply using _updateStyleContent().
+ */
+ _styleChild = _styleElementNode();
+ if (_store->children().empty()) {
+ _document->getReprRoot()->removeChild(_styleChild);
+ _styleExists = false;
+ }
+ else {
+ _updateStyleContent();
+ }
+ }
+ }
+}
+
+/**
+ * @brief StyleDialog::_styleElementNode
+ * @return
+ * This function returns the node containing style element. The document's
+ * children are iterated and the repr of the style element that occurs is
+ * obtained.
+ */
+Inkscape::XML::Node* StyleDialog::_styleElementNode()
+{
+ for (unsigned i = 0; i < _document->getReprRoot()->childCount(); ++i) {
+ if (std::string(_document->getReprRoot()->nthChild(i)->name())
+ == "svg:style") {
+ _styleExists = true;
+ _newDrawing = true;
+ return _document->getReprRoot()->nthChild(i);
+ }
+ }
+ return NULL;
+}
+
+/**
+ * @brief StyleDialog::_setClassAttribute
+ * @param sel
+ * @return This function returns the ids of objects selected which are passed
+ * to entrybox.
+ */
+std::string StyleDialog::_setClassAttribute(std::vector<SPObject*> sel)
+{
+ std::string str = "";
+ for ( unsigned i = 0; i < sel.size(); ++i ) {
+ SPObject *obj = sel.at(i);
+ str = str + "#" + std::string(obj->getId()) + " ";
+ }
+ return str;
+}
+
+/**
+ * @brief StyleDialog::_getSelectorVec
+ * @return selVec
+ * This function returns a vector whose key is the style selector name and value
+ * is the style properties. All style selectors are extracted from svg:style
+ * element. _newDrawing is flag is set to false check if an existing drawing is
+ * opened.
+ */
+std::vector<StyleDialog::InkSelector> StyleDialog::_getSelectorVec()
+{
+ for (unsigned i = 0; i < _document->getReprRoot()->childCount(); ++i) {
+ if (std::string(_document->getReprRoot()->nthChild(i)->name()) == "svg:style") {
+ _styleExists = true;
+ _newDrawing = false;
+ _styleChild = _document->getReprRoot()->nthChild(i);
+
+ // Get content from first style element.
+ std::string content = _styleChild->firstChild()->content();
+
+ // Remove end-of-lines (check it works on Windoze).
+ content.erase(std::remove(content.begin(), content.end(), '\n'), content.end());
+
+ // First split into selector/value chunks.
+ // An attempt to use Glib::Regex failed. A C++11 version worked but
+ // reportedly has problems on Windows. Using split_simple() is simpler
+ // and probably faster.
+ //
+ // Glib::RefPtr<Glib::Regex> regex1 =
+ // Glib::Regex::create("([^\\{]+)\\{([^\\{]+)\\}");
+ //
+ // Glib::MatchInfo minfo;
+ // regex1->match(content, minfo);
+
+ // Split on curly brackets. Even tokens are selectors, odd are values.
+ std::vector<std::string> tokens = Glib::Regex::split_simple("[}{]", content);
+
+ for (unsigned i = 0; i < tokens.size()-1; i += 2) {
+ std::string selectors = tokens[i];
+ REMOVE_SPACES(selectors); // Remove leading/trailing spaces
+
+ /** Make a list of all objects that selector matches. This is
+ * currently limited to simple id, class, and element selectors.
+ * Expanding this would take integrating a true CSS parser.
+ */
+ std::vector<SPObject *>objVec;
+
+ // Split selector string into individual selectors (which are comma separated).
+ std::vector<std::string> tokens2 = Glib::Regex::split_simple
+ ("\\s*,\\s*", selectors );
+
+ for(unsigned i = 0; i < tokens2.size(); ++i) {
+ std::string token2 = tokens2[i];
+
+ // Find objects that match class selector
+ if (token2[0] == '.') {
+ token2.erase(0,1);
+ std::vector<SPObject *> objects = _document
+ ->getObjectsByClass(token2);
+ objVec.insert(objVec.end(), objects.begin(), objects.end());
+ }
+
+ // Find objects that match id selector
+ else if (token2[0] == '#') {
+ token2.erase(0,1);
+ SPObject * object = _document->getObjectById(token2);
+ if (object) {
+ objVec.push_back(object);
+ }
+ }
+
+ // Find objects that match element selector
+ else {
+ std::vector<SPObject *> objects = _document->
+ getObjectsByElement(token2);
+ objVec.insert(objVec.end(), objects.begin(), objects.end());
+ }
+ }
+
+ std::string values;
+ // Check to make sure we do have a value to match selector.
+ if ((i+1) < tokens.size()) {
+ values = tokens[i+1];
+ } else {
+ std::cerr << "StyleDialog::_getSelectorVec: Missing values "
+ "for last selector!" << std::endl;
+ }
+
+ _selectorValue = selectors + "{" + values + "}\n";
+ inkSelector._selector = selectors;
+ inkSelector._matchingObjs = objVec;
+ inkSelector._xmlContent = _selectorValue;
+ _selectorVec.push_back(inkSelector);
+ }
+ }
+ }
+ return _selectorVec;
+}
+
+/**
+ * @brief StyleDialog::_populateTree
+ * @param _selVec
+ * This function populates the treeview with selectors available in the
+ * stylesheet.
+ */
+std::string StyleDialog::_populateTree(std::vector<InkSelector> _selVec)
+{
+ _selectorVec = _selVec;
+ std::string selectorValue;
+
+ for(unsigned it = 0; it < _selectorVec.size(); ++it) {
+ Gtk::TreeModel::Row row = *(_store->append());
+ row[_mColumns._selectorLabel] = _selectorVec[it]._selector;
+ row[_mColumns._colAddRemove] = true;
+ row[_mColumns._colObj] = _selectorVec[it]._matchingObjs;
+ std::string selValue = _selectorVec[it]._xmlContent;
+ selectorValue.append(selValue.c_str());
+ }
+
+ if (_selectorVec.size() > 0) {
+ del->set_sensitive(true);
+ }
+
+ return selectorValue;
+}
+
+/**
+ * @brief StyleDialog::_handleButtonEvent
+ * @param event
+ * @return
+ * This function handles the event when '+' button in front of a selector name
+ * is clicked. The selected objects (if any) is added to the selector as a child
+ * in the treeview.
+ */
+bool StyleDialog::_handleButtonEvent(GdkEventButton *event)
+{
+ if (event->type == GDK_BUTTON_PRESS && event->button == 1) {
+ Gtk::TreeViewColumn *col = 0;
+ Gtk::TreeModel::Path path;
+ int x = static_cast<int>(event->x);
+ int y = static_cast<int>(event->y);
+ int x2 = 0;
+ int y2 = 0;
+ if (_treeView.get_path_at_pos(x, y, path, col, x2, y2)) {
+ if (col == _treeView.get_column(0)) {
+ Glib::RefPtr<Gtk::TreeSelection> refTreeSelection =
+ _treeView.get_selection();
+ Gtk::TreeModel::iterator iter = refTreeSelection->
+ get_selected();
+ Gtk::TreeModel::Row row = *iter;
+
+ /**
+ * This adds child rows to selected rows. If the parent row is
+ * a class selector, then the class attribute of object added
+ * to child row is appended with class in the parent row. The
+ * else below deletes objects from selectors when 'delete' button
+ * in front of child row is clicked. The class attribute is updated
+ * by removing the parent row's class selector name.
+ */
+ if (!row.parent()) {
+ _selAdd(row);
+ }
+
+ else {
+ std::string sel, key, value;
+ std::vector<InkSelector>::iterator it;
+ Gtk::TreeModel::Row parentRow = *(row).parent();
+ Glib::ustring parentKey = parentRow[_mColumns._selectorLabel];
+
+ for (it = _selectorVec.begin(); it != _selectorVec.end(); ++it) {
+ sel = (*it)._xmlContent;
+ REMOVE_SPACES(sel);
+ if (!sel.empty()) {
+ key = strtok((char*)sel.c_str(), "{");
+ REMOVE_SPACES(key);
+ char *temp = strtok(NULL, "}");
+ if (strtok(temp, "}") != NULL) {
+ value = strtok(temp, "}");
+ }
+ }
+
+ /**
+ * @brief matchSelector
+ * For id selectors, whenever any child row is deleted,
+ * the row label is updated and so is the entry for the
+ * selector in style element.
+ */
+ std::string matchSelector = parentKey;
+ REMOVE_SPACES(matchSelector);
+ if (key == matchSelector) {
+ if (key[0] == '#') {
+ std::string s = parentKey;
+ Glib::ustring toDelRow = row[_mColumns._selectorLabel];
+ std::string toDelKey = toDelRow;
+ std::size_t idFound = s.find(toDelKey);
+ if (idFound != std::string::npos) {
+ if (idFound == 0) {
+ s.erase(idFound, toDelKey.length()+1);
+ parentKey = s;
+ parentRow[_mColumns._selectorLabel] = parentKey;
+ (*it)._xmlContent.erase(idFound, toDelKey.length());
+ }
+ else {
+ s.erase(idFound-2, toDelKey.length()+2);
+ parentKey = s;
+ parentRow[_mColumns._selectorLabel] = parentKey;
+ (*it)._xmlContent.erase(idFound-2, toDelKey.
+ length()+2);
+ }
+ }
+ }
+ }
+
+ if (parentKey[0] == '.') {
+ std::vector<SPObject *> objVec = row[_mColumns._colObj];
+ for (unsigned i = 0; i < objVec.size(); ++i) {
+ SPObject *obj = objVec[i];
+ std::string classAttr = std::string(obj->getRepr()
+ ->attribute("class"));
+ std::size_t found = classAttr.find(parentKey.erase(0,1));
+ if (found != std::string::npos) {
+ classAttr.erase(found, parentKey.length()+1);
+ obj->getRepr()->setAttribute("class", classAttr);
+ }
+ }
+ }
+
+ if (parentKey.empty()) {
+ (*it)._xmlContent = "";
+ }
+ }
+
+ if (_styleChild) {
+ _updateStyleContent();
+ }
+ else {
+ _styleChild = _styleElementNode();
+ _updateStyleContent();
+ }
+
+ _store->erase(row);
+
+ /**
+ * On continuous deletion of objects (child rows) from the
+ * selector (parent row), if the parent row has no child, then
+ * the row is erased from the _store. Further if there is no
+ * row left in _store, which implies there is no content in
+ * XML style element, then the 'svg:style' element is also
+ * removed.
+ */
+ if (parentKey.empty()) {
+ _store->erase(parentRow);
+ }
+
+ if (parentKey.empty() && _store->children().empty()) {
+ _document->getReprRoot()->removeChild(_styleChild);
+ _styleExists = false;
+ }
+ }
+ }
+ }
+ }
+ return false;
+}
+
+/**
+ * @brief StyleDialog::_selAdd
+ * @param row
+ * This routine is called when an object is added to a selector by clicking on
+ * '+' in front of the row with selector's label.
+ */
+void StyleDialog::_selAdd(Gtk::TreeModel::Row row)
+{
+ Glib::ustring selectorName;
+ Gtk::TreeModel::Row childrow;
+ Inkscape::Selection* selection = _desktop->getSelection();
+ std::vector<SPObject *> sel = std::vector<SPObject *>
+ (selection->objects().begin(), selection->objects().end());
+ for (auto& obj: selection->objects()) {
+ if (*row) {
+ if (_selectorVec.size() != 0) {
+ childrow = *(_store->append(row->children()));
+ childrow[_mColumns._selectorLabel] = "#" +
+ std::string(obj->getId());
+ childrow[_mColumns._colAddRemove] = false;
+ childrow[_mColumns._colObj] = sel;
+ Glib::ustring key = row[_mColumns._selectorLabel];
+ if (key[0] == '.') {
+ if (!obj->getRepr()->attribute("class")) {
+ obj->setAttribute("class", key.erase(0,1));
+ }
+ else {
+ if (obj->getRepr()->attribute("class") != key
+ .erase(0,1)) {
+ obj->setAttribute("class", std::string
+ (obj->getRepr()->
+ attribute("class"))
+ + " " + key
+ .erase(0,1));
+ }
+ }
+ }
+ }
+ selectorName = row[_mColumns._selectorLabel];
+ }
+
+ /**
+ * If the object's parent row is a class selector, then
+ * there are no changes in style element except the class
+ * attribute is updated. For the id selector cases, XML
+ * content's style element is updated.
+ */
+ REMOVE_SPACES(selectorName);
+ std::vector<InkSelector>::iterator it;
+ for (it = _selectorVec.begin(); it != _selectorVec.end(); ++it) {
+ std::string sel, key, value;
+ sel = (*it)._xmlContent;
+ REMOVE_SPACES(sel);
+ if (!sel.empty()) {
+ key = strtok((char*)sel.c_str(), "{");
+ REMOVE_SPACES(key);
+ char *temp = strtok(NULL, "}");
+ if (strtok(temp, "}") != NULL) {
+ value = strtok(temp, "}");
+ }
+ }
+
+ Glib::ustring selectedRowLabel = row[_mColumns._selectorLabel];
+ std::string matchSelector = selectedRowLabel;
+ REMOVE_SPACES(matchSelector);
+ if (key == matchSelector) {
+ REMOVE_SPACES((*it)._selector);
+ if (selectorName[0] == '#') {
+ if ("#" + std::string(obj->getId()) != selectorName) {
+ inkSelector._selector = (*it)._selector;
+ inkSelector._selector.append(", #" + std::string(obj->getId()));
+ inkSelector._xmlContent = inkSelector._selector + "{" + value + "}\n";
+ row[_mColumns._selectorLabel] = selectorName + ", " +
+ childrow[_mColumns._selectorLabel];
+ }
+ }
+ else if (selectorName[0] == '.') {
+ inkSelector._xmlContent = (*it)._selector + "{" + value + "}\n";
+ }
+
+ it = _selectorVec.erase(it);
+ it = _selectorVec.insert(it, inkSelector);
+ }
+ }
+ }
+ if (_styleElementNode()) {
+ _styleChild = _styleElementNode();
+ _updateStyleContent();
+ }
+ else if (_styleExists && !_newDrawing) {
+ _updateStyleContent();
+ }
+}
+
+/**
+ * @brief StyleDialog::_selectObjects
+ * @param event
+ * This function detects single or double click on a selector in any row. Single
+ * click on a selector selects the matching objects. A double click on any
+ * selector selects the matching objects as well as will open CSS dialog. It
+ * calls _selectObjects to add objects to selection.
+ */
+void StyleDialog::_buttonEventsSelectObjs(GdkEventButton* event )
+{
+ if (event->type == GDK_BUTTON_PRESS && event->button == 1) {
+ int x = static_cast<int>(event->x);
+ int y = static_cast<int>(event->y);
+ _selectObjects(x, y);
+ }
+ else if (event->type == GDK_2BUTTON_PRESS && event->button == 1) {
+ int x = static_cast<int>(event->x);
+ int y = static_cast<int>(event->y);
+ _selectObjects(x, y);
+
+ //Open CSS dialog here.
+ if (!_cssPane->get_visible()) {
+ _paned.pack2(*_cssPane, Gtk::SHRINK);
+ _cssPane->show_all();
+ }
+
+ Glib::RefPtr<Gtk::TreeSelection> refTreeSelection = _treeView.get_selection();
+ Gtk::TreeModel::iterator iter = refTreeSelection->get_selected();
+ if (iter) {
+ Gtk::TreeModel::Row row = *iter;
+ std::string sel, key, value;
+ std::vector<InkSelector>::iterator it;
+ for (it = _selectorVec.begin(); it != _selectorVec.end(); ++it) {
+ sel = (*it)._xmlContent;
+ REMOVE_SPACES(sel);
+ if (!sel.empty()) {
+ key = strtok((char*)sel.c_str(), "{");
+ REMOVE_SPACES(key);
+ char *temp = strtok(NULL, "}");
+ if (strtok(temp, "}") != NULL) {
+ value = strtok(temp, "}");
+ }
+ }
+
+ Glib::ustring selectedRowLabel = row[_mColumns._selectorLabel];
+ std::string matchSelector = selectedRowLabel;
+ REMOVE_SPACES(matchSelector);
+
+ if (key == matchSelector) {
+ _cssPane->_store->clear();
+ std::stringstream ss(value);
+ std::string token;
+ std::size_t found = value.find(";");
+ if (found!=std::string::npos) {
+ while(std::getline(ss, token, ';')) {
+ REMOVE_SPACES(token);
+ if (!token.empty()) {
+ _cssPane->_propRow = *(_cssPane->_store->append());
+ _cssPane->_propRow[_cssPane->_cssColumns._colUnsetProp] = false;
+ _cssPane->_propRow[_cssPane->_cssColumns._propertyLabel] = token;
+ _cssPane->_propCol->add_attribute(_cssPane->_textRenderer
+ ->property_text(),
+ _cssPane->_cssColumns
+ ._propertyLabel);
+ }
+ }
+ }
+ }
+ }
+ }
+ else {
+ _cssPane->_store->clear();
+ _cssPane->hide();
+ }
+
+ _cssPane->_textRenderer->signal_edited().connect(sigc::mem_fun(*this,
+ &StyleDialog::
+ _handleEdited));
+ _cssPane->_treeView.signal_button_press_event().connect(sigc::mem_fun
+ (*this, &StyleDialog::
+ _delProperty),
+ false);
+ }
+}
+
+/**
+ * @brief StyleDialog::_selChanged
+ * When no row in _treeView of Style Dialog is selected, the _cssPane is hidden.
+ */
+void StyleDialog::_selChanged() {
+ if (_treeView.get_selection()->count_selected_rows() == 0) {
+ _cssPane->hide();
+ }
+}
+
+/**
+ * @brief StyleDialog::_handleEdited
+ * @param path
+ * @param new_text
+ * This function edits CSS properties of the selector chosen. new_text is used
+ * to update the property in XML repr. The value from selected selector is
+ * obtained and modified as per value of new_text. If a new property is added,
+ * value is appended with new_text. Later _updateStyleContent() is called to
+ * update XML repr and hence changes are reflected in the drawing too.
+ */
+void StyleDialog::_handleEdited(const Glib::ustring& path, const Glib::ustring& new_text)
+{
+ Gtk::TreeModel::iterator iterCss = _cssPane->_treeView.get_model()->get_iter(path);
+ if (iterCss) {
+ Gtk::TreeModel::Row row = *iterCss;
+ row[_cssPane->_cssColumns._propertyLabel] = new_text;
+ _cssPane->_editedProp = new_text;
+ }
+
+ // Selected selector row is obtained here to get corresponding key and value.
+ Glib::RefPtr<Gtk::TreeSelection> refTreeSelection = _treeView.get_selection();
+ Gtk::TreeModel::iterator iter = refTreeSelection->get_selected();
+ if (iter) {
+ Gtk::TreeModel::Row row = *iter;
+ std::string sel, key, value;
+ std::vector<InkSelector>::iterator it;
+ for (it = _selectorVec.begin(); it != _selectorVec.end(); ++it) {
+ sel = (*it)._xmlContent;
+ REMOVE_SPACES(sel);
+ if (!sel.empty()) {
+ key = strtok((char*)sel.c_str(), "{");
+ REMOVE_SPACES(key);
+ char *temp = strtok(NULL, "}");
+ if (strtok(temp, "}") != NULL) {
+ value = strtok(temp, "}");
+ }
+ }
+
+ Glib::ustring selectedRowLabel = row[_mColumns._selectorLabel];
+ std::string matchSelector = selectedRowLabel;
+ REMOVE_SPACES(matchSelector);
+
+ if (key == matchSelector) {
+ /** If a new property is added, existing value is appended with new
+ * property, else replacements in value are done in the 'else' block.
+ */
+ if (_cssPane->_newProperty) {
+ if (!new_text.empty()) {
+ value.append((new_text + ";").c_str());
+ _cssPane->_propCol->add_attribute(_cssPane->_textRenderer
+ ->property_text(),
+ _cssPane->_cssColumns
+ ._propertyLabel);
+ _cssPane->_newProperty = false;
+ }
+ }
+ else {
+ std::stringstream ss(value);
+ std::string token, editedToken;
+ std::size_t found = value.find(";");
+ if (found!=std::string::npos) {
+ while(std::getline(ss, token, ';')) {
+ REMOVE_SPACES(token);
+ if (!token.empty()) {
+ if (token.substr(0, token.find(":")) == _cssPane
+ ->_editedProp.substr(0, _cssPane->_editedProp
+ .find(":"))) {
+ editedToken = _cssPane->_editedProp;
+ size_t startPos = value.find(token);
+ value.replace(startPos, token.length(), editedToken);
+ }
+ }
+ }
+ }
+ }
+ value.erase(std::remove(value.begin(), value.end(), '\n'), value.end());
+ (*it)._xmlContent = key + "{" + value + "}\n";
+ _updateStyleContent();
+ }
+ }
+ }
+}
+
+/**
+ * @brief StyleDialog::_delProperty
+ * @param event
+ * @return
+ * This function deletes property when '-' in front of property in CSS panel is
+ * clicked. The property row is deleted from CSS panel and XML repr is updated.
+ * toDelProperty is the property to be deleted which is looked in 'value' and is
+ * erased from 'value'.
+ */
+bool StyleDialog::_delProperty(GdkEventButton *event)
+{
+ if (event->type == GDK_BUTTON_PRESS && event->button == 1) {
+ Gtk::TreeViewColumn *col = 0;
+ Gtk::TreeModel::Path path;
+ int x = static_cast<int>(event->x);
+ int y = static_cast<int>(event->y);
+ int x2 = 0;
+ int y2 = 0;
+ Gtk::TreeModel::Row cssRow;
+ Glib::ustring toDelProperty;
+ if (_cssPane->_treeView.get_path_at_pos(x, y, path, col, x2, y2)) {
+ if (col == _cssPane->_treeView.get_column(0)) {
+ Gtk::TreeModel::iterator cssIter = _cssPane->_treeView.get_selection()
+ ->get_selected();
+ if (cssIter) {
+ cssRow = *cssIter;
+ toDelProperty = cssRow[_cssPane->_cssColumns._propertyLabel];
+ }
+
+ Gtk::TreeModel::iterator iter = _treeView.get_selection()->get_selected();
+ if (iter) {
+ Gtk::TreeModel::Row row = *iter;
+ std::string sel, key, value;
+ std::vector<InkSelector>::iterator it;
+ for (it = _selectorVec.begin(); it != _selectorVec.end(); ++it) {
+ sel = (*it)._xmlContent;
+ REMOVE_SPACES(sel);
+ if (!sel.empty()) {
+ key = strtok((char*)sel.c_str(), "{");
+ REMOVE_SPACES(key);
+ char *temp = strtok(NULL, "}");
+ if (strtok(temp, "}") != NULL) {
+ value = strtok(temp, "}");
+ }
+ }
+
+ Glib::ustring selectedRowLabel = row[_mColumns._selectorLabel];
+ std::string matchSelector = selectedRowLabel;
+ REMOVE_SPACES(matchSelector);
+
+ if (key == matchSelector) {
+ std::size_t found = value.find(toDelProperty);
+ if (found!=std::string::npos) {
+ if (!toDelProperty.empty()) {
+ value.erase(found, toDelProperty.length()+1);
+ (*it)._xmlContent = key + "{" + value + "}\n";
+ _updateStyleContent();
+ _cssPane->_store->erase(cssRow);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ return false;
+}
+
+/**
+ * @brief StyleDialog::_selectObjects
+ * @param eventX
+ * @param eventY
+ * This function selects objects in the drawing corresponding to the selector
+ * selected in the treeview.
+ */
+void StyleDialog::_selectObjects(int eventX, int eventY)
+{
+ _desktop->selection->clear();
+ Gtk::TreeViewColumn *col = _treeView.get_column(1);
+ Gtk::TreeModel::Path path;
+ int x = eventX;
+ int y = eventY;
+ int x2 = 0;
+ int y2 = 0;
+ if (_treeView.get_path_at_pos(x, y, path, col, x2, y2)) {
+ if (col == _treeView.get_column(1)) {
+ Gtk::TreeModel::iterator iter = _store->get_iter(path);
+ if (iter) {
+ Gtk::TreeModel::Row row = *iter;
+ Gtk::TreeModel::Children children = row.children();
+ std::vector<SPObject *> objVec = row[_mColumns._colObj];
+ for (unsigned i = 0; i < objVec.size(); ++i) {
+ SPObject *obj = objVec[i];
+ _desktop->selection->add(obj);
+ }
+ if (children) {
+ _checkAllChildren(children);
+ }
+ }
+ }
+ }
+}
+
+/**
+ * @brief StyleDialog::_checkAllChildren
+ * @param children
+ * This function iterates children of the row selected in treeview and selects
+ * the objects corresponding to any selector in child rows.
+ */
+void StyleDialog::_checkAllChildren(Gtk::TreeModel::Children& children)
+{
+ for (Gtk::TreeModel::Children::iterator iter = children.begin();
+ iter!= children.end(); ++iter) {
+ Gtk::TreeModel::Row childrow = *iter;
+ std::vector<SPObject *> objVec = childrow[_mColumns._colObj];
+ for (unsigned i = 0; i < objVec.size(); ++i) {
+ SPObject *obj = objVec[i];
+ _desktop->selection->add(obj);
+ }
+ }
+}
+
+/**
+ * @brief StyleDialog::_selectRow
+ * This function selects the rows in treeview corresponding to an object selected
+ * in the drawing.
+ */
+void StyleDialog::_selectRow(Selection */*sel*/)
+{
+ SPObject *obj = NULL;
+ if (!_desktop->selection->isEmpty()) {
+ Inkscape::Selection* selection = _desktop->getSelection();
+ std::vector<SPObject*> selected = std::vector<SPObject *>
+ (selection->objects().begin(), selection->objects().end());
+ obj = selected.back();
+ }
+
+ /**
+ * If obj has some SPObject, then it is added to desktop's selection. If a
+ * row in treeview has children, those rows are checked too against selected
+ * object's id. If an object which is not present in any selector is selected,
+ * the treeview's selections are unselected.
+ */
+ if (obj != NULL) {
+ Gtk::TreeModel::Children children = _store->children();
+ for(Gtk::TreeModel::Children::iterator iter = children.begin();
+ iter != children.end(); ++iter) {
+ Gtk::TreeModel::Row row = *iter;
+ std::vector<SPObject *> objVec = row[_mColumns._colObj];
+ for (unsigned i = 0; i < objVec.size(); ++i) {
+ if (obj->getId() == objVec[i]->getId()) {
+ _treeView.get_selection()->select(row);
+ }
+ }
+ }
+ }
+ else {
+ _treeView.get_selection()->unselect_all();
+ }
+}
+
+} // namespace Dialog
+} // namespace UI
+} // namespace Inkscape
diff --git a/src/ui/dialog/styledialog.h b/src/ui/dialog/styledialog.h
new file mode 100644
index 000000000..b03a1d2e1
--- /dev/null
+++ b/src/ui/dialog/styledialog.h
@@ -0,0 +1,117 @@
+/** @file
+ * @brief A dialog for CSS selectors
+ */
+/* Authors:
+ * Kamalpreet Kaur Grewal
+ *
+ * Copyright (C) Kamalpreet Kaur Grewal 2016 <grewalkamal005@gmail.com>
+ *
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ */
+
+#ifndef STYLEDIALOG_H
+#define 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 "document.h"
+#include "ui/dialog/cssdialog.h"
+
+namespace Inkscape {
+namespace UI {
+namespace Dialog {
+
+/**
+ * @brief The StyleDialog class
+ * A list of CSS selectors will show up in this dialog. This dialog allows to
+ * add and delete selectors. Objects can be added to and removed from the selectors
+ * in the dialog. Besides, selection of any selector row selects the matching
+ * objects in the drawing and vice-versa.
+ */
+typedef std::pair<std::pair<std::string, std::vector<SPObject *> >, std::string>
+_selectorVecType;
+
+class StyleDialog : public UI::Widget::Panel
+{
+public:
+ StyleDialog();
+ ~StyleDialog();
+
+ static StyleDialog &getInstance() { return *new StyleDialog(); }
+ void setDesktop(SPDesktop* desktop);
+
+private:
+ void _styleButton(Gtk::Button& btn, char const* iconName, char const* tooltip);
+ std::string _setClassAttribute(std::vector<SPObject *>);
+
+ class InkSelector {
+ public:
+ std::string _selector;
+ std::vector<SPObject *> _matchingObjs;
+ std::string _xmlContent;
+ };
+
+ InkSelector inkSelector;
+ std::vector<InkSelector> _selectorVec;
+ std::vector<InkSelector> _getSelectorVec();
+ std::string _populateTree(std::vector<InkSelector>);
+ bool _handleButtonEvent(GdkEventButton *event);
+ void _buttonEventsSelectObjs(GdkEventButton *event);
+ void _selectObjects(int, int);
+ void _checkAllChildren(Gtk::TreeModel::Children& children);
+ Inkscape::XML::Node *_styleElementNode();
+ void _updateStyleContent();
+ void _selectRow(Selection *);
+
+ class ModelColumns : public Gtk::TreeModel::ColumnRecord
+ {
+ public:
+ ModelColumns()
+ { add(_selectorLabel); add(_colAddRemove); add(_colObj); }
+ Gtk::TreeModelColumn<Glib::ustring> _selectorLabel;
+ Gtk::TreeModelColumn<bool> _colAddRemove;
+ Gtk::TreeModelColumn<std::vector<SPObject *> > _colObj;
+ };
+
+ SPDesktop* _desktop;
+ SPDesktop* _targetDesktop;
+ ModelColumns _mColumns;
+ Gtk::VPaned _paned;
+ Gtk::VBox _mainBox;
+ Gtk::HBox _buttonBox;
+ Gtk::TreeView _treeView;
+ Glib::RefPtr<Gtk::TreeStore> _store;
+ Gtk::ScrolledWindow _scrolledWindow;
+ Gtk::Button* del;
+ Gtk::Button* create;
+ SPDocument* _document;
+ bool _styleExists;
+ Inkscape::XML::Node *_styleChild;
+ std::string _selectorName;
+ std::string _selectorValue;
+ bool _newDrawing;
+ CssDialog *_cssPane;
+ void _selAdd(Gtk::TreeModel::Row row);
+
+ // Signal handlers
+ void _addSelector();
+ void _delSelector();
+ void _selChanged();
+
+ // Signal handler for CssDialog
+ void _handleEdited(const Glib::ustring& path, const Glib::ustring& new_text);
+ bool _delProperty(GdkEventButton *event);
+};
+
+} // namespace Dialog
+} // namespace UI
+} // namespace Inkscape
+
+#endif // STYLEDIALOG_H
diff --git a/src/ui/tool/multi-path-manipulator.h b/src/ui/tool/multi-path-manipulator.h
index c908cede2..4f152e0a2 100644
--- a/src/ui/tool/multi-path-manipulator.h
+++ b/src/ui/tool/multi-path-manipulator.h
@@ -82,8 +82,19 @@ private:
template <typename R>
void invokeForAll(R (PathManipulator::*method)()) {
- for (MapType::iterator i = _mmap.begin(); i != _mmap.end(); ++i) {
- ((i->second.get())->*method)();
+ for (MapType::iterator i = _mmap.begin(); i != _mmap.end(); ) {
+ // Sometimes the PathManipulator got freed at loop end, thus
+ // invalidating the iterator so make sure that next_i will
+ // be a valid iterator and then assign i to it.
+ MapType::iterator next_i = i;
+ ++next_i;
+ // i->second is a boost::shared_ptr so try to hold on to it so
+ // it won't get freed prematurely by the WriteXML() method or
+ // whatever. See https://bugs.launchpad.net/inkscape/+bug/1617615
+ // Applicable to empty paths.
+ boost::shared_ptr<PathManipulator> hold(i->second);
+ ((hold.get())->*method)();
+ i = next_i;
}
}
template <typename R, typename A>
diff --git a/src/ui/tool/node.h b/src/ui/tool/node.h
index 025c460e2..a05f0e3b9 100644
--- a/src/ui/tool/node.h
+++ b/src/ui/tool/node.h
@@ -20,12 +20,7 @@
#include <iosfwd>
#include <stdexcept>
#include <cstddef>
-
-#if __cplusplus >= 201103L
#include <functional>
-#else
-#include <tr1/functional>
-#endif
#include <boost/enable_shared_from_this.hpp>
#include <boost/shared_ptr.hpp>
diff --git a/src/ui/tool/transform-handle-set.cpp b/src/ui/tool/transform-handle-set.cpp
index 33015fe11..083a7d0ba 100644
--- a/src/ui/tool/transform-handle-set.cpp
+++ b/src/ui/tool/transform-handle-set.cpp
@@ -183,6 +183,11 @@ void TransformHandle::ungrabbed(GdkEventButton *)
_setState(_state);
endTransform();
_th.signal_commit.emit(getCommitEvent());
+
+ //updates the positions of the nodes
+ Inkscape::UI::Tools::NodeTool *nt = INK_NODE_TOOL(_th._desktop->event_context);
+ ControlPointSelection* selection = nt->_selected_nodes;
+ selection->setOriginalPoints();
}
diff --git a/src/ui/tools/flood-tool.cpp b/src/ui/tools/flood-tool.cpp
index 1801a0ea0..6e1d085aa 100644
--- a/src/ui/tools/flood-tool.cpp
+++ b/src/ui/tools/flood-tool.cpp
@@ -21,6 +21,7 @@
#include <config.h>
#endif
+#include <cmath>
#include "trace/potrace/inkscape-potrace.h"
#include <2geom/pathvector.h>
#include <gdk/gdkkeysyms.h>
@@ -186,6 +187,21 @@ inline unsigned char * get_trace_pixel(guchar *trace_px, int x, int y, int width
}
/**
+ * \brief Check whether two unsigned integers are close to each other
+ *
+ * \param[in] a The 1st unsigned int
+ * \param[in] b The 2nd unsigned int
+ * \param[in] d The threshold for comparison
+ *
+ * \return true if |a-b| <= d; false otherwise
+ */
+static bool compare_guint32(guint32 const a, guint32 const b, guint32 const d)
+{
+ const int difference = std::abs(static_cast<int>(a) - static_cast<int>(b));
+ return difference <= d;
+}
+
+/**
* Compare a pixel in a pixel buffer with another pixel to determine if a point should be included in the fill operation.
* @param check The pixel in the pixel buffer to check.
* @param orig The original selected pixel to use as the fill target color.
@@ -196,7 +212,6 @@ inline unsigned char * get_trace_pixel(guchar *trace_px, int x, int y, int width
*/
static bool compare_pixels(guint32 check, guint32 orig, guint32 merged_orig_pixel, guint32 dtc, int threshold, PaintBucketChannels method)
{
- int diff = 0;
float hsl_check[3] = {0,0,0}, hsl_orig[3] = {0,0,0};
guint32 ac = 0, rc = 0, gc = 0, bc = 0;
@@ -222,27 +237,35 @@ static bool compare_pixels(guint32 check, guint32 orig, guint32 merged_orig_pixe
switch (method) {
case FLOOD_CHANNELS_ALPHA:
- return abs(static_cast<int>(ac) - ao) <= threshold;
+ return compare_guint32(ac, ao, threshold);
case FLOOD_CHANNELS_R:
- return abs(static_cast<int>(ac ? unpremul_alpha(rc, ac) : 0) - (ao ? unpremul_alpha(ro, ao) : 0)) <= threshold;
+ return compare_guint32(ac ? unpremul_alpha(rc, ac) : 0,
+ ao ? unpremul_alpha(ro, ao) : 0,
+ threshold);
case FLOOD_CHANNELS_G:
- return abs(static_cast<int>(ac ? unpremul_alpha(gc, ac) : 0) - (ao ? unpremul_alpha(go, ao) : 0)) <= threshold;
+ return compare_guint32(ac ? unpremul_alpha(gc, ac) : 0,
+ ao ? unpremul_alpha(go, ao) : 0,
+ threshold);
case FLOOD_CHANNELS_B:
- return abs(static_cast<int>(ac ? unpremul_alpha(bc, ac) : 0) - (ao ? unpremul_alpha(bo, ao) : 0)) <= threshold;
+ return compare_guint32(ac ? unpremul_alpha(bc, ac) : 0,
+ ao ? unpremul_alpha(bo, ao) : 0,
+ threshold);
case FLOOD_CHANNELS_RGB:
- guint32 amc, rmc, bmc, gmc;
- //amc = 255*255 - (255-ac)*(255-ad); amc = (amc + 127) / 255;
- //amc = (255-ac)*ad + 255*ac; amc = (amc + 127) / 255;
- amc = 255; // Why are we looking at desktop? Cairo version ignores destop alpha
- rmc = (255-ac)*rd + 255*rc; rmc = (rmc + 127) / 255;
- gmc = (255-ac)*gd + 255*gc; gmc = (gmc + 127) / 255;
- bmc = (255-ac)*bd + 255*bc; bmc = (bmc + 127) / 255;
-
- diff += abs(static_cast<int>(amc ? unpremul_alpha(rmc, amc) : 0) - (amop ? unpremul_alpha(rmop, amop) : 0));
- diff += abs(static_cast<int>(amc ? unpremul_alpha(gmc, amc) : 0) - (amop ? unpremul_alpha(gmop, amop) : 0));
- diff += abs(static_cast<int>(amc ? unpremul_alpha(bmc, amc) : 0) - (amop ? unpremul_alpha(bmop, amop) : 0));
- return ((diff / 3) <= ((threshold * 3) / 4));
-
+ {
+ guint32 amc, rmc, bmc, gmc;
+ //amc = 255*255 - (255-ac)*(255-ad); amc = (amc + 127) / 255;
+ //amc = (255-ac)*ad + 255*ac; amc = (amc + 127) / 255;
+ amc = 255; // Why are we looking at desktop? Cairo version ignores destop alpha
+ rmc = (255-ac)*rd + 255*rc; rmc = (rmc + 127) / 255;
+ gmc = (255-ac)*gd + 255*gc; gmc = (gmc + 127) / 255;
+ bmc = (255-ac)*bd + 255*bc; bmc = (bmc + 127) / 255;
+
+ int diff = 0; // The total difference between each of the 3 color components
+ diff += std::abs(static_cast<int>(amc ? unpremul_alpha(rmc, amc) : 0) - static_cast<int>(amop ? unpremul_alpha(rmop, amop) : 0));
+ diff += std::abs(static_cast<int>(amc ? unpremul_alpha(gmc, amc) : 0) - static_cast<int>(amop ? unpremul_alpha(gmop, amop) : 0));
+ diff += std::abs(static_cast<int>(amc ? unpremul_alpha(bmc, amc) : 0) - static_cast<int>(amop ? unpremul_alpha(bmop, amop) : 0));
+ return ((diff / 3) <= ((threshold * 3) / 4));
+ }
case FLOOD_CHANNELS_H:
return ((int)(fabs(hsl_check[0] - hsl_orig[0]) * 100.0) <= threshold);
case FLOOD_CHANNELS_S:
diff --git a/src/ui/tools/mesh-tool.cpp b/src/ui/tools/mesh-tool.cpp
index e628094d9..ac43b6c9d 100644
--- a/src/ui/tools/mesh-tool.cpp
+++ b/src/ui/tools/mesh-tool.cpp
@@ -1137,7 +1137,7 @@ static void sp_mesh_new_default(MeshTool &rc) {
Inkscape::FOR_FILL : Inkscape::FOR_STROKE;
// Ensure mesh is immediately editable.
- // Editting both fill and stroke at same time doesn't work well so avoid.
+ // Editing both fill and stroke at same time doesn't work well so avoid.
if (fill_or_stroke == Inkscape::FOR_FILL) {
prefs->setBool("/tools/mesh/edit_fill", true );
prefs->setBool("/tools/mesh/edit_stroke", false);
diff --git a/src/ui/tools/tool-base.cpp b/src/ui/tools/tool-base.cpp
index 8a35882b9..aab256712 100644
--- a/src/ui/tools/tool-base.cpp
+++ b/src/ui/tools/tool-base.cpp
@@ -94,6 +94,7 @@ ToolBase::ToolBase(gchar const *const *cursor_shape, gint hot_x, gint hot_y, boo
, _grdrag(NULL)
, shape_editor(NULL)
, space_panning(false)
+ , rotating_mode(false)
, _delayed_snap_event(NULL)
, _dse_callback_in_process(false)
, desktop(NULL)
@@ -327,99 +328,130 @@ bool ToolBase::root_handler(GdkEvent* event) {
static unsigned int panning = 0;
static unsigned int panning_cursor = 0;
static unsigned int zoom_rb = 0;
+ static double angle = 0;
Inkscape::Preferences *prefs = Inkscape::Preferences::get();
/// @todo REmove redundant /value in preference keys
tolerance = prefs->getIntLimited("/options/dragtolerance/value", 0, 0, 100);
bool allow_panning = prefs->getBool("/options/spacebarpans/value");
+ int rotation_snap = 180.0/prefs->getInt("/options/rotationsnapsperpi/value", 12);
gint ret = FALSE;
switch (event->type) {
- case GDK_2BUTTON_PRESS:
- if (panning) {
- panning = 0;
- sp_canvas_item_ungrab(SP_CANVAS_ITEM(desktop->acetate), event->button.time);
- ret = TRUE;
- } else {
- /* sp_desktop_dialog(); */
- }
- break;
-
- case GDK_BUTTON_PRESS:
- // save drag origin
- xp = (gint) event->button.x;
- yp = (gint) event->button.y;
- within_tolerance = true;
-
- button_w = Geom::Point(event->button.x, event->button.y);
-
- switch (event->button.button) {
- case 1:
- if (this->space_panning) {
- // When starting panning, make sure there are no snap events pending because these might disable the panning again
- if (_uses_snap) {
- sp_event_context_discard_delayed_snap_event(this);
- }
- panning = 1;
-
- sp_canvas_item_grab(SP_CANVAS_ITEM(desktop->acetate),
- GDK_KEY_RELEASE_MASK | GDK_BUTTON_RELEASE_MASK
- | GDK_POINTER_MOTION_MASK
- | GDK_POINTER_MOTION_HINT_MASK, NULL,
- event->button.time - 1);
-
+ case GDK_2BUTTON_PRESS:
+ if (panning) {
+ panning = 0;
+ sp_canvas_item_ungrab(SP_CANVAS_ITEM(desktop->acetate), event->button.time);
ret = TRUE;
+ } else {
+ /* sp_desktop_dialog(); */
}
break;
- case 2:
- if (event->button.state & GDK_SHIFT_MASK) {
- zoom_rb = 2;
- } else {
- // When starting panning, make sure there are no snap events pending because these might disable the panning again
- if (_uses_snap) {
- sp_event_context_discard_delayed_snap_event(this);
- }
- panning = 2;
+ case GDK_BUTTON_PRESS:
+ // save drag origin
+ xp = (gint) event->button.x;
+ yp = (gint) event->button.y;
+ within_tolerance = true;
- sp_canvas_item_grab(SP_CANVAS_ITEM(desktop->acetate),
- GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK
- | GDK_POINTER_MOTION_HINT_MASK, NULL,
- event->button.time - 1);
+ button_w = Geom::Point(event->button.x, event->button.y);
+ switch (event->button.button) {
+ case 1:
+ if (this->space_panning) {
+ // When starting panning, make sure there are no snap events pending because these might disable the panning again
+ if (_uses_snap) {
+ sp_event_context_discard_delayed_snap_event(this);
+ }
+ panning = 1;
- }
+ sp_canvas_item_grab(SP_CANVAS_ITEM(desktop->acetate),
+ GDK_KEY_RELEASE_MASK | GDK_BUTTON_RELEASE_MASK
+ | GDK_POINTER_MOTION_MASK
+ | GDK_POINTER_MOTION_HINT_MASK, NULL,
+ event->button.time - 1);
- ret = TRUE;
- break;
+ ret = TRUE;
+ }
+ desktop->canvas->clearRotateTo();
+ this->rotating_mode = false;
+ break;
- case 3:
- if ((event->button.state & GDK_SHIFT_MASK) || (event->button.state & GDK_CONTROL_MASK)) {
- // When starting panning, make sure there are no snap events pending because these might disable the panning again
- if (_uses_snap) {
- sp_event_context_discard_delayed_snap_event(this);
+ case 2:
+ if (event->button.state & GDK_CONTROL_MASK) {
+ sp_canvas_item_ungrab(SP_CANVAS_ITEM(desktop->acetate), event->button.time);
+ desktop->canvas->startRotateTo(desktop->namedview->document_rotation);
+ this->rotating_mode = true;
+ this->message_context->set(Inkscape::INFORMATION_MESSAGE,
+ _("<b>MMB + mouse move</b> to rotate canvas, use modifiers on screen to change snaps"));
+ } else {
+ if (event->button.state & GDK_SHIFT_MASK) {
+ zoom_rb = 2;
+ } else {
+ // When starting panning, make sure there are no snap events pending because these might disable the panning again
+ if (_uses_snap) {
+ sp_event_context_discard_delayed_snap_event(this);
+ }
+ panning = 2;
+
+ sp_canvas_item_grab(SP_CANVAS_ITEM(desktop->acetate),
+ GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK
+ | GDK_POINTER_MOTION_HINT_MASK, NULL,
+ event->button.time - 1);
+ }
+ ret = TRUE;
}
- panning = 3;
+ ret = TRUE;
+ break;
- sp_canvas_item_grab(SP_CANVAS_ITEM(desktop->acetate),
- GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK
- | GDK_POINTER_MOTION_HINT_MASK, NULL,
- event->button.time);
+ case 3:
+ if ((event->button.state & GDK_SHIFT_MASK) || (event->button.state & GDK_CONTROL_MASK)) {
+ // When starting panning, make sure there are no snap events pending because these might disable the panning again
+ if (_uses_snap) {
+ sp_event_context_discard_delayed_snap_event(this);
+ }
+ panning = 3;
+
+ sp_canvas_item_grab(SP_CANVAS_ITEM(desktop->acetate),
+ GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK
+ | GDK_POINTER_MOTION_HINT_MASK, NULL,
+ event->button.time);
+ ret = TRUE;
+ } else if( !this->space_panning) {
+ sp_event_root_menu_popup(desktop, NULL, event);
+ }
ret = TRUE;
- } else {
- sp_event_root_menu_popup(desktop, NULL, event);
- }
- break;
+ desktop->canvas->clearRotateTo();
+ this->rotating_mode = false;
+ break;
- default:
+ default:
+ break;
+ }
break;
- }
- break;
- case GDK_MOTION_NOTIFY:
- if (panning) {
- if (panning == 4 && !xp && !yp ) {
+ case GDK_MOTION_NOTIFY:
+ if (this->rotating_mode) {
+ button_w = Geom::Point(event->motion.x, event->motion.y);
+ Geom::Point const motion_dt(desktop->doc2dt(desktop->w2d(button_w)));
+ Geom::Rect view = desktop->get_display_area();
+ Geom::Point view_center = desktop->doc2dt(view.midpoint());
+ Geom::Ray center_ray(motion_dt, view_center);
+ angle = Geom::deg_from_rad(center_ray.angle()) - 90;
+ if (event->motion.state & GDK_SHIFT_MASK && event->motion.state & GDK_CONTROL_MASK) {
+ angle = 0;
+ } else if(event->motion.state & GDK_CONTROL_MASK) {
+ angle = floor(angle/rotation_snap) * rotation_snap;
+ } else if (event->motion.state & GDK_SHIFT_MASK) {
+ angle = desktop->namedview->document_rotation;
+ } else if (event->motion.state & GDK_MOD1_MASK) {
+ //Decimal raw angle
+ } else {
+ angle = floor(angle);
+ }
+ desktop->canvas->rotateTo(angle);
+ } else if (panning == 4 && !xp && !yp ) {
// <Space> + mouse panning started, save location and grab canvas
xp = event->motion.x;
yp = event->motion.y;
@@ -431,401 +463,465 @@ bool ToolBase::root_handler(GdkEvent* event) {
| GDK_POINTER_MOTION_HINT_MASK, NULL,
event->motion.time - 1);
}
+ if (panning && !this->rotating_mode) {
+ if ((panning == 2 && !(event->motion.state & GDK_BUTTON2_MASK))
+ || (panning == 1 && !(event->motion.state & GDK_BUTTON1_MASK))
+ || (panning == 3 && !(event->motion.state & GDK_BUTTON3_MASK))) {
+ /* Gdk seems to lose button release for us sometimes :-( */
+ panning = 0;
+ sp_canvas_item_ungrab(SP_CANVAS_ITEM(desktop->acetate), event->button.time);
+ ret = TRUE;
+ } else {
+ if (within_tolerance && (abs((gint) event->motion.x - xp)
+ < tolerance) && (abs((gint) event->motion.y - yp)
+ < tolerance)) {
+ // do not drag if we're within tolerance from origin
+ break;
+ }
+
+ // Once the user has moved farther than tolerance from
+ // the original location (indicating they intend to move
+ // the object, not click), then always process the motion
+ // notify coordinates as given (no snapping back to origin)
+ within_tolerance = false;
+
+ // gobble subsequent motion events to prevent "sticking"
+ // when scrolling is slow
+ gobble_motion_events(panning == 2 ? GDK_BUTTON2_MASK : (panning
+ == 1 ? GDK_BUTTON1_MASK : GDK_BUTTON3_MASK));
+
+ if (panning_cursor == 0) {
+ panning_cursor = 1;
+ this->sp_event_context_set_cursor(GDK_FLEUR);
+ }
+
+ Geom::Point const motion_w(event->motion.x, event->motion.y);
+ Geom::Point const moved_w(motion_w - button_w);
+ this->desktop->scroll_world(moved_w, true); // we're still scrolling, do not redraw
+ ret = TRUE;
+ }
+ } else if (zoom_rb) {
+ Geom::Point const motion_w(event->motion.x, event->motion.y);
+ Geom::Point const motion_dt(desktop->w2d(motion_w));
- if ((panning == 2 && !(event->motion.state & GDK_BUTTON2_MASK))
- || (panning == 1 && !(event->motion.state & GDK_BUTTON1_MASK))
- || (panning == 3 && !(event->motion.state & GDK_BUTTON3_MASK))) {
- /* Gdk seems to lose button release for us sometimes :-( */
- panning = 0;
- sp_canvas_item_ungrab(SP_CANVAS_ITEM(desktop->acetate), event->button.time);
- ret = TRUE;
- } else {
if (within_tolerance && (abs((gint) event->motion.x - xp)
< tolerance) && (abs((gint) event->motion.y - yp)
< tolerance)) {
- // do not drag if we're within tolerance from origin
- break;
+ break; // do not drag if we're within tolerance from origin
}
- // Once the user has moved farther than tolerance from
- // the original location (indicating they intend to move
- // the object, not click), then always process the motion
- // notify coordinates as given (no snapping back to origin)
+ // Once the user has moved farther than tolerance from the original location
+ // (indicating they intend to move the object, not click), then always process the
+ // motion notify coordinates as given (no snapping back to origin)
within_tolerance = false;
- // gobble subsequent motion events to prevent "sticking"
- // when scrolling is slow
- gobble_motion_events(panning == 2 ? GDK_BUTTON2_MASK : (panning
- == 1 ? GDK_BUTTON1_MASK : GDK_BUTTON3_MASK));
-
- if (panning_cursor == 0) {
- panning_cursor = 1;
- this->sp_event_context_set_cursor(GDK_FLEUR);
+ if (Inkscape::Rubberband::get(desktop)->is_started()) {
+ Inkscape::Rubberband::get(desktop)->move(motion_dt);
+ } else {
+ Inkscape::Rubberband::get(desktop)->start(desktop, motion_dt);
}
- Geom::Point const motion_w(event->motion.x, event->motion.y);
- Geom::Point const moved_w(motion_w - button_w);
- this->desktop->scroll_world(moved_w, true); // we're still scrolling, do not redraw
- ret = TRUE;
- }
- } else if (zoom_rb) {
- Geom::Point const motion_w(event->motion.x, event->motion.y);
- Geom::Point const motion_dt(desktop->w2d(motion_w));
-
- if (within_tolerance && (abs((gint) event->motion.x - xp)
- < tolerance) && (abs((gint) event->motion.y - yp)
- < tolerance)) {
- break; // do not drag if we're within tolerance from origin
+ if (zoom_rb == 2) {
+ gobble_motion_events(GDK_BUTTON2_MASK);
+ }
}
+ break;
- // Once the user has moved farther than tolerance from the original location
- // (indicating they intend to move the object, not click), then always process the
- // motion notify coordinates as given (no snapping back to origin)
- within_tolerance = false;
-
- if (Inkscape::Rubberband::get(desktop)->is_started()) {
- Inkscape::Rubberband::get(desktop)->move(motion_dt);
+ case GDK_BUTTON_RELEASE:
+ if (this->rotating_mode && event->button.button == 2) {
+ desktop->canvas->clearRotateTo();
+ this->rotating_mode = false;
+ ret = TRUE;
+ if (desktop->canvas->endRotateTo()) {
+ sp_repr_set_svg_double(desktop->namedview->getRepr(), "inkscape:document-rotation", angle);
+ }
} else {
- Inkscape::Rubberband::get(desktop)->start(desktop, motion_dt);
- }
+ xp = yp = 0;
+ if (panning_cursor == 1) {
+ panning_cursor = 0;
+ GtkWidget *w = GTK_WIDGET(this->desktop->getCanvas());
+ gdk_window_set_cursor(gtk_widget_get_window (w), this->cursor);
+ }
- if (zoom_rb == 2) {
- gobble_motion_events(GDK_BUTTON2_MASK);
- }
- }
- break;
+ if (within_tolerance && (panning || zoom_rb)) {
+ zoom_rb = 0;
- case GDK_BUTTON_RELEASE:
- xp = yp = 0;
+ if (panning) {
+ panning = 0;
+ sp_canvas_item_ungrab(SP_CANVAS_ITEM(desktop->acetate),
+ event->button.time);
+ }
- if (panning_cursor == 1) {
- panning_cursor = 0;
- GtkWidget *w = GTK_WIDGET(this->desktop->getCanvas());
- gdk_window_set_cursor(gtk_widget_get_window (w), this->cursor);
- }
+ Geom::Point const event_w(event->button.x, event->button.y);
+ Geom::Point const event_dt(desktop->w2d(event_w));
- if (within_tolerance && (panning || zoom_rb)) {
- zoom_rb = 0;
+ double const zoom_inc = prefs->getDoubleLimited(
+ "/options/zoomincrement/value", M_SQRT2, 1.01, 10);
- if (panning) {
- panning = 0;
- sp_canvas_item_ungrab(SP_CANVAS_ITEM(desktop->acetate),
- event->button.time);
- }
+ desktop->zoom_relative_keep_point(event_dt, (event->button.state
+ & GDK_SHIFT_MASK) ? 1 / zoom_inc : zoom_inc);
- Geom::Point const event_w(event->button.x, event->button.y);
- Geom::Point const event_dt(desktop->w2d(event_w));
+ desktop->updateNow();
+ ret = TRUE;
+ } else if (panning == event->button.button) {
+ panning = 0;
+ sp_canvas_item_ungrab(SP_CANVAS_ITEM(desktop->acetate),
+ event->button.time);
- double const zoom_inc = prefs->getDoubleLimited(
- "/options/zoomincrement/value", M_SQRT2, 1.01, 10);
+ // in slow complex drawings, some of the motion events are lost;
+ // to make up for this, we scroll it once again to the button-up event coordinates
+ // (i.e. canvas will always get scrolled all the way to the mouse release point,
+ // even if few intermediate steps were visible)
+ Geom::Point const motion_w(event->button.x, event->button.y);
+ Geom::Point const moved_w(motion_w - button_w);
- desktop->zoom_relative_keep_point(event_dt, (event->button.state
- & GDK_SHIFT_MASK) ? 1 / zoom_inc : zoom_inc);
+ this->desktop->scroll_world(moved_w);
+ desktop->updateNow();
+ ret = TRUE;
+ } else if (zoom_rb == event->button.button) {
+ zoom_rb = 0;
- desktop->updateNow();
- ret = TRUE;
- } else if (panning == event->button.button) {
- panning = 0;
- sp_canvas_item_ungrab(SP_CANVAS_ITEM(desktop->acetate),
- event->button.time);
-
- // in slow complex drawings, some of the motion events are lost;
- // to make up for this, we scroll it once again to the button-up event coordinates
- // (i.e. canvas will always get scrolled all the way to the mouse release point,
- // even if few intermediate steps were visible)
- Geom::Point const motion_w(event->button.x, event->button.y);
- Geom::Point const moved_w(motion_w - button_w);
-
- this->desktop->scroll_world(moved_w);
- desktop->updateNow();
- ret = TRUE;
- } else if (zoom_rb == event->button.button) {
- zoom_rb = 0;
+ Geom::OptRect const b = Inkscape::Rubberband::get(desktop)->getRectangle();
+ Inkscape::Rubberband::get(desktop)->stop();
- Geom::OptRect const b = Inkscape::Rubberband::get(desktop)->getRectangle();
- Inkscape::Rubberband::get(desktop)->stop();
+ if (b && !within_tolerance) {
+ desktop->set_display_area(*b, 10);
+ }
- if (b && !within_tolerance) {
- desktop->set_display_area(*b, 10);
+ ret = TRUE;
+ }
+ if (this->rotating_mode && event->button.button != 3) {
+ desktop->canvas->clearRotateTo();
+ this->rotating_mode = false;
+ ret = TRUE;
+ desktop->canvas->endRotateTo();
+ }
}
+ break;
- ret = TRUE;
- }
- break;
-
- case GDK_KEY_PRESS: {
- double const acceleration = prefs->getDoubleLimited(
- "/options/scrollingacceleration/value", 0, 0, 6);
- int const key_scroll = prefs->getIntLimited("/options/keyscroll/value",
- 10, 0, 1000);
-
- switch (get_group0_keyval(&event->key)) {
- // GDK insists on stealing these keys (F1 for no idea what, tab for cycling widgets
- // in the editing window). So we resteal them back and run our regular shortcut
- // invoker on them.
- unsigned int shortcut;
- case GDK_KEY_Tab:
- case GDK_KEY_ISO_Left_Tab:
- case GDK_KEY_F1:
- shortcut = get_group0_keyval(&event->key);
-
- if (event->key.state & GDK_SHIFT_MASK) {
- shortcut |= SP_SHORTCUT_SHIFT_MASK;
+ case GDK_KEY_PRESS: {
+ double const acceleration = prefs->getDoubleLimited(
+ "/options/scrollingacceleration/value", 0, 0, 6);
+ int const key_scroll = prefs->getIntLimited("/options/keyscroll/value",
+ 10, 0, 1000);
+
+ if (this->rotating_mode &&
+ get_group0_keyval(&event->key) != GDK_KEY_space &&
+ get_group0_keyval(&event->key) != GDK_KEY_Shift_L &&
+ get_group0_keyval(&event->key) != GDK_KEY_Shift_R &&
+ get_group0_keyval(&event->key) != GDK_KEY_Control_L &&
+ get_group0_keyval(&event->key) != GDK_KEY_Control_R &&
+ get_group0_keyval(&event->key) != GDK_KEY_Alt_L &&
+ get_group0_keyval(&event->key) != GDK_KEY_Alt_R )
+ {
+ desktop->canvas->clearRotateTo();
+ this->rotating_mode = false;
+ ret = TRUE;
+ desktop->canvas->endRotateTo();
+ break;
}
- if (event->key.state & GDK_CONTROL_MASK) {
- shortcut |= SP_SHORTCUT_CONTROL_MASK;
- }
+ switch (get_group0_keyval(&event->key)) {
+ // GDK insists on stealing these keys (F1 for no idea what, tab for cycling widgets
+ // in the editing window). So we resteal them back and run our regular shortcut
+ // invoker on them.
+ unsigned int shortcut;
+ case GDK_KEY_Tab:
+ case GDK_KEY_ISO_Left_Tab:
+ case GDK_KEY_F1:
+ shortcut = get_group0_keyval(&event->key);
+
+ if (event->key.state & GDK_SHIFT_MASK) {
+ shortcut |= SP_SHORTCUT_SHIFT_MASK;
+ }
- if (event->key.state & GDK_MOD1_MASK) {
- shortcut |= SP_SHORTCUT_ALT_MASK;
- }
+ if (event->key.state & GDK_CONTROL_MASK) {
+ shortcut |= SP_SHORTCUT_CONTROL_MASK;
+ }
- ret = sp_shortcut_invoke(shortcut, desktop);
- break;
+ if (event->key.state & GDK_MOD1_MASK) {
+ shortcut |= SP_SHORTCUT_ALT_MASK;
+ }
- case GDK_KEY_Q:
- case GDK_KEY_q:
- if (desktop->quick_zoomed()) {
- ret = TRUE;
- }
- if (!MOD__SHIFT(event) && !MOD__CTRL(event) && !MOD__ALT(event)) {
- desktop->zoom_quick(true);
- ret = TRUE;
- }
- break;
+ ret = sp_shortcut_invoke(shortcut, desktop);
+ break;
- case GDK_KEY_W:
- case GDK_KEY_w:
- case GDK_KEY_F4:
- /* Close view */
- if (MOD__CTRL_ONLY(event)) {
- sp_ui_close_view(NULL);
- ret = TRUE;
- }
- break;
+ case GDK_KEY_Q:
+ case GDK_KEY_q:
+ if (desktop->quick_zoomed()) {
+ ret = TRUE;
+ }
+ if (!MOD__SHIFT(event) && !MOD__CTRL(event) && !MOD__ALT(event)) {
+ desktop->zoom_quick(true);
+ ret = TRUE;
+ }
+ break;
- case GDK_KEY_Left: // Ctrl Left
- case GDK_KEY_KP_Left:
- case GDK_KEY_KP_4:
- if (MOD__CTRL_ONLY(event)) {
- int i = (int) floor(key_scroll * accelerate_scroll(event,
- acceleration, desktop->getCanvas()));
+ case GDK_KEY_W:
+ case GDK_KEY_w:
+ case GDK_KEY_F4:
+ /* Close view */
+ if (MOD__CTRL_ONLY(event)) {
+ sp_ui_close_view(NULL);
+ ret = TRUE;
+ }
+ break;
- gobble_key_events(get_group0_keyval(&event->key), GDK_CONTROL_MASK);
- this->desktop->scroll_world(i, 0);
- ret = TRUE;
- }
- break;
+ case GDK_KEY_Left: // Ctrl Left
+ case GDK_KEY_KP_Left:
+ case GDK_KEY_KP_4:
+ if (MOD__CTRL_ONLY(event)) {
+ int i = (int) floor(key_scroll * accelerate_scroll(event,
+ acceleration, desktop->getCanvas()));
- case GDK_KEY_Up: // Ctrl Up
- case GDK_KEY_KP_Up:
- case GDK_KEY_KP_8:
- if (MOD__CTRL_ONLY(event)) {
- int i = (int) floor(key_scroll * accelerate_scroll(event,
- acceleration, desktop->getCanvas()));
+ gobble_key_events(get_group0_keyval(&event->key), GDK_CONTROL_MASK);
+ this->desktop->scroll_world(i, 0);
+ ret = TRUE;
+ }
+ break;
- gobble_key_events(get_group0_keyval(&event->key), GDK_CONTROL_MASK);
- this->desktop->scroll_world(0, i);
- ret = TRUE;
- }
- break;
+ case GDK_KEY_Up: // Ctrl Up
+ case GDK_KEY_KP_Up:
+ case GDK_KEY_KP_8:
+ if (MOD__CTRL_ONLY(event)) {
+ int i = (int) floor(key_scroll * accelerate_scroll(event,
+ acceleration, desktop->getCanvas()));
- case GDK_KEY_Right: // Ctrl Right
- case GDK_KEY_KP_Right:
- case GDK_KEY_KP_6:
- if (MOD__CTRL_ONLY(event)) {
- int i = (int) floor(key_scroll * accelerate_scroll(event,
- acceleration, desktop->getCanvas()));
+ gobble_key_events(get_group0_keyval(&event->key), GDK_CONTROL_MASK);
+ this->desktop->scroll_world(0, i);
+ ret = TRUE;
+ }
+ break;
- gobble_key_events(get_group0_keyval(&event->key), GDK_CONTROL_MASK);
- this->desktop->scroll_world(-i, 0);
- ret = TRUE;
- }
- break;
+ case GDK_KEY_Right: // Ctrl Right
+ case GDK_KEY_KP_Right:
+ case GDK_KEY_KP_6:
+ if (MOD__CTRL_ONLY(event)) {
+ int i = (int) floor(key_scroll * accelerate_scroll(event,
+ acceleration, desktop->getCanvas()));
- case GDK_KEY_Down: // Ctrl Down
- case GDK_KEY_KP_Down:
- case GDK_KEY_KP_2:
- if (MOD__CTRL_ONLY(event)) {
- int i = (int) floor(key_scroll * accelerate_scroll(event,
- acceleration, desktop->getCanvas()));
+ gobble_key_events(get_group0_keyval(&event->key), GDK_CONTROL_MASK);
+ this->desktop->scroll_world(-i, 0);
+ ret = TRUE;
+ }
+ break;
- gobble_key_events(get_group0_keyval(&event->key), GDK_CONTROL_MASK);
- this->desktop->scroll_world(0, -i);
- ret = TRUE;
- }
- break;
+ case GDK_KEY_Down: // Ctrl Down
+ case GDK_KEY_KP_Down:
+ case GDK_KEY_KP_2:
+ if (MOD__CTRL_ONLY(event)) {
+ int i = (int) floor(key_scroll * accelerate_scroll(event,
+ acceleration, desktop->getCanvas()));
- case GDK_KEY_Menu:
- sp_event_root_menu_popup(desktop, NULL, event);
- ret = TRUE;
- break;
+ gobble_key_events(get_group0_keyval(&event->key), GDK_CONTROL_MASK);
+ this->desktop->scroll_world(0, -i);
+ ret = TRUE;
+ }
+ break;
- case GDK_KEY_F10:
- if (MOD__SHIFT_ONLY(event)) {
+ case GDK_KEY_Menu:
sp_event_root_menu_popup(desktop, NULL, event);
ret = TRUE;
- }
- break;
+ break;
- case GDK_KEY_space:
- within_tolerance = true;
- xp = yp = 0;
- if (!allow_panning) break;
- panning = 4;
- this->space_panning = true;
- this->message_context->set(Inkscape::INFORMATION_MESSAGE,
- _("<b>Space+mouse move</b> to pan canvas"));
+ case GDK_KEY_F10:
+ if (MOD__SHIFT_ONLY(event)) {
+ sp_event_root_menu_popup(desktop, NULL, event);
+ ret = TRUE;
+ }
+ break;
- ret = TRUE;
+ case GDK_KEY_space:
+// if (event->key.state & GDK_CONTROL_MASK) {
+// sp_canvas_item_ungrab(SP_CANVAS_ITEM(desktop->acetate), event->button.time);
+// desktop->canvas->startRotateTo(desktop->namedview->document_rotation);
+// this->rotating_mode = true;
+// this->message_context->set(Inkscape::INFORMATION_MESSAGE,
+// _("<b>Space+mouse move</b> to rotate canvas, use modifiers on screen to change snaps"));
+// } else {
+ within_tolerance = true;
+ xp = yp = 0;
+ if (!allow_panning) break;
+ panning = 4;
+ this->space_panning = true;
+ this->message_context->set(Inkscape::INFORMATION_MESSAGE,
+ _("<b>Space+mouse move</b> to pan canvas"));
+// }
+ ret = TRUE;
+ break;
+
+ case GDK_KEY_z:
+ case GDK_KEY_Z:
+ if (MOD__ALT_ONLY(event)) {
+ desktop->zoom_grab_focus();
+ ret = TRUE;
+ }
+ break;
+
+ default:
+ break;
+ }
+ }
break;
- case GDK_KEY_z:
- case GDK_KEY_Z:
- if (MOD__ALT_ONLY(event)) {
- desktop->zoom_grab_focus();
+ case GDK_KEY_RELEASE:
+ if (this->rotating_mode &&
+ get_group0_keyval(&event->key) != GDK_KEY_space &&
+ get_group0_keyval(&event->key) != GDK_KEY_Shift_L &&
+ get_group0_keyval(&event->key) != GDK_KEY_Shift_R &&
+ get_group0_keyval(&event->key) != GDK_KEY_Control_L &&
+ get_group0_keyval(&event->key) != GDK_KEY_Control_R &&
+ get_group0_keyval(&event->key) != GDK_KEY_Alt_L &&
+ get_group0_keyval(&event->key) != GDK_KEY_Alt_R )
+ {
+ desktop->canvas->clearRotateTo();
+ this->rotating_mode = false;
ret = TRUE;
+ desktop->canvas->endRotateTo();
+ break;
}
- break;
- default:
- break;
+ // Stop panning on any key release
+ if (this->space_panning) {
+ this->space_panning = false;
+ this->message_context->clear();
}
- }
- break;
- case GDK_KEY_RELEASE:
- // Stop panning on any key release
- if (this->space_panning) {
- this->space_panning = false;
- this->message_context->clear();
- }
+ if (panning) {
+ panning = 0;
+ xp = yp = 0;
- if (panning) {
- panning = 0;
- xp = yp = 0;
+ sp_canvas_item_ungrab(SP_CANVAS_ITEM(desktop->acetate),
+ event->key.time);
- sp_canvas_item_ungrab(SP_CANVAS_ITEM(desktop->acetate),
- event->key.time);
+ desktop->updateNow();
+ }
- desktop->updateNow();
- }
+ if (panning_cursor == 1) {
+ panning_cursor = 0;
+ GtkWidget *w = GTK_WIDGET(this->desktop->getCanvas());
+ gdk_window_set_cursor(gtk_widget_get_window (w), this->cursor);
+ }
- if (panning_cursor == 1) {
- panning_cursor = 0;
- GtkWidget *w = GTK_WIDGET(this->desktop->getCanvas());
- gdk_window_set_cursor(gtk_widget_get_window (w), this->cursor);
- }
+ switch (get_group0_keyval(&event->key)) {
+ case GDK_KEY_space:
+// if (this->rotating_mode) {
+// desktop->canvas->clearRotateTo();
+// this->rotating_mode = false;
+// ret = TRUE;
+// if (desktop->canvas->endRotateTo()) {
+// sp_repr_set_svg_double(desktop->namedview->getRepr(), "inkscape:document-rotation", angle);
+// }
+// }
+ if (within_tolerance) {
+ // Space was pressed, but not panned
+ sp_toggle_selector(desktop);
+
+ // Be careful, sp_toggle_selector will delete ourselves.
+ // Thus, make sure we return immediately.
+ return true;
+ }
+ break;
- switch (get_group0_keyval(&event->key)) {
- case GDK_KEY_space:
- if (within_tolerance) {
- // Space was pressed, but not panned
- sp_toggle_selector(desktop);
+ case GDK_KEY_Q:
+ case GDK_KEY_q:
+ if (desktop->quick_zoomed()) {
+ desktop->zoom_quick(false);
+ ret = TRUE;
+ }
+ break;
- // Be careful, sp_toggle_selector will delete ourselves.
- // Thus, make sure we return immediately.
- return true;
+ default:
+ break;
}
-
break;
- case GDK_KEY_Q:
- case GDK_KEY_q:
- if (desktop->quick_zoomed()) {
- desktop->zoom_quick(false);
- ret = TRUE;
+ case GDK_SCROLL: {
+ if (this->rotating_mode) {
+ desktop->canvas->clearRotateTo();
+ this->rotating_mode = false;
+ desktop->canvas->endRotateTo();
}
- break;
-
- default:
- break;
- }
- break;
+ bool ctrl = (event->scroll.state & GDK_CONTROL_MASK);
+ bool wheelzooms = prefs->getBool("/options/wheelzooms/value");
- case GDK_SCROLL: {
- bool ctrl = (event->scroll.state & GDK_CONTROL_MASK);
- bool wheelzooms = prefs->getBool("/options/wheelzooms/value");
+ int const wheel_scroll = prefs->getIntLimited(
+ "/options/wheelscroll/value", 40, 0, 1000);
- int const wheel_scroll = prefs->getIntLimited(
- "/options/wheelscroll/value", 40, 0, 1000);
+ // Size of smooth-scrolls (only used in GTK+ 3)
+ gdouble delta_x = 0;
+ gdouble delta_y = 0;
- // Size of smooth-scrolls (only used in GTK+ 3)
- gdouble delta_x = 0;
- gdouble delta_y = 0;
-
- /* shift + wheel, pan left--right */
- if (event->scroll.state & GDK_SHIFT_MASK) {
- switch (event->scroll.direction) {
- case GDK_SCROLL_UP:
- desktop->scroll_world(wheel_scroll, 0);
- break;
+ /* shift + wheel, pan left--right */
+ if (event->scroll.state & GDK_SHIFT_MASK) {
+ switch (event->scroll.direction) {
+ case GDK_SCROLL_UP:
+ desktop->scroll_world(wheel_scroll, 0);
+ break;
- case GDK_SCROLL_DOWN:
- desktop->scroll_world(-wheel_scroll, 0);
- break;
+ case GDK_SCROLL_DOWN:
+ desktop->scroll_world(-wheel_scroll, 0);
+ break;
- default:
- break;
- }
+ default:
+ break;
+ }
- /* ctrl + wheel, zoom in--out */
- } else if ((ctrl && !wheelzooms) || (!ctrl && wheelzooms)) {
- double rel_zoom;
- double const zoom_inc = prefs->getDoubleLimited(
- "/options/zoomincrement/value", M_SQRT2, 1.01, 10);
+ /* ctrl + wheel, zoom in--out */
+ } else if ((ctrl && !wheelzooms) || (!ctrl && wheelzooms)) {
+ double rel_zoom;
+ double const zoom_inc = prefs->getDoubleLimited(
+ "/options/zoomincrement/value", M_SQRT2, 1.01, 10);
- switch (event->scroll.direction) {
- case GDK_SCROLL_UP:
- rel_zoom = zoom_inc;
- break;
+ switch (event->scroll.direction) {
+ case GDK_SCROLL_UP:
+ rel_zoom = zoom_inc;
+ break;
- case GDK_SCROLL_DOWN:
- rel_zoom = 1 / zoom_inc;
- break;
+ case GDK_SCROLL_DOWN:
+ rel_zoom = 1 / zoom_inc;
+ break;
- default:
- rel_zoom = 0.0;
- break;
- }
+ default:
+ rel_zoom = 0.0;
+ break;
+ }
- if (rel_zoom != 0.0) {
- Geom::Point const scroll_dt = desktop->point();
- desktop->zoom_relative_keep_point(scroll_dt, rel_zoom);
- }
+ if (rel_zoom != 0.0) {
+ Geom::Point const scroll_dt = desktop->point();
+ desktop->zoom_relative_keep_point(scroll_dt, rel_zoom);
+ }
- /* no modifier, pan up--down (left--right on multiwheel mice?) */
- } else {
- switch (event->scroll.direction) {
- case GDK_SCROLL_UP:
- desktop->scroll_world(0, wheel_scroll);
- break;
+ /* no modifier, pan up--down (left--right on multiwheel mice?) */
+ } else {
+ switch (event->scroll.direction) {
+ case GDK_SCROLL_UP:
+ desktop->scroll_world(0, wheel_scroll);
+ break;
- case GDK_SCROLL_DOWN:
- desktop->scroll_world(0, -wheel_scroll);
- break;
+ case GDK_SCROLL_DOWN:
+ desktop->scroll_world(0, -wheel_scroll);
+ break;
- case GDK_SCROLL_LEFT:
- desktop->scroll_world(wheel_scroll, 0);
- break;
+ case GDK_SCROLL_LEFT:
+ desktop->scroll_world(wheel_scroll, 0);
+ break;
- case GDK_SCROLL_RIGHT:
- desktop->scroll_world(-wheel_scroll, 0);
- break;
+ case GDK_SCROLL_RIGHT:
+ desktop->scroll_world(-wheel_scroll, 0);
+ break;
- case GDK_SCROLL_SMOOTH:
- gdk_event_get_scroll_deltas(event, &delta_x, &delta_y);
- desktop->scroll_world(delta_x, delta_y);
- break;
+ case GDK_SCROLL_SMOOTH:
+ gdk_event_get_scroll_deltas(event, &delta_x, &delta_y);
+ desktop->scroll_world(delta_x, delta_y);
+ break;
+ }
}
+ break;
}
- break;
- }
- default:
- break;
+ default:
+ break;
}
-
return ret;
}
diff --git a/src/ui/tools/tool-base.h b/src/ui/tools/tool-base.h
index 58eb6f88e..3d22fc66f 100644
--- a/src/ui/tools/tool-base.h
+++ b/src/ui/tools/tool-base.h
@@ -176,6 +176,7 @@ public:
ShapeEditor* shape_editor;
bool space_panning;
+ bool rotating_mode;
DelayedSnapEvent *_delayed_snap_event;
bool _dse_callback_in_process;
diff --git a/src/ui/widget/selected-style.cpp b/src/ui/widget/selected-style.cpp
index fa7a83732..0370d55db 100644
--- a/src/ui/widget/selected-style.cpp
+++ b/src/ui/widget/selected-style.cpp
@@ -781,62 +781,7 @@ void SelectedStyle::on_stroke_paste() {
}
void SelectedStyle::on_fillstroke_swap() {
- SPCSSAttr *css = sp_repr_css_attr_new ();
-
- switch (_mode[SS_FILL]) {
- case SS_NA:
- case SS_MANY:
- break;
- case SS_NONE:
- sp_repr_css_set_property (css, "stroke", "none");
- break;
- case SS_UNSET:
- sp_repr_css_unset_property (css, "stroke");
- break;
- case SS_COLOR:
- gchar c[64];
- sp_svg_write_color (c, sizeof(c), _thisselected[SS_FILL]);
- sp_repr_css_set_property (css, "stroke", c);
- break;
- case SS_LGRADIENT:
- case SS_RGRADIENT:
-#ifdef WITH_MESH
- case SS_MGRADIENT:
-#endif
- case SS_PATTERN:
- sp_repr_css_set_property (css, "stroke", _paintserver_id[SS_FILL].c_str());
- break;
- }
-
- switch (_mode[SS_STROKE]) {
- case SS_NA:
- case SS_MANY:
- break;
- case SS_NONE:
- sp_repr_css_set_property (css, "fill", "none");
- break;
- case SS_UNSET:
- sp_repr_css_unset_property (css, "fill");
- break;
- case SS_COLOR:
- gchar c[64];
- sp_svg_write_color (c, sizeof(c), _thisselected[SS_STROKE]);
- sp_repr_css_set_property (css, "fill", c);
- break;
- case SS_LGRADIENT:
- case SS_RGRADIENT:
-#ifdef WITH_MESH
- case SS_MGRADIENT:
-#endif
- case SS_PATTERN:
- sp_repr_css_set_property (css, "fill", _paintserver_id[SS_STROKE].c_str());
- break;
- }
-
- sp_desktop_set_style (_desktop, css);
- sp_repr_css_attr_unref (css);
- DocumentUndo::done(_desktop->getDocument(), SP_VERB_DIALOG_FILL_STROKE,
- _("Swap fill and stroke"));
+ _desktop->getSelection()->swapFillStroke();
}
void SelectedStyle::on_fill_edit() {