summaryrefslogtreecommitdiffstats
path: root/src/ui
diff options
context:
space:
mode:
authorTed Gould <ted@gould.cx>2010-03-26 04:34:25 +0000
committerTed Gould <ted@gould.cx>2010-03-26 04:34:25 +0000
commit9e023a3aa964a0d3fa1e31e46d33657367ba68aa (patch)
tree33f1392a340737e4eeefca6fd031f96c29befd2b /src/ui
parentInstalling the pkgconfig file (diff)
parentAdding in shape-record.h (diff)
downloadinkscape-9e023a3aa964a0d3fa1e31e46d33657367ba68aa.tar.gz
inkscape-9e023a3aa964a0d3fa1e31e46d33657367ba68aa.zip
Merge from trunk
(bzr r8254.1.53)
Diffstat (limited to 'src/ui')
-rw-r--r--src/ui/Makefile_insert4
-rw-r--r--src/ui/clipboard.cpp406
-rw-r--r--src/ui/clipboard.h20
-rw-r--r--src/ui/dialog/CMakeLists.txt1
-rw-r--r--src/ui/dialog/Makefile_insert4
-rw-r--r--src/ui/dialog/aboutbox.cpp4
-rw-r--r--src/ui/dialog/align-and-distribute.cpp15
-rw-r--r--src/ui/dialog/color-item.cpp837
-rw-r--r--src/ui/dialog/color-item.h128
-rw-r--r--src/ui/dialog/dialog-manager.cpp3
-rw-r--r--src/ui/dialog/document-properties.cpp2
-rw-r--r--src/ui/dialog/filedialogimpl-gtkmm.cpp2
-rw-r--r--src/ui/dialog/filedialogimpl-win32.cpp12
-rw-r--r--src/ui/dialog/filter-effects-dialog.cpp4
-rw-r--r--src/ui/dialog/icon-preview.cpp6
-rw-r--r--src/ui/dialog/inkscape-preferences.cpp57
-rw-r--r--src/ui/dialog/inkscape-preferences.h11
-rw-r--r--src/ui/dialog/layers.cpp4
-rw-r--r--src/ui/dialog/spray-option.cpp397
-rw-r--r--src/ui/dialog/spray-option.h145
-rw-r--r--src/ui/dialog/svg-fonts-dialog.cpp4
-rw-r--r--src/ui/dialog/swatches.cpp1168
-rw-r--r--src/ui/dialog/swatches.h18
-rw-r--r--src/ui/icon-names.h2
-rw-r--r--src/ui/tool/Makefile_insert32
-rw-r--r--src/ui/tool/commit-events.h51
-rw-r--r--src/ui/tool/control-point-selection.cpp613
-rw-r--r--src/ui/tool/control-point-selection.h161
-rw-r--r--src/ui/tool/control-point.cpp581
-rw-r--r--src/ui/tool/control-point.h202
-rw-r--r--src/ui/tool/curve-drag-point.cpp196
-rw-r--r--src/ui/tool/curve-drag-point.h62
-rw-r--r--src/ui/tool/event-utils.cpp156
-rw-r--r--src/ui/tool/event-utils.h132
-rw-r--r--src/ui/tool/manipulator.cpp89
-rw-r--r--src/ui/tool/manipulator.h165
-rw-r--r--src/ui/tool/modifier-tracker.cpp93
-rw-r--r--src/ui/tool/modifier-tracker.h54
-rw-r--r--src/ui/tool/multi-path-manipulator.cpp717
-rw-r--r--src/ui/tool/multi-path-manipulator.h138
-rw-r--r--src/ui/tool/node-tool.cpp651
-rw-r--r--src/ui/tool/node-tool.h88
-rw-r--r--src/ui/tool/node-types.h48
-rw-r--r--src/ui/tool/node.cpp1407
-rw-r--r--src/ui/tool/node.h411
-rw-r--r--src/ui/tool/path-manipulator.cpp1462
-rw-r--r--src/ui/tool/path-manipulator.h165
-rw-r--r--src/ui/tool/selectable-control-point.cpp139
-rw-r--r--src/ui/tool/selectable-control-point.h72
-rw-r--r--src/ui/tool/selector.cpp133
-rw-r--r--src/ui/tool/selector.h59
-rw-r--r--src/ui/tool/shape-record.h61
-rw-r--r--src/ui/tool/transform-handle-set.cpp657
-rw-r--r--src/ui/tool/transform-handle-set.h96
-rw-r--r--src/ui/uxmanager.cpp174
-rw-r--r--src/ui/uxmanager.h65
-rw-r--r--src/ui/view/edit-widget.cpp6
-rw-r--r--src/ui/view/edit-widget.h1
-rw-r--r--src/ui/widget/color-picker.cpp11
-rw-r--r--src/ui/widget/labelled.h2
-rw-r--r--src/ui/widget/page-sizer.cpp69
-rw-r--r--src/ui/widget/page-sizer.h26
-rw-r--r--src/ui/widget/point.cpp2
-rw-r--r--src/ui/widget/point.h2
-rw-r--r--src/ui/widget/registered-widget.cpp90
-rw-r--r--src/ui/widget/registered-widget.h25
-rw-r--r--src/ui/widget/selected-style.cpp14
67 files changed, 10935 insertions, 1697 deletions
diff --git a/src/ui/Makefile_insert b/src/ui/Makefile_insert
index 3eb6c6b13..eb8966d11 100644
--- a/src/ui/Makefile_insert
+++ b/src/ui/Makefile_insert
@@ -9,4 +9,6 @@ ink_common_sources += \
ui/previewable.h \
ui/previewfillable.h \
ui/previewholder.cpp \
- ui/previewholder.h
+ ui/previewholder.h \
+ ui/uxmanager.cpp \
+ ui/uxmanager.h
diff --git a/src/ui/clipboard.cpp b/src/ui/clipboard.cpp
index af0ec0129..dd1f981b5 100644
--- a/src/ui/clipboard.cpp
+++ b/src/ui/clipboard.cpp
@@ -3,9 +3,11 @@
*/
/* Authors:
* Krzysztof Kosiński <tweenk@o2.pl>
+ * Jon A. Cruz <jon@joncruz.org>
* Incorporates some code from selection-chemistry.cpp, see that file for more credits.
*
* Copyright (C) 2008 authors
+ * Copyright (C) 2010 Jon A. Cruz
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@@ -69,7 +71,6 @@
#include "svg/svg.h" // for sp_svg_transform_write, used in _copySelection
#include "svg/css-ostringstream.h" // used in _parseColor
#include "file.h" // for file_import, used in _pasteImage
-#include "preferences.h" // for used in _pasteImage
#include "text-context.h"
#include "text-editing.h"
#include "tools-switch.h"
@@ -105,14 +106,14 @@ namespace UI {
*/
class ClipboardManagerImpl : public ClipboardManager {
public:
- virtual void copy();
+ virtual void copy(SPDesktop *desktop);
virtual void copyPathParameter(Inkscape::LivePathEffect::PathParam *);
- virtual bool paste(bool in_place);
- virtual bool pasteStyle();
- virtual bool pasteSize(bool, bool, bool);
- virtual bool pastePathEffect();
- virtual Glib::ustring getPathParameter();
- virtual Glib::ustring getShapeOrTextObjectId();
+ virtual bool paste(SPDesktop *desktop, bool in_place);
+ virtual bool pasteStyle(SPDesktop *desktop);
+ virtual bool pasteSize(SPDesktop *desktop, bool separately, bool apply_x, bool apply_y);
+ virtual bool pastePathEffect(SPDesktop *desktop);
+ virtual Glib::ustring getPathParameter(SPDesktop* desktop);
+ virtual Glib::ustring getShapeOrTextObjectId(SPDesktop *desktop);
virtual const gchar *getFirstObjectID();
ClipboardManagerImpl();
@@ -126,10 +127,10 @@ private:
void _copyTextPath(SPTextPath *);
Inkscape::XML::Node *_copyNode(Inkscape::XML::Node *, Inkscape::XML::Document *, Inkscape::XML::Node *);
- void _pasteDocument(SPDocument *, bool in_place);
- void _pasteDefs(SPDocument *);
- bool _pasteImage();
- bool _pasteText();
+ void _pasteDocument(SPDesktop *desktop, SPDocument *clipdoc, bool in_place);
+ void _pasteDefs(SPDesktop *desktop, SPDocument *clipdoc);
+ bool _pasteImage(SPDocument *doc);
+ bool _pasteText(SPDesktop *desktop);
SPCSSAttr *_parseColor(const Glib::ustring &);
void _applyPathEffect(SPItem *, gchar const *);
SPDocument *_retrieveClipboard(Glib::ustring = "");
@@ -142,7 +143,7 @@ private:
void _createInternalClipboard();
void _discardInternalClipboard();
Inkscape::XML::Node *_createClipNode();
- Geom::Scale _getScale(Geom::Point const &, Geom::Point const &, Geom::Rect const &, bool, bool);
+ Geom::Scale _getScale(SPDesktop *desktop, Geom::Point const &min, Geom::Point const &max, Geom::Rect const &obj_rect, bool apply_x, bool apply_y);
Glib::ustring _getBestTarget();
void _setClipboardTargets();
void _setClipboardColor(guint32);
@@ -193,10 +194,11 @@ ClipboardManagerImpl::~ClipboardManagerImpl() {}
/**
* @brief Copy selection contents to the clipboard
*/
-void ClipboardManagerImpl::copy()
+void ClipboardManagerImpl::copy(SPDesktop *desktop)
{
- SPDesktop *desktop = SP_ACTIVE_DESKTOP;
- if ( desktop == NULL ) return;
+ if ( desktop == NULL ) {
+ return;
+ }
Inkscape::Selection *selection = sp_desktop_selection(desktop);
// Special case for when the gradient dragger is active - copies gradient color
@@ -220,7 +222,9 @@ void ClipboardManagerImpl::copy()
g_snprintf(color_str, 16, "#%06x", col >> 8);
sp_repr_css_set_property(_text_style, "fill", color_str);
float opacity = SP_RGBA32_A_F(col);
- if (opacity > 1.0) opacity = 1.0; // safeguard
+ if (opacity > 1.0) {
+ opacity = 1.0; // safeguard
+ }
Inkscape::CSSOStringStream opcss;
opcss << opacity;
sp_repr_css_set_property(_text_style, "opacity", opcss.str().data());
@@ -273,9 +277,13 @@ void ClipboardManagerImpl::copy()
*/
void ClipboardManagerImpl::copyPathParameter(Inkscape::LivePathEffect::PathParam *pp)
{
- if ( pp == NULL ) return;
+ if ( pp == NULL ) {
+ return;
+ }
gchar *svgd = sp_svg_write_path( pp->get_pathvector() );
- if ( svgd == NULL || *svgd == '\0' ) return;
+ if ( svgd == NULL || *svgd == '\0' ) {
+ return;
+ }
_discardInternalClipboard();
_createInternalClipboard();
@@ -294,12 +302,15 @@ void ClipboardManagerImpl::copyPathParameter(Inkscape::LivePathEffect::PathParam
* @brief Paste from the system clipboard into the active desktop
* @param in_place Whether to put the contents where they were when copied
*/
-bool ClipboardManagerImpl::paste(bool in_place)
+bool ClipboardManagerImpl::paste(SPDesktop *desktop, bool in_place)
{
// do any checking whether we really are able to paste before requesting the contents
- SPDesktop *desktop = SP_ACTIVE_DESKTOP;
- if ( desktop == NULL ) return false;
- if ( Inkscape::have_viable_layer(desktop, desktop->messageStack()) == false ) return false;
+ if ( desktop == NULL ) {
+ return false;
+ }
+ if ( Inkscape::have_viable_layer(desktop, desktop->messageStack()) == false ) {
+ return false;
+ }
Glib::ustring target = _getBestTarget();
@@ -308,9 +319,13 @@ bool ClipboardManagerImpl::paste(bool in_place)
// TODO: Handle x-special/gnome-copied-files and text/uri-list to support pasting files
// if there is an image on the clipboard, paste it
- if ( target == CLIPBOARD_GDK_PIXBUF_TARGET ) return _pasteImage();
+ if ( target == CLIPBOARD_GDK_PIXBUF_TARGET ) {
+ return _pasteImage(desktop->doc());
+ }
// if there's only text, paste it into a selected text object or create a new one
- if ( target == CLIPBOARD_TEXT_TARGET ) return _pasteText();
+ if ( target == CLIPBOARD_TEXT_TARGET ) {
+ return _pasteText(desktop);
+ }
// otherwise, use the import extensions
SPDocument *tempdoc = _retrieveClipboard(target);
@@ -319,7 +334,7 @@ bool ClipboardManagerImpl::paste(bool in_place)
return false;
}
- _pasteDocument(tempdoc, in_place);
+ _pasteDocument(desktop, tempdoc, in_place);
sp_document_unref(tempdoc);
return true;
@@ -338,8 +353,9 @@ const gchar *ClipboardManagerImpl::getFirstObjectID()
Inkscape::XML::Node
*root = sp_document_repr_root(tempdoc);
- if (!root)
+ if (!root) {
return NULL;
+ }
Inkscape::XML::Node *ch = sp_repr_children(root);
while (ch != NULL &&
@@ -349,8 +365,9 @@ const gchar *ClipboardManagerImpl::getFirstObjectID()
strcmp(ch->name(), "svg:text") &&
strcmp(ch->name(), "svg:image") &&
strcmp(ch->name(), "svg:rect")
- )
+ ) {
ch = ch->next();
+ }
if (ch) {
return ch->attribute("id");
@@ -363,10 +380,11 @@ const gchar *ClipboardManagerImpl::getFirstObjectID()
/**
* @brief Implements the Paste Style action
*/
-bool ClipboardManagerImpl::pasteStyle()
+bool ClipboardManagerImpl::pasteStyle(SPDesktop *desktop)
{
- SPDesktop *desktop = SP_ACTIVE_DESKTOP;
- if (desktop == NULL) return false;
+ if (desktop == NULL) {
+ return false;
+ }
// check whether something is selected
Inkscape::Selection *selection = sp_desktop_selection(desktop);
@@ -394,7 +412,7 @@ bool ClipboardManagerImpl::pasteStyle()
bool pasted = false;
if (clipnode) {
- _pasteDefs(tempdoc);
+ _pasteDefs(desktop, tempdoc);
SPCSSAttr *style = sp_repr_css_attr(clipnode, "style");
sp_desktop_set_style(desktop, style);
pasted = true;
@@ -414,12 +432,15 @@ bool ClipboardManagerImpl::pasteStyle()
* @param apply_x Whether to scale the width of objects / selection
* @param apply_y Whether to scale the height of objects / selection
*/
-bool ClipboardManagerImpl::pasteSize(bool separately, bool apply_x, bool apply_y)
+bool ClipboardManagerImpl::pasteSize(SPDesktop *desktop, bool separately, bool apply_x, bool apply_y)
{
- if(!apply_x && !apply_y) return false; // pointless parameters
+ if (!apply_x && !apply_y) {
+ return false; // pointless parameters
+ }
- SPDesktop *desktop = SP_ACTIVE_DESKTOP;
- if ( desktop == NULL ) return false;
+ if ( desktop == NULL ) {
+ return false;
+ }
Inkscape::Selection *selection = sp_desktop_selection(desktop);
if (selection->isEmpty()) {
_userWarn(desktop, _("Select <b>object(s)</b> to paste size to."));
@@ -447,8 +468,10 @@ bool ClipboardManagerImpl::pasteSize(bool separately, bool apply_x, bool apply_y
for (GSList *i = const_cast<GSList*>(selection->itemList()) ; i ; i = i->next) {
SPItem *item = SP_ITEM(i->data);
Geom::OptRect obj_size = sp_item_bbox_desktop(item);
- if ( !obj_size ) continue;
- sp_item_scale_rel(item, _getScale(min, max, *obj_size, apply_x, apply_y));
+ if ( !obj_size ) {
+ continue;
+ }
+ sp_item_scale_rel(item, _getScale(desktop, min, max, *obj_size, apply_x, apply_y));
}
}
// resize the selection as a whole
@@ -456,7 +479,7 @@ bool ClipboardManagerImpl::pasteSize(bool separately, bool apply_x, bool apply_y
Geom::OptRect sel_size = selection->bounds();
if ( sel_size ) {
sp_selection_scale_relative(selection, sel_size->midpoint(),
- _getScale(min, max, *sel_size, apply_x, apply_y));
+ _getScale(desktop, min, max, *sel_size, apply_x, apply_y));
}
}
pasted = true;
@@ -469,14 +492,14 @@ bool ClipboardManagerImpl::pasteSize(bool separately, bool apply_x, bool apply_y
/**
* @brief Applies a path effect from the clipboard to the selected path
*/
-bool ClipboardManagerImpl::pastePathEffect()
+bool ClipboardManagerImpl::pastePathEffect(SPDesktop *desktop)
{
/** @todo FIXME: pastePathEffect crashes when moving the path with the applied effect,
segfaulting in fork_private_if_necessary(). */
- SPDesktop *desktop = SP_ACTIVE_DESKTOP;
- if ( desktop == NULL )
+ if ( desktop == NULL ) {
return false;
+ }
Inkscape::Selection *selection = sp_desktop_selection(desktop);
if (selection && selection->isEmpty()) {
@@ -489,13 +512,14 @@ bool ClipboardManagerImpl::pastePathEffect()
Inkscape::XML::Node *root = sp_document_repr_root(tempdoc);
Inkscape::XML::Node *clipnode = sp_repr_lookup_name(root, "inkscape:clipboard", 1);
if ( clipnode ) {
- gchar const *effect = clipnode->attribute("inkscape:path-effect");
- if ( effect ) {
- _pasteDefs(tempdoc);
+ gchar const *effectstack = clipnode->attribute("inkscape:path-effect");
+ if ( effectstack ) {
+ _pasteDefs(desktop, tempdoc);
// make sure all selected items are converted to paths first (i.e. rectangles)
- sp_selected_path_to_curves(desktop, false);
- for (GSList *item = const_cast<GSList *>(selection->itemList()) ; item ; item = item->next) {
- _applyPathEffect(reinterpret_cast<SPItem*>(item->data), effect);
+ sp_selected_to_lpeitems(desktop);
+ for (GSList *itemptr = const_cast<GSList *>(selection->itemList()) ; itemptr ; itemptr = itemptr->next) {
+ SPItem *item = reinterpret_cast<SPItem*>(itemptr->data);
+ _applyPathEffect(item, effectstack);
}
return true;
@@ -513,18 +537,18 @@ bool ClipboardManagerImpl::pastePathEffect()
* @brief Get LPE path data from the clipboard
* @return The retrieved path data (contents of the d attribute), or "" if no path was found
*/
-Glib::ustring ClipboardManagerImpl::getPathParameter()
+Glib::ustring ClipboardManagerImpl::getPathParameter(SPDesktop* desktop)
{
SPDocument *tempdoc = _retrieveClipboard(); // any target will do here
if ( tempdoc == NULL ) {
- _userWarn(SP_ACTIVE_DESKTOP, _("Nothing on the clipboard."));
+ _userWarn(desktop, _("Nothing on the clipboard."));
return "";
}
Inkscape::XML::Node
*root = sp_document_repr_root(tempdoc),
*path = sp_repr_lookup_name(root, "svg:path", -1); // unlimited search depth
if ( path == NULL ) {
- _userWarn(SP_ACTIVE_DESKTOP, _("Clipboard does not contain a path."));
+ _userWarn(desktop, _("Clipboard does not contain a path."));
sp_document_unref(tempdoc);
return "";
}
@@ -537,21 +561,22 @@ Glib::ustring ClipboardManagerImpl::getPathParameter()
* @brief Get object id of a shape or text item from the clipboard
* @return The retrieved id string (contents of the id attribute), or "" if no shape or text item was found
*/
-Glib::ustring ClipboardManagerImpl::getShapeOrTextObjectId()
+Glib::ustring ClipboardManagerImpl::getShapeOrTextObjectId(SPDesktop *desktop)
{
SPDocument *tempdoc = _retrieveClipboard(); // any target will do here
if ( tempdoc == NULL ) {
- _userWarn(SP_ACTIVE_DESKTOP, _("Nothing on the clipboard."));
+ _userWarn(desktop, _("Nothing on the clipboard."));
return "";
}
Inkscape::XML::Node *root = sp_document_repr_root(tempdoc);
Inkscape::XML::Node *repr = sp_repr_lookup_name(root, "svg:path", -1); // unlimited search depth
- if ( repr == NULL )
+ if ( repr == NULL ) {
repr = sp_repr_lookup_name(root, "svg:text", -1);
+ }
if ( repr == NULL ) {
- _userWarn(SP_ACTIVE_DESKTOP, _("Clipboard does not contain a path."));
+ _userWarn(desktop, _("Clipboard does not contain a path."));
sp_document_unref(tempdoc);
return "";
}
@@ -576,7 +601,9 @@ void ClipboardManagerImpl::_copySelection(Inkscape::Selection *selection)
sorted_items = g_slist_sort(sorted_items, (GCompareFunc) sp_object_compare_position);
for (GSList *i = sorted_items ; i ; i = i->next) {
- if (!SP_IS_ITEM(i->data)) continue;
+ if (!SP_IS_ITEM(i->data)) {
+ continue;
+ }
Inkscape::XML::Node *obj = SP_OBJECT_REPR(i->data);
Inkscape::XML::Node *obj_copy = _copyNode(obj, _doc, _root);
@@ -595,7 +622,7 @@ void ClipboardManagerImpl::_copySelection(Inkscape::Selection *selection)
// copy style for Paste Style action
if (sorted_items) {
- if(SP_IS_ITEM(sorted_items->data)) {
+ if (SP_IS_ITEM(sorted_items->data)) {
SPCSSAttr *style = take_style_from_item((SPItem *) sorted_items->data);
sp_repr_css_set(_clipnode, style, "style");
sp_repr_css_attr_unref(style);
@@ -630,17 +657,21 @@ void ClipboardManagerImpl::_copyUsedDefs(SPItem *item)
if (style && (style->fill.isPaintserver())) {
SPObject *server = SP_OBJECT_STYLE_FILL_SERVER(item);
- if (SP_IS_LINEARGRADIENT(server) || SP_IS_RADIALGRADIENT(server))
+ if (SP_IS_LINEARGRADIENT(server) || SP_IS_RADIALGRADIENT(server)) {
_copyGradient(SP_GRADIENT(server));
- if (SP_IS_PATTERN(server))
+ }
+ if (SP_IS_PATTERN(server)) {
_copyPattern(SP_PATTERN(server));
+ }
}
if (style && (style->stroke.isPaintserver())) {
SPObject *server = SP_OBJECT_STYLE_STROKE_SERVER(item);
- if (SP_IS_LINEARGRADIENT(server) || SP_IS_RADIALGRADIENT(server))
+ if (SP_IS_LINEARGRADIENT(server) || SP_IS_RADIALGRADIENT(server)) {
_copyGradient(SP_GRADIENT(server));
- if (SP_IS_PATTERN(server))
+ }
+ if (SP_IS_PATTERN(server)) {
_copyPattern(SP_PATTERN(server));
+ }
}
// For shapes, copy all of the shape's markers
@@ -659,8 +690,9 @@ void ClipboardManagerImpl::_copyUsedDefs(SPItem *item)
for (PathEffectList::iterator it = lpeitem->path_effect_list->begin(); it != lpeitem->path_effect_list->end(); ++it)
{
LivePathEffectObject *lpeobj = (*it)->lpeobject;
- if (lpeobj)
+ if (lpeobj) {
_copyNode(SP_OBJECT_REPR(SP_OBJECT(lpeobj)), _doc, _defs);
+ }
}
}
}
@@ -682,8 +714,9 @@ void ClipboardManagerImpl::_copyUsedDefs(SPItem *item)
_copyNode(SP_OBJECT_REPR(mask), _doc, _defs);
// recurse into the mask for its gradients etc.
for (SPObject *o = SP_OBJECT(mask)->children ; o != NULL ; o = o->next) {
- if (SP_IS_ITEM(o))
+ if (SP_IS_ITEM(o)) {
_copyUsedDefs(SP_ITEM(o));
+ }
}
}
// Copy filters
@@ -696,8 +729,9 @@ void ClipboardManagerImpl::_copyUsedDefs(SPItem *item)
// recurse
for (SPObject *o = SP_OBJECT(item)->children ; o != NULL ; o = o->next) {
- if (SP_IS_ITEM(o))
+ if (SP_IS_ITEM(o)) {
_copyUsedDefs(SP_ITEM(o));
+ }
}
}
@@ -726,7 +760,9 @@ void ClipboardManagerImpl::_copyPattern(SPPattern *pattern)
// items in the pattern may also use gradients and other patterns, so recurse
for (SPObject *child = sp_object_first_child(SP_OBJECT(pattern)) ; child != NULL ; child = SP_OBJECT_NEXT(child) ) {
- if (!SP_IS_ITEM (child)) continue;
+ if (!SP_IS_ITEM (child)) {
+ continue;
+ }
_copyUsedDefs(SP_ITEM(child));
}
pattern = pattern->ref->getObject();
@@ -740,11 +776,15 @@ void ClipboardManagerImpl::_copyPattern(SPPattern *pattern)
void ClipboardManagerImpl::_copyTextPath(SPTextPath *tp)
{
SPItem *path = sp_textpath_get_path_item(tp);
- if(!path) return;
+ if (!path) {
+ return;
+ }
Inkscape::XML::Node *path_node = SP_OBJECT_REPR(path);
// Do not copy the text path to defs if it's already copied
- if(sp_repr_lookup_child(_root, "id", path_node->attribute("id"))) return;
+ if (sp_repr_lookup_child(_root, "id", path_node->attribute("id"))) {
+ return;
+ }
_copyNode(path_node, _doc, _defs);
}
@@ -771,9 +811,8 @@ Inkscape::XML::Node *ClipboardManagerImpl::_copyNode(Inkscape::XML::Node *node,
* @param in_place Whether to paste the selection where it was when copied
* @pre @c clipdoc is not empty and items can be added to the current layer
*/
-void ClipboardManagerImpl::_pasteDocument(SPDocument *clipdoc, bool in_place)
+void ClipboardManagerImpl::_pasteDocument(SPDesktop *desktop, SPDocument *clipdoc, bool in_place)
{
- SPDesktop *desktop = SP_ACTIVE_DESKTOP;
SPDocument *target_document = sp_desktop_document(desktop);
Inkscape::XML::Node
*root = sp_document_repr_root(clipdoc),
@@ -781,16 +820,24 @@ void ClipboardManagerImpl::_pasteDocument(SPDocument *clipdoc, bool in_place)
Inkscape::XML::Document *target_xmldoc = sp_document_repr_doc(target_document);
// copy definitions
- _pasteDefs(clipdoc);
+ _pasteDefs(desktop, clipdoc);
// copy objects
GSList *pasted_objects = NULL;
for (Inkscape::XML::Node *obj = root->firstChild() ; obj ; obj = obj->next()) {
// Don't copy metadata, defs, named views and internal clipboard contents to the document
- if (!strcmp(obj->name(), "svg:defs")) continue;
- if (!strcmp(obj->name(), "svg:metadata")) continue;
- if (!strcmp(obj->name(), "sodipodi:namedview")) continue;
- if (!strcmp(obj->name(), "inkscape:clipboard")) continue;
+ if (!strcmp(obj->name(), "svg:defs")) {
+ continue;
+ }
+ if (!strcmp(obj->name(), "svg:metadata")) {
+ continue;
+ }
+ if (!strcmp(obj->name(), "sodipodi:namedview")) {
+ continue;
+ }
+ if (!strcmp(obj->name(), "inkscape:clipboard")) {
+ continue;
+ }
Inkscape::XML::Node *obj_copy = _copyNode(obj, target_xmldoc, target_parent);
pasted_objects = g_slist_prepend(pasted_objects, (gpointer) obj_copy);
}
@@ -822,11 +869,12 @@ void ClipboardManagerImpl::_pasteDocument(SPDocument *clipdoc, bool in_place)
if (!in_place) {
SnapManager &m = desktop->namedview->snap_manager;
- m.setup(desktop, false); // Don't display the snapindicator
+ m.setup(desktop);
+ sp_event_context_discard_delayed_snap_event(desktop->event_context);
// get offset from mouse pointer to bbox center, snap to grid if enabled
Geom::Point mouse_offset = desktop->point() - sel_bbox->midpoint();
- offset = m.multipleOfGridPitch(mouse_offset - offset) + offset;
+ offset = m.multipleOfGridPitch(mouse_offset - offset, sel_bbox->midpoint() + offset) + offset;
}
sp_selection_move_relative(selection, offset);
@@ -841,10 +889,9 @@ void ClipboardManagerImpl::_pasteDocument(SPDocument *clipdoc, bool in_place)
* @param clipdoc The document to paste
* @pre @c clipdoc != NULL and pasting into the active document is possible
*/
-void ClipboardManagerImpl::_pasteDefs(SPDocument *clipdoc)
+void ClipboardManagerImpl::_pasteDefs(SPDesktop *desktop, SPDocument *clipdoc)
{
// boilerplate vars copied from _pasteDocument
- SPDesktop *desktop = SP_ACTIVE_DESKTOP;
SPDocument *target_document = sp_desktop_document(desktop);
Inkscape::XML::Node
*root = sp_document_repr_root(clipdoc),
@@ -863,35 +910,38 @@ void ClipboardManagerImpl::_pasteDefs(SPDocument *clipdoc)
/**
* @brief Retrieve a bitmap image from the clipboard and paste it into the active document
*/
-bool ClipboardManagerImpl::_pasteImage()
+bool ClipboardManagerImpl::_pasteImage(SPDocument *doc)
{
- SPDocument *doc = SP_ACTIVE_DOCUMENT;
- if ( doc == NULL ) return false;
+ if ( doc == NULL ) {
+ return false;
+ }
// retrieve image data
Glib::RefPtr<Gdk::Pixbuf> img = _clipboard->wait_for_image();
- if (!img) return false;
-
- // Very stupid hack: Write into a file, then import the file into the document.
- // To avoid using tmpfile and POSIX file handles, make the filename based on current time.
- // This wasn't my idea, I just copied this from selection-chemistry.cpp
- // and just can't think of something saner at the moment. Pasting more than
- // one image per second will overwrite the image.
- // However, I don't think anyone is able to copy a _different_ image into inkscape
- // in 1 second.
- time_t rawtime;
- char image_filename[128];
-
- time(&rawtime);
- strftime(image_filename, 128, "inkscape_pasted_image_%Y%m%d_%H%M%S.png", localtime( &rawtime ));
- /// @todo Check whether the encoding is correct here
- Inkscape::Preferences *prefs = Inkscape::Preferences::get();
- std::string save_folder = Glib::filename_from_utf8(prefs->getString("/dialogs/save_as/path"));
-
- gchar *image_path = g_build_filename(save_folder.data(), image_filename, NULL);
- img->save(image_path, "png");
- file_import(doc, image_path, NULL);
- g_free(image_path);
+ if (!img) {
+ return false;
+ }
+
+ // TODO unify with interface.cpp's sp_ui_drag_data_received()
+ // AARGH stupid
+ Inkscape::Extension::DB::InputList o;
+ Inkscape::Extension::db.get_input_list(o);
+ Inkscape::Extension::DB::InputList::const_iterator i = o.begin();
+ while (i != o.end() && strcmp( (*i)->get_mimetype(), "image/png" ) != 0) {
+ ++i;
+ }
+ Inkscape::Extension::Extension *png = *i;
+ bool save = (strcmp(png->get_param_optiongroup("link"), "embed") == 0);
+ png->set_param_optiongroup("link", "embed");
+ png->set_gui(false);
+
+ gchar *filename = g_build_filename( g_get_tmp_dir(), "inkscape-clipboard-import", NULL );
+ img->save(filename, "png");
+ file_import(doc, filename, png);
+ g_free(filename);
+
+ png->set_param_optiongroup("link", save ? "embed" : "link");
+ png->set_gui(true);
return true;
}
@@ -899,14 +949,16 @@ bool ClipboardManagerImpl::_pasteImage()
/**
* @brief Paste text into the selected text object or create a new one to hold it
*/
-bool ClipboardManagerImpl::_pasteText()
+bool ClipboardManagerImpl::_pasteText(SPDesktop *desktop)
{
- SPDesktop *desktop = SP_ACTIVE_DESKTOP;
- if ( desktop == NULL ) return false;
+ if ( desktop == NULL ) {
+ return false;
+ }
// if the text editing tool is active, paste the text into the active text object
- if (tools_isactive(desktop, TOOLS_TEXT))
+ if (tools_isactive(desktop, TOOLS_TEXT)) {
return sp_text_paste_inline(desktop->event_context);
+ }
// try to parse the text as a color and, if successful, apply it as the current style
SPCSSAttr *css = _parseColor(_clipboard->wait_for_text());
@@ -930,29 +982,43 @@ SPCSSAttr *ClipboardManagerImpl::_parseColor(const Glib::ustring &text)
Glib::ustring::size_type len = text.bytes();
char *str = const_cast<char *>(text.data());
bool attempt_alpha = false;
- if ( !str || ( *str == '\0' ) ) return NULL; // this is OK due to boolean short-circuit
+ if ( !str || ( *str == '\0' ) ) {
+ return NULL; // this is OK due to boolean short-circuit
+ }
// those conditionals guard against parsing e.g. the string "fab" as "fab000"
// (incomplete color) and "45fab71" as "45fab710" (incomplete alpha)
if ( *str == '#' ) {
- if ( len < 7 ) return NULL;
- if ( len >= 9 ) attempt_alpha = true;
+ if ( len < 7 ) {
+ return NULL;
+ }
+ if ( len >= 9 ) {
+ attempt_alpha = true;
+ }
} else {
- if ( len < 6 ) return NULL;
- if ( len >= 8 ) attempt_alpha = true;
+ if ( len < 6 ) {
+ return NULL;
+ }
+ if ( len >= 8 ) {
+ attempt_alpha = true;
+ }
}
unsigned int color = 0, alpha = 0xff;
// skip a leading #, if present
- if ( *str == '#' ) ++str;
+ if ( *str == '#' ) {
+ ++str;
+ }
// try to parse first 6 digits
int res = sscanf(str, "%6x", &color);
if ( res && ( res != EOF ) ) {
if (attempt_alpha) {// try to parse alpha if there's enough characters
sscanf(str + 6, "%2x", &alpha);
- if ( !res || res == EOF ) alpha = 0xff;
+ if ( !res || res == EOF ) {
+ alpha = 0xff;
+ }
}
SPCSSAttr *color_css = sp_repr_css_attr_new();
@@ -963,7 +1029,9 @@ SPCSSAttr *ClipboardManagerImpl::_parseColor(const Glib::ustring &text)
sp_repr_css_set_property(color_css, "fill", color_str);
float opacity = static_cast<float>(alpha)/static_cast<float>(0xff);
- if (opacity > 1.0) opacity = 1.0; // safeguard
+ if (opacity > 1.0) {
+ opacity = 1.0; // safeguard
+ }
Inkscape::CSSOStringStream opcss;
opcss << opacity;
sp_repr_css_set_property(color_css, "fill-opacity", opcss.str().data());
@@ -976,19 +1044,31 @@ SPCSSAttr *ClipboardManagerImpl::_parseColor(const Glib::ustring &text)
/**
* @brief Applies a pasted path effect to a given item
*/
-void ClipboardManagerImpl::_applyPathEffect(SPItem *item, gchar const *effect)
+void ClipboardManagerImpl::_applyPathEffect(SPItem *item, gchar const *effectstack)
{
- if ( item == NULL ) return;
- if ( SP_IS_RECT(item) ) return;
+ if ( item == NULL ) {
+ return;
+ }
+ if ( SP_IS_RECT(item) ) {
+ return;
+ }
if (SP_IS_LPE_ITEM(item))
{
SPLPEItem *lpeitem = SP_LPE_ITEM(item);
- SPObject *obj = sp_uri_reference_resolve(_clipboardSPDoc, effect);
- if (!obj) return;
- // if the effect is not used by anyone, we might as well take it
- LivePathEffectObject *lpeobj = LIVEPATHEFFECT(obj)->fork_private_if_necessary(1);
- sp_lpe_item_add_path_effect(lpeitem, lpeobj);
+ // for each effect in the stack, check if we need to fork it before adding it to the item
+ std::istringstream iss(effectstack);
+ std::string href;
+ while (std::getline(iss, href, ';'))
+ {
+ SPObject *obj = sp_uri_reference_resolve(_clipboardSPDoc, href.c_str());
+ if (!obj) {
+ return;
+ }
+ // if the effectstack is not used by anyone, we might as well take it
+ LivePathEffectObject *lpeobj = LIVEPATHEFFECT(obj)->fork_private_if_necessary(1);
+ sp_lpe_item_add_path_effect(lpeitem, lpeobj);
+ }
}
}
@@ -1000,10 +1080,11 @@ void ClipboardManagerImpl::_applyPathEffect(SPItem *item, gchar const *effect)
SPDocument *ClipboardManagerImpl::_retrieveClipboard(Glib::ustring required_target)
{
Glib::ustring best_target;
- if ( required_target == "" )
+ if ( required_target == "" ) {
best_target = _getBestTarget();
- else
+ } else {
best_target = required_target;
+ }
if ( best_target == "" ) {
return NULL;
@@ -1053,15 +1134,18 @@ SPDocument *ClipboardManagerImpl::_retrieveClipboard(Glib::ustring required_targ
// there is no specific plain SVG input extension, so if we can paste the Inkscape SVG format,
// we use the image/svg+xml mimetype to look up the input extension
- if(target == "image/x-inkscape-svg")
+ if (target == "image/x-inkscape-svg") {
target = "image/svg+xml";
+ }
Inkscape::Extension::DB::InputList inlist;
Inkscape::Extension::db.get_input_list(inlist);
Inkscape::Extension::DB::InputList::const_iterator in = inlist.begin();
- for (; in != inlist.end() && target != (*in)->get_mimetype() ; ++in){};
- if ( in == inlist.end() )
+ for (; in != inlist.end() && target != (*in)->get_mimetype() ; ++in) {
+ };
+ if ( in == inlist.end() ) {
return NULL; // this shouldn't happen unless _getBestTarget returns something bogus
+ }
SPDocument *tempdoc = NULL;
try {
@@ -1086,7 +1170,9 @@ void ClipboardManagerImpl::_onGet(Gtk::SelectionData &sel, guint /*info*/)
g_assert( _clipboardSPDoc != NULL );
Glib::ustring target = sel.get_target();
- if(target == "") return; // this shouldn't happen
+ if (target == "") {
+ return; // this shouldn't happen
+ }
if (target == CLIPBOARD_TEXT_TARGET) {
target = "image/x-inkscape-svg";
@@ -1095,8 +1181,11 @@ void ClipboardManagerImpl::_onGet(Gtk::SelectionData &sel, guint /*info*/)
Inkscape::Extension::DB::OutputList outlist;
Inkscape::Extension::db.get_output_list(outlist);
Inkscape::Extension::DB::OutputList::const_iterator out = outlist.begin();
- for ( ; out != outlist.end() && target != (*out)->get_mimetype() ; ++out){};
- if ( out == outlist.end() && target != "image/png") return; // this also shouldn't happen
+ for ( ; out != outlist.end() && target != (*out)->get_mimetype() ; ++out) {
+ };
+ if ( out == outlist.end() && target != "image/png") {
+ return; // this also shouldn't happen
+ }
// FIXME: Temporary hack until we add support for memory output.
// Save to a temporary file, read it back and then set the clipboard contents
@@ -1117,10 +1206,12 @@ void ClipboardManagerImpl::_onGet(Gtk::SelectionData &sel, guint /*info*/)
// read from namedview
Inkscape::XML::Node *nv = sp_repr_lookup_name (_clipboardSPDoc->rroot, "sodipodi:namedview");
- if (nv && nv->attribute("pagecolor"))
+ if (nv && nv->attribute("pagecolor")) {
bgcolor = sp_svg_read_color(nv->attribute("pagecolor"), 0xffffff00);
- if (nv && nv->attribute("inkscape:pageopacity"))
+ }
+ if (nv && nv->attribute("inkscape:pageopacity")) {
bgcolor |= SP_COLOR_F_TO_U(sp_repr_get_double_attribute (nv, "inkscape:pageopacity", 1.0));
+ }
sp_export_png_file(_clipboardSPDoc, filename, area, width, height, dpi, dpi, bgcolor, NULL, NULL, true, NULL);
}
@@ -1200,9 +1291,8 @@ void ClipboardManagerImpl::_discardInternalClipboard()
/**
* @brief Get the scale to resize an item, based on the command and desktop state
*/
-Geom::Scale ClipboardManagerImpl::_getScale(Geom::Point const &min, Geom::Point const &max, Geom::Rect const &obj_rect, bool apply_x, bool apply_y)
+Geom::Scale ClipboardManagerImpl::_getScale(SPDesktop *desktop, Geom::Point const &min, Geom::Point const &max, Geom::Rect const &obj_rect, bool apply_x, bool apply_y)
{
- SPDesktop *desktop = SP_ACTIVE_DESKTOP;
double scale_x = 1.0;
double scale_y = 1.0;
@@ -1215,8 +1305,12 @@ Geom::Scale ClipboardManagerImpl::_getScale(Geom::Point const &min, Geom::Point
// If the "lock aspect ratio" button is pressed and we paste only a single coordinate,
// resize the second one by the same ratio too
if (desktop->isToolboxButtonActive("lock")) {
- if (apply_x && !apply_y) scale_y = scale_x;
- if (apply_y && !apply_x) scale_x = scale_y;
+ if (apply_x && !apply_y) {
+ scale_y = scale_x;
+ }
+ if (apply_y && !apply_x) {
+ scale_x = scale_y;
+ }
}
return Geom::Scale(scale_x, scale_y);
@@ -1240,36 +1334,43 @@ Glib::ustring ClipboardManagerImpl::_getBestTarget()
g_debug("End clipboard targets\n");
//*/
- for(std::list<Glib::ustring>::iterator i = _preferred_targets.begin() ;
+ for (std::list<Glib::ustring>::iterator i = _preferred_targets.begin() ;
i != _preferred_targets.end() ; ++i)
{
- if ( std::find(targets.begin(), targets.end(), *i) != targets.end() )
+ if ( std::find(targets.begin(), targets.end(), *i) != targets.end() ) {
return *i;
+ }
}
#ifdef WIN32
if (OpenClipboard(NULL))
{ // If both bitmap and metafile are present, pick the one that was exported first.
UINT format = EnumClipboardFormats(0);
while (format) {
- if (format == CF_ENHMETAFILE || format == CF_DIB || format == CF_BITMAP)
+ if (format == CF_ENHMETAFILE || format == CF_DIB || format == CF_BITMAP) {
break;
+ }
format = EnumClipboardFormats(format);
}
CloseClipboard();
- if (format == CF_ENHMETAFILE)
+ if (format == CF_ENHMETAFILE) {
return CLIPBOARD_WIN32_EMF_TARGET;
- if (format == CF_DIB || format == CF_BITMAP)
+ }
+ if (format == CF_DIB || format == CF_BITMAP) {
return CLIPBOARD_GDK_PIXBUF_TARGET;
+ }
}
- if (IsClipboardFormatAvailable(CF_ENHMETAFILE))
+ if (IsClipboardFormatAvailable(CF_ENHMETAFILE)) {
return CLIPBOARD_WIN32_EMF_TARGET;
+ }
#endif
- if (_clipboard->wait_is_image_available())
+ if (_clipboard->wait_is_image_available()) {
return CLIPBOARD_GDK_PIXBUF_TARGET;
- if (_clipboard->wait_is_text_available())
+ }
+ if (_clipboard->wait_is_text_available()) {
return CLIPBOARD_TEXT_TARGET;
+ }
return "";
}
@@ -1324,7 +1425,8 @@ void ClipboardManagerImpl::_setClipboardTargets()
Inkscape::Extension::DB::OutputList outlist;
Inkscape::Extension::db.get_output_list(outlist);
Inkscape::Extension::DB::OutputList::const_iterator out = outlist.begin();
- for ( ; out != outlist.end() && target != (*out)->get_mimetype() ; ++out);
+ for ( ; out != outlist.end() && target != (*out)->get_mimetype() ; ++out) {
+ }
if ( out != outlist.end() ) {
// FIXME: Temporary hack until we add support for memory output.
// Save to a temporary file, read it back and then set the clipboard contents
@@ -1382,18 +1484,20 @@ void ClipboardManagerImpl::_inkscape_wait_for_targets(std::list<Glib::ustring> &
GdkAtom* targets = 0;
gint n_targets = 0;
gboolean test = gtk_clipboard_wait_for_targets( gtk_clipboard_get(GDK_SELECTION_CLIPBOARD), &targets, &n_targets );
- if(!test)
+ if (!test) {
n_targets = 0; //otherwise it will be -1.
+ }
//Add the targets to the C++ container:
- for(int i = 0; i < n_targets; i++)
+ for (int i = 0; i < n_targets; i++)
{
//Convert the atom to a string:
gchar* const atom_name = gdk_atom_name(targets[i]);
Glib::ustring target;
- if(atom_name)
+ if (atom_name) {
target = Glib::ScopedPtr<char>(atom_name).get(); //This frees the gchar*.
+ }
listTargets.push_back(target);
}
@@ -1409,8 +1513,10 @@ ClipboardManager::ClipboardManager() {}
ClipboardManager::~ClipboardManager() {}
ClipboardManager *ClipboardManager::get()
{
- if ( _instance == NULL )
+ if ( _instance == NULL ) {
_instance = new ClipboardManagerImpl;
+ }
+
return _instance;
}
diff --git a/src/ui/clipboard.h b/src/ui/clipboard.h
index 54c05ac0d..6020ecdd8 100644
--- a/src/ui/clipboard.h
+++ b/src/ui/clipboard.h
@@ -6,8 +6,10 @@
*/
/* Authors:
* Krzysztof Kosiński <tweenk@o2.pl>
+ * Jon A. Cruz <jon@joncruz.org>
*
* Copyright (C) 2008 authors
+ * Copyright (C) 2010 Jon A. Cruz
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@@ -39,16 +41,16 @@ namespace UI {
class ClipboardManager {
public:
- virtual void copy() = 0;
+ virtual void copy(SPDesktop *desktop) = 0;
virtual void copyPathParameter(Inkscape::LivePathEffect::PathParam *) = 0;
- virtual bool paste(bool in_place = false) = 0;
- virtual bool pasteStyle() = 0;
- virtual bool pasteSize(bool separately, bool apply_x, bool apply_y) = 0;
- virtual bool pastePathEffect() = 0;
- virtual Glib::ustring getPathParameter() = 0;
- virtual Glib::ustring getShapeOrTextObjectId() = 0;
+ virtual bool paste(SPDesktop *desktop, bool in_place = false) = 0;
+ virtual bool pasteStyle(SPDesktop *desktop) = 0;
+ virtual bool pasteSize(SPDesktop *desktop, bool separately, bool apply_x, bool apply_y) = 0;
+ virtual bool pastePathEffect(SPDesktop *desktop) = 0;
+ virtual Glib::ustring getPathParameter(SPDesktop* desktop) = 0;
+ virtual Glib::ustring getShapeOrTextObjectId(SPDesktop *desktop) = 0;
virtual const gchar *getFirstObjectID() = 0;
-
+
static ClipboardManager *get();
protected:
ClipboardManager(); // singleton
@@ -56,7 +58,7 @@ protected:
private:
ClipboardManager(const ClipboardManager &); ///< no copy
ClipboardManager &operator=(const ClipboardManager &); ///< no assign
-
+
static ClipboardManager *_instance;
};
diff --git a/src/ui/dialog/CMakeLists.txt b/src/ui/dialog/CMakeLists.txt
index ea63d6023..98c4a47bb 100644
--- a/src/ui/dialog/CMakeLists.txt
+++ b/src/ui/dialog/CMakeLists.txt
@@ -9,6 +9,7 @@ ENDIF(WIN32)
SET(ui_dialog_SRC
aboutbox.cpp
align-and-distribute.cpp
+color-item.cpp
debug.cpp
dialog.cpp
dialog-manager.cpp
diff --git a/src/ui/dialog/Makefile_insert b/src/ui/dialog/Makefile_insert
index fac5bad80..033bec875 100644
--- a/src/ui/dialog/Makefile_insert
+++ b/src/ui/dialog/Makefile_insert
@@ -18,6 +18,8 @@ ink_common_sources += \
ui/dialog/behavior.h \
ui/dialog/calligraphic-profile-rename.h \
ui/dialog/calligraphic-profile-rename.cpp \
+ ui/dialog/color-item.cpp \
+ ui/dialog/color-item.h \
ui/dialog/debug.cpp \
ui/dialog/debug.h \
ui/dialog/dialog.cpp \
@@ -75,8 +77,6 @@ ink_common_sources += \
ui/dialog/print-colors-preview-dialog.h \
ui/dialog/scriptdialog.cpp \
ui/dialog/scriptdialog.h \
- ui/dialog/spray-option.cpp \
- ui/dialog/spray-option.h \
ui/dialog/svg-fonts-dialog.cpp \
ui/dialog/svg-fonts-dialog.h \
ui/dialog/swatches.cpp \
diff --git a/src/ui/dialog/aboutbox.cpp b/src/ui/dialog/aboutbox.cpp
index 025bec37a..bbd02fa5d 100644
--- a/src/ui/dialog/aboutbox.cpp
+++ b/src/ui/dialog/aboutbox.cpp
@@ -103,8 +103,8 @@ AboutBox::AboutBox() : Gtk::Dialog(_("About Inkscape")) {
Gtk::Label *label=new Gtk::Label();
gchar *label_text =
- g_strdup_printf("<small><i>Inkscape %s, built %s</i></small>",
- Inkscape::version_string, __DATE__);
+ g_strdup_printf("<small><i>Inkscape %s</i></small>",
+ Inkscape::version_string);
label->set_markup(label_text);
label->set_alignment(Gtk::ALIGN_RIGHT, Gtk::ALIGN_CENTER);
g_free(label_text);
diff --git a/src/ui/dialog/align-and-distribute.cpp b/src/ui/dialog/align-and-distribute.cpp
index 2bba0a0f8..a75a8d68d 100644
--- a/src/ui/dialog/align-and-distribute.cpp
+++ b/src/ui/dialog/align-and-distribute.cpp
@@ -24,20 +24,20 @@
#include "unclump.h"
#include "document.h"
#include "enums.h"
-#include "graphlayout/graphlayout.h"
+#include "graphlayout.h"
#include "inkscape.h"
#include "macros.h"
-#include "node-context.h" //For access to ShapeEditor
#include "preferences.h"
-#include "removeoverlap/removeoverlap.h"
+#include "removeoverlap.h"
#include "selection.h"
-#include "shape-editor.h" //For node align/distribute methods
#include "sp-flowtext.h"
#include "sp-item-transform.h"
#include "sp-text.h"
#include "text-editing.h"
#include "tools-switch.h"
#include "ui/icon-names.h"
+#include "ui/tool/node-tool.h"
+#include "ui/tool/multi-path-manipulator.h"
#include "util/glib-list-iterators.h"
#include "verbs.h"
#include "widgets/icon.h"
@@ -429,12 +429,13 @@ private :
if (!_dialog.getDesktop()) return;
SPEventContext *event_context = sp_desktop_event_context(_dialog.getDesktop());
- if (!SP_IS_NODE_CONTEXT (event_context)) return ;
+ if (!INK_IS_NODE_TOOL (event_context)) return;
+ InkNodeTool *nt = INK_NODE_TOOL(event_context);
if (_distribute)
- event_context->shape_editor->distribute((Geom::Dim2)_orientation);
+ nt->_multipath->distributeNodes(_orientation);
else
- event_context->shape_editor->align((Geom::Dim2)_orientation);
+ nt->_multipath->alignNodes(_orientation);
}
};
diff --git a/src/ui/dialog/color-item.cpp b/src/ui/dialog/color-item.cpp
new file mode 100644
index 000000000..cb6cfbbbe
--- /dev/null
+++ b/src/ui/dialog/color-item.cpp
@@ -0,0 +1,837 @@
+/** @file
+ * @brief Inkscape color swatch UI item.
+ */
+/* Authors:
+ * Jon A. Cruz
+ *
+ * Copyright (C) 2010 Jon A. Cruz
+ *
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ */
+
+#include <errno.h>
+#include <glibmm/i18n.h>
+#include <gtkmm/label.h>
+#include <gtk/gtkdnd.h>
+
+#include "color-item.h"
+
+#include "desktop.h"
+#include "desktop-handles.h"
+#include "desktop-style.h"
+#include "display/nr-plain-stuff.h"
+#include "document.h"
+#include "inkscape.h" // for SP_ACTIVE_DESKTOP
+#include "io/resource.h"
+#include "io/sys.h"
+#include "message-context.h"
+#include "sp-gradient.h"
+#include "sp-item.h"
+#include "svg/svg-color.h"
+#include "xml/node.h"
+#include "xml/repr.h"
+
+#include "color.h" // for SP_RGBA32_U_COMPOSE
+
+
+namespace Inkscape {
+namespace UI {
+namespace Dialogs {
+
+static std::vector<std::string> mimeStrings;
+static std::map<std::string, guint> mimeToInt;
+
+
+#if ENABLE_MAGIC_COLORS
+// TODO remove this soon:
+extern std::vector<SwatchPage*> possible;
+#endif // ENABLE_MAGIC_COLORS
+
+
+#if ENABLE_MAGIC_COLORS
+static bool bruteForce( SPDocument* document, Inkscape::XML::Node* node, Glib::ustring const& match, int r, int g, int b )
+{
+ bool changed = false;
+
+ if ( node ) {
+ gchar const * val = node->attribute("inkscape:x-fill-tag");
+ if ( val && (match == val) ) {
+ SPObject *obj = document->getObjectByRepr( node );
+
+ gchar c[64] = {0};
+ sp_svg_write_color( c, sizeof(c), SP_RGBA32_U_COMPOSE( r, g, b, 0xff ) );
+ SPCSSAttr *css = sp_repr_css_attr_new();
+ sp_repr_css_set_property( css, "fill", c );
+
+ sp_desktop_apply_css_recursive( obj, css, true );
+ static_cast<SPItem*>(obj)->updateRepr();
+
+ changed = true;
+ }
+
+ val = node->attribute("inkscape:x-stroke-tag");
+ if ( val && (match == val) ) {
+ SPObject *obj = document->getObjectByRepr( node );
+
+ gchar c[64] = {0};
+ sp_svg_write_color( c, sizeof(c), SP_RGBA32_U_COMPOSE( r, g, b, 0xff ) );
+ SPCSSAttr *css = sp_repr_css_attr_new();
+ sp_repr_css_set_property( css, "stroke", c );
+
+ sp_desktop_apply_css_recursive( (SPItem*)obj, css, true );
+ ((SPItem*)obj)->updateRepr();
+
+ changed = true;
+ }
+
+ Inkscape::XML::Node* first = node->firstChild();
+ changed |= bruteForce( document, first, match, r, g, b );
+
+ changed |= bruteForce( document, node->next(), match, r, g, b );
+ }
+
+ return changed;
+}
+#endif // ENABLE_MAGIC_COLORS
+
+static void handleClick( GtkWidget* /*widget*/, gpointer callback_data ) {
+ ColorItem* item = reinterpret_cast<ColorItem*>(callback_data);
+ if ( item ) {
+ item->buttonClicked(false);
+ }
+}
+
+static void handleSecondaryClick( GtkWidget* /*widget*/, gint /*arg1*/, gpointer callback_data ) {
+ ColorItem* item = reinterpret_cast<ColorItem*>(callback_data);
+ if ( item ) {
+ item->buttonClicked(true);
+ }
+}
+
+static gboolean handleEnterNotify( GtkWidget* /*widget*/, GdkEventCrossing* /*event*/, gpointer callback_data ) {
+ ColorItem* item = reinterpret_cast<ColorItem*>(callback_data);
+ if ( item ) {
+ SPDesktop *desktop = SP_ACTIVE_DESKTOP;
+ if ( desktop ) {
+ gchar* msg = g_strdup_printf(_("Color: <b>%s</b>; <b>Click</b> to set fill, <b>Shift+click</b> to set stroke"),
+ item->def.descr.c_str());
+ desktop->tipsMessageContext()->set(Inkscape::INFORMATION_MESSAGE, msg);
+ g_free(msg);
+ }
+ }
+ return FALSE;
+}
+
+static gboolean handleLeaveNotify( GtkWidget* /*widget*/, GdkEventCrossing* /*event*/, gpointer callback_data ) {
+ ColorItem* item = reinterpret_cast<ColorItem*>(callback_data);
+ if ( item ) {
+ SPDesktop *desktop = SP_ACTIVE_DESKTOP;
+ if ( desktop ) {
+ desktop->tipsMessageContext()->clear();
+ }
+ }
+ return FALSE;
+}
+
+static void dieDieDie( GtkObject *obj, gpointer user_data )
+{
+ g_message("die die die %p %p", obj, user_data );
+}
+
+static bool getBlock( std::string& dst, guchar ch, std::string const str )
+{
+ bool good = false;
+ std::string::size_type pos = str.find(ch);
+ if ( pos != std::string::npos )
+ {
+ std::string::size_type pos2 = str.find( '(', pos );
+ if ( pos2 != std::string::npos ) {
+ std::string::size_type endPos = str.find( ')', pos2 );
+ if ( endPos != std::string::npos ) {
+ dst = str.substr( pos2 + 1, (endPos - pos2 - 1) );
+ good = true;
+ }
+ }
+ }
+ return good;
+}
+
+static bool popVal( guint64& numVal, std::string& str )
+{
+ bool good = false;
+ std::string::size_type endPos = str.find(',');
+ if ( endPos == std::string::npos ) {
+ endPos = str.length();
+ }
+
+ if ( endPos != std::string::npos && endPos > 0 ) {
+ std::string xxx = str.substr( 0, endPos );
+ const gchar* ptr = xxx.c_str();
+ gchar* endPtr = 0;
+ numVal = g_ascii_strtoull( ptr, &endPtr, 10 );
+ if ( (numVal == G_MAXUINT64) && (ERANGE == errno) ) {
+ // overflow
+ } else if ( (numVal == 0) && (endPtr == ptr) ) {
+ // failed conversion
+ } else {
+ good = true;
+ str.erase( 0, endPos + 1 );
+ }
+ }
+
+ return good;
+}
+
+// TODO resolve this more cleanly:
+extern gboolean colorItemHandleButtonPress( GtkWidget* /*widget*/, GdkEventButton* event, gpointer user_data);
+
+static void colorItemDragBegin( GtkWidget */*widget*/, GdkDragContext* dc, gpointer data )
+{
+ ColorItem* item = reinterpret_cast<ColorItem*>(data);
+ if ( item )
+ {
+ using Inkscape::IO::Resource::get_path;
+ using Inkscape::IO::Resource::ICONS;
+ using Inkscape::IO::Resource::SYSTEM;
+ int width = 32;
+ int height = 24;
+
+ if (item->def.getType() != ege::PaintDef::RGB){
+ GError *error = NULL;
+ gsize bytesRead = 0;
+ gsize bytesWritten = 0;
+ gchar *localFilename = g_filename_from_utf8( get_path(SYSTEM, ICONS, "remove-color.png"),
+ -1,
+ &bytesRead,
+ &bytesWritten,
+ &error);
+ GdkPixbuf* pixbuf = gdk_pixbuf_new_from_file_at_scale(localFilename, width, height, FALSE, &error);
+ g_free(localFilename);
+ gtk_drag_set_icon_pixbuf( dc, pixbuf, 0, 0 );
+ } else {
+ GdkPixbuf* pixbuf = 0;
+ if ( item->getGradient() ){
+ guchar* px = g_new( guchar, 3 * height * width );
+ nr_render_checkerboard_rgb( px, width, height, 3 * width, 0, 0 );
+
+ sp_gradient_render_vector_block_rgb( item->getGradient(),
+ px, width, height, 3 * width,
+ 0, width, TRUE );
+
+ pixbuf = gdk_pixbuf_new_from_data( px, GDK_COLORSPACE_RGB, FALSE, 8,
+ width, height, width * 3,
+ 0, // add delete function
+ 0 );
+ } else {
+ Glib::RefPtr<Gdk::Pixbuf> thumb = Gdk::Pixbuf::create( Gdk::COLORSPACE_RGB, false, 8, width, height );
+ guint32 fillWith = (0xff000000 & (item->def.getR() << 24))
+ | (0x00ff0000 & (item->def.getG() << 16))
+ | (0x0000ff00 & (item->def.getB() << 8));
+ thumb->fill( fillWith );
+ pixbuf = thumb->gobj();
+ g_object_ref(G_OBJECT(pixbuf));
+ }
+ gtk_drag_set_icon_pixbuf( dc, pixbuf, 0, 0 );
+ }
+ }
+
+}
+
+//"drag-drop"
+// gboolean dragDropColorData( GtkWidget *widget,
+// GdkDragContext *drag_context,
+// gint x,
+// gint y,
+// guint time,
+// gpointer user_data)
+// {
+// // TODO finish
+
+// return TRUE;
+// }
+
+
+SwatchPage::SwatchPage() :
+ _name(),
+ _prefWidth(0),
+ _colors()
+{
+}
+
+SwatchPage::~SwatchPage()
+{
+}
+
+
+ColorItem::ColorItem(ege::PaintDef::ColorType type) :
+ Previewable(),
+ def(type),
+ tips(),
+ _previews(),
+ _isFill(false),
+ _isStroke(false),
+ _isLive(false),
+ _linkIsTone(false),
+ _linkPercent(0),
+ _linkGray(0),
+ _linkSrc(0),
+ _grad(0),
+ _pixData(0),
+ _pixWidth(0),
+ _pixHeight(0),
+ _listeners()
+{
+}
+
+ColorItem::ColorItem( unsigned int r, unsigned int g, unsigned int b, Glib::ustring& name ) :
+ Previewable(),
+ def( r, g, b, name ),
+ tips(),
+ _previews(),
+ _isFill(false),
+ _isStroke(false),
+ _isLive(false),
+ _linkIsTone(false),
+ _linkPercent(0),
+ _linkGray(0),
+ _linkSrc(0),
+ _grad(0),
+ _pixData(0),
+ _pixWidth(0),
+ _pixHeight(0),
+ _listeners()
+{
+}
+
+ColorItem::~ColorItem()
+{
+}
+
+ColorItem::ColorItem(ColorItem const &other) :
+ Inkscape::UI::Previewable()
+{
+ if ( this != &other ) {
+ *this = other;
+ }
+}
+
+ColorItem &ColorItem::operator=(ColorItem const &other)
+{
+ if ( this != &other ) {
+ def = other.def;
+
+ // TODO - correct linkage
+ _linkSrc = other._linkSrc;
+ g_message("Erk!");
+ }
+ return *this;
+}
+
+void ColorItem::setState( bool fill, bool stroke )
+{
+ if ( (_isFill != fill) || (_isStroke != stroke) ) {
+ _isFill = fill;
+ _isStroke = stroke;
+
+ for ( std::vector<Gtk::Widget*>::iterator it = _previews.begin(); it != _previews.end(); ++it ) {
+ Gtk::Widget* widget = *it;
+ if ( IS_EEK_PREVIEW(widget->gobj()) ) {
+ EekPreview * preview = EEK_PREVIEW(widget->gobj());
+
+ int val = eek_preview_get_linked( preview );
+ val &= ~(PREVIEW_FILL | PREVIEW_STROKE);
+ if ( _isFill ) {
+ val |= PREVIEW_FILL;
+ }
+ if ( _isStroke ) {
+ val |= PREVIEW_STROKE;
+ }
+ eek_preview_set_linked( preview, static_cast<LinkType>(val) );
+ }
+ }
+ }
+}
+
+void ColorItem::setGradient(SPGradient *grad)
+{
+ if (_grad != grad) {
+ _grad = grad;
+ // TODO regen and push to listeners
+ }
+}
+
+void ColorItem::setPixData(guchar* px, int width, int height)
+{
+ if (px != _pixData) {
+ if (_pixData) {
+ g_free(_pixData);
+ }
+ _pixData = px;
+ _pixWidth = width;
+ _pixHeight = height;
+
+ _updatePreviews();
+ }
+}
+
+void ColorItem::_dragGetColorData( GtkWidget */*widget*/,
+ GdkDragContext */*drag_context*/,
+ GtkSelectionData *data,
+ guint info,
+ guint /*time*/,
+ gpointer user_data)
+{
+ ColorItem* item = reinterpret_cast<ColorItem*>(user_data);
+ std::string key;
+ if ( info < mimeStrings.size() ) {
+ key = mimeStrings[info];
+ } else {
+ g_warning("ERROR: unknown value (%d)", info);
+ }
+
+ if ( !key.empty() ) {
+ char* tmp = 0;
+ int len = 0;
+ int format = 0;
+ item->def.getMIMEData(key, tmp, len, format);
+ if ( tmp ) {
+ GdkAtom dataAtom = gdk_atom_intern( key.c_str(), FALSE );
+ gtk_selection_data_set( data, dataAtom, format, (guchar*)tmp, len );
+ delete[] tmp;
+ }
+ }
+}
+
+void ColorItem::_dropDataIn( GtkWidget */*widget*/,
+ GdkDragContext */*drag_context*/,
+ gint /*x*/, gint /*y*/,
+ GtkSelectionData */*data*/,
+ guint /*info*/,
+ guint /*event_time*/,
+ gpointer /*user_data*/)
+{
+}
+
+void ColorItem::_colorDefChanged(void* data)
+{
+ ColorItem* item = reinterpret_cast<ColorItem*>(data);
+ if ( item ) {
+ item->_updatePreviews();
+ }
+}
+
+void ColorItem::_updatePreviews()
+{
+ for ( std::vector<Gtk::Widget*>::iterator it = _previews.begin(); it != _previews.end(); ++it ) {
+ Gtk::Widget* widget = *it;
+ if ( IS_EEK_PREVIEW(widget->gobj()) ) {
+ EekPreview * preview = EEK_PREVIEW(widget->gobj());
+
+ _regenPreview(preview);
+
+ widget->queue_draw();
+ }
+ }
+
+ for ( std::vector<ColorItem*>::iterator it = _listeners.begin(); it != _listeners.end(); ++it ) {
+ guint r = def.getR();
+ guint g = def.getG();
+ guint b = def.getB();
+
+ if ( (*it)->_linkIsTone ) {
+ r = ( ((*it)->_linkPercent * (*it)->_linkGray) + ((100 - (*it)->_linkPercent) * r) ) / 100;
+ g = ( ((*it)->_linkPercent * (*it)->_linkGray) + ((100 - (*it)->_linkPercent) * g) ) / 100;
+ b = ( ((*it)->_linkPercent * (*it)->_linkGray) + ((100 - (*it)->_linkPercent) * b) ) / 100;
+ } else {
+ r = ( ((*it)->_linkPercent * 255) + ((100 - (*it)->_linkPercent) * r) ) / 100;
+ g = ( ((*it)->_linkPercent * 255) + ((100 - (*it)->_linkPercent) * g) ) / 100;
+ b = ( ((*it)->_linkPercent * 255) + ((100 - (*it)->_linkPercent) * b) ) / 100;
+ }
+
+ (*it)->def.setRGB( r, g, b );
+ }
+
+
+#if ENABLE_MAGIC_COLORS
+ // Look for objects using this color
+ {
+ SPDesktop *desktop = SP_ACTIVE_DESKTOP;
+ if ( desktop ) {
+ SPDocument* document = sp_desktop_document( desktop );
+ Inkscape::XML::Node *rroot = sp_document_repr_root( document );
+ if ( rroot ) {
+
+ // Find where this thing came from
+ Glib::ustring paletteName;
+ bool found = false;
+ int index = 0;
+ for ( std::vector<SwatchPage*>::iterator it2 = possible.begin(); it2 != possible.end() && !found; ++it2 ) {
+ SwatchPage* curr = *it2;
+ index = 0;
+ for ( std::vector<ColorItem*>::iterator zz = curr->_colors.begin(); zz != curr->_colors.end(); ++zz ) {
+ if ( this == *zz ) {
+ found = true;
+ paletteName = curr->_name;
+ break;
+ } else {
+ index++;
+ }
+ }
+ }
+
+ if ( !paletteName.empty() ) {
+ gchar* str = g_strdup_printf("%d|", index);
+ paletteName.insert( 0, str );
+ g_free(str);
+ str = 0;
+
+ if ( bruteForce( document, rroot, paletteName, def.getR(), def.getG(), def.getB() ) ) {
+ sp_document_done( document , SP_VERB_DIALOG_SWATCHES,
+ _("Change color definition"));
+ }
+ }
+ }
+ }
+ }
+#endif // ENABLE_MAGIC_COLORS
+
+}
+
+void ColorItem::_regenPreview(EekPreview * preview)
+{
+ if ( def.getType() != ege::PaintDef::RGB ) {
+ using Inkscape::IO::Resource::get_path;
+ using Inkscape::IO::Resource::ICONS;
+ using Inkscape::IO::Resource::SYSTEM;
+ GError *error = NULL;
+ gsize bytesRead = 0;
+ gsize bytesWritten = 0;
+ gchar *localFilename = g_filename_from_utf8( get_path(SYSTEM, ICONS, "remove-color.png"),
+ -1,
+ &bytesRead,
+ &bytesWritten,
+ &error);
+ GdkPixbuf* pixbuf = gdk_pixbuf_new_from_file(localFilename, &error);
+ if (!pixbuf) {
+ g_warning("Null pixbuf for %p [%s]", localFilename, localFilename );
+ }
+ g_free(localFilename);
+
+ eek_preview_set_pixbuf( preview, pixbuf );
+ }
+ else if ( !_pixData ){
+ eek_preview_set_color( preview,
+ (def.getR() << 8) | def.getR(),
+ (def.getG() << 8) | def.getG(),
+ (def.getB() << 8) | def.getB() );
+ } else {
+ GdkPixbuf* pixbuf = gdk_pixbuf_new_from_data( _pixData, GDK_COLORSPACE_RGB, FALSE, 8,
+ _pixWidth, _pixHeight, _pixWidth * 3,
+ 0, // add delete function
+ 0 );
+ eek_preview_set_pixbuf( preview, pixbuf );
+ }
+
+ eek_preview_set_linked( preview, (LinkType)((_linkSrc ? PREVIEW_LINK_IN:0)
+ | (_listeners.empty() ? 0:PREVIEW_LINK_OUT)
+ | (_isLive ? PREVIEW_LINK_OTHER:0)) );
+}
+
+Gtk::Widget* ColorItem::getPreview(PreviewStyle style, ViewType view, ::PreviewSize size, guint ratio)
+{
+ Gtk::Widget* widget = 0;
+ if ( style == PREVIEW_STYLE_BLURB) {
+ Gtk::Label *lbl = new Gtk::Label(def.descr);
+ lbl->set_alignment(Gtk::ALIGN_LEFT, Gtk::ALIGN_CENTER);
+ widget = lbl;
+ } else {
+// Glib::ustring blank(" ");
+// if ( size == Inkscape::ICON_SIZE_MENU || size == Inkscape::ICON_SIZE_DECORATION ) {
+// blank = " ";
+// }
+
+ GtkWidget* eekWidget = eek_preview_new();
+ EekPreview * preview = EEK_PREVIEW(eekWidget);
+ Gtk::Widget* newBlot = Glib::wrap(eekWidget);
+
+ _regenPreview(preview);
+
+ eek_preview_set_details( preview, (::PreviewStyle)style, (::ViewType)view, (::PreviewSize)size, ratio );
+
+ def.addCallback( _colorDefChanged, this );
+
+ GValue val = {0, {{0}, {0}}};
+ g_value_init( &val, G_TYPE_BOOLEAN );
+ g_value_set_boolean( &val, FALSE );
+ g_object_set_property( G_OBJECT(preview), "focus-on-click", &val );
+
+/*
+ Gtk::Button *btn = new Gtk::Button(blank);
+ Gdk::Color color;
+ color.set_rgb((_r << 8)|_r, (_g << 8)|_g, (_b << 8)|_b);
+ btn->modify_bg(Gtk::STATE_NORMAL, color);
+ btn->modify_bg(Gtk::STATE_ACTIVE, color);
+ btn->modify_bg(Gtk::STATE_PRELIGHT, color);
+ btn->modify_bg(Gtk::STATE_SELECTED, color);
+
+ Gtk::Widget* newBlot = btn;
+*/
+
+ tips.set_tip((*newBlot), def.descr);
+
+/*
+ newBlot->signal_clicked().connect( sigc::mem_fun(*this, &ColorItem::buttonClicked) );
+
+ sigc::signal<void> type_signal_something;
+*/
+
+ g_signal_connect( G_OBJECT(newBlot->gobj()),
+ "clicked",
+ G_CALLBACK(handleClick),
+ this);
+
+ g_signal_connect( G_OBJECT(newBlot->gobj()),
+ "alt-clicked",
+ G_CALLBACK(handleSecondaryClick),
+ this);
+
+ g_signal_connect( G_OBJECT(newBlot->gobj()),
+ "button-press-event",
+ G_CALLBACK(colorItemHandleButtonPress),
+ this);
+
+ {
+ std::vector<std::string> listing = def.getMIMETypes();
+ int entryCount = listing.size();
+ GtkTargetEntry* entries = new GtkTargetEntry[entryCount];
+ GtkTargetEntry* curr = entries;
+ for ( std::vector<std::string>::iterator it = listing.begin(); it != listing.end(); ++it ) {
+ curr->target = g_strdup(it->c_str());
+ curr->flags = 0;
+ if ( mimeToInt.find(*it) == mimeToInt.end() ){
+ // these next lines are order-dependent:
+ mimeToInt[*it] = mimeStrings.size();
+ mimeStrings.push_back(*it);
+ }
+ curr->info = mimeToInt[curr->target];
+ curr++;
+ }
+ gtk_drag_source_set( GTK_WIDGET(newBlot->gobj()),
+ GDK_BUTTON1_MASK,
+ entries, entryCount,
+ GdkDragAction(GDK_ACTION_MOVE | GDK_ACTION_COPY) );
+ for ( int i = 0; i < entryCount; i++ ) {
+ g_free(entries[i].target);
+ }
+ delete[] entries;
+ }
+
+ g_signal_connect( G_OBJECT(newBlot->gobj()),
+ "drag-data-get",
+ G_CALLBACK(ColorItem::_dragGetColorData),
+ this);
+
+ g_signal_connect( G_OBJECT(newBlot->gobj()),
+ "drag-begin",
+ G_CALLBACK(colorItemDragBegin),
+ this );
+
+ g_signal_connect( G_OBJECT(newBlot->gobj()),
+ "enter-notify-event",
+ G_CALLBACK(handleEnterNotify),
+ this);
+
+ g_signal_connect( G_OBJECT(newBlot->gobj()),
+ "leave-notify-event",
+ G_CALLBACK(handleLeaveNotify),
+ this);
+
+// g_signal_connect( G_OBJECT(newBlot->gobj()),
+// "drag-drop",
+// G_CALLBACK(dragDropColorData),
+// this);
+
+ if ( def.isEditable() )
+ {
+// gtk_drag_dest_set( GTK_WIDGET(newBlot->gobj()),
+// GTK_DEST_DEFAULT_ALL,
+// destColorTargets,
+// G_N_ELEMENTS(destColorTargets),
+// GdkDragAction(GDK_ACTION_COPY | GDK_ACTION_MOVE) );
+
+
+// g_signal_connect( G_OBJECT(newBlot->gobj()),
+// "drag-data-received",
+// G_CALLBACK(_dropDataIn),
+// this );
+ }
+
+ g_signal_connect( G_OBJECT(newBlot->gobj()),
+ "destroy",
+ G_CALLBACK(dieDieDie),
+ this);
+
+
+ widget = newBlot;
+ }
+
+ _previews.push_back( widget );
+
+ return widget;
+}
+
+void ColorItem::buttonClicked(bool secondary)
+{
+ SPDesktop *desktop = SP_ACTIVE_DESKTOP;
+ if (desktop) {
+ char const * attrName = secondary ? "stroke" : "fill";
+
+ SPCSSAttr *css = sp_repr_css_attr_new();
+ Glib::ustring descr;
+ switch (def.getType()) {
+ case ege::PaintDef::CLEAR: {
+ // TODO actually make this clear
+ sp_repr_css_set_property( css, attrName, "none" );
+ descr = secondary? _("Remove stroke color") : _("Remove fill color");
+ break;
+ }
+ case ege::PaintDef::NONE: {
+ sp_repr_css_set_property( css, attrName, "none" );
+ descr = secondary? _("Set stroke color to none") : _("Set fill color to none");
+ break;
+ }
+ case ege::PaintDef::RGB: {
+ Glib::ustring colorspec;
+ if ( _grad ){
+ colorspec = "url(#";
+ colorspec += _grad->getId();
+ colorspec += ")";
+ } else {
+ gchar c[64];
+ guint32 rgba = (def.getR() << 24) | (def.getG() << 16) | (def.getB() << 8) | 0xff;
+ sp_svg_write_color(c, sizeof(c), rgba);
+ colorspec = c;
+ }
+ sp_repr_css_set_property( css, attrName, colorspec.c_str() );
+ descr = secondary? _("Set stroke color from swatch") : _("Set fill color from swatch");
+ break;
+ }
+ }
+ sp_desktop_set_style(desktop, css);
+ sp_repr_css_attr_unref(css);
+
+ sp_document_done( sp_desktop_document(desktop), SP_VERB_DIALOG_SWATCHES, descr.c_str() );
+ }
+}
+
+void ColorItem::_wireMagicColors( SwatchPage *colorSet )
+{
+ if ( colorSet )
+ {
+ for ( std::vector<ColorItem*>::iterator it = colorSet->_colors.begin(); it != colorSet->_colors.end(); ++it )
+ {
+ std::string::size_type pos = (*it)->def.descr.find("*{");
+ if ( pos != std::string::npos )
+ {
+ std::string subby = (*it)->def.descr.substr( pos + 2 );
+ std::string::size_type endPos = subby.find("}*");
+ if ( endPos != std::string::npos )
+ {
+ subby.erase( endPos );
+ //g_message("FOUND MAGIC at '%s'", (*it)->def.descr.c_str());
+ //g_message(" '%s'", subby.c_str());
+
+ if ( subby.find('E') != std::string::npos )
+ {
+ (*it)->def.setEditable( true );
+ }
+
+ if ( subby.find('L') != std::string::npos )
+ {
+ (*it)->_isLive = true;
+ }
+
+ std::string part;
+ // Tint. index + 1 more val.
+ if ( getBlock( part, 'T', subby ) ) {
+ guint64 colorIndex = 0;
+ if ( popVal( colorIndex, part ) ) {
+ guint64 percent = 0;
+ if ( popVal( percent, part ) ) {
+ (*it)->_linkTint( *(colorSet->_colors[colorIndex]), percent );
+ }
+ }
+ }
+
+ // Shade/tone. index + 1 or 2 more val.
+ if ( getBlock( part, 'S', subby ) ) {
+ guint64 colorIndex = 0;
+ if ( popVal( colorIndex, part ) ) {
+ guint64 percent = 0;
+ if ( popVal( percent, part ) ) {
+ guint64 grayLevel = 0;
+ if ( !popVal( grayLevel, part ) ) {
+ grayLevel = 0;
+ }
+ (*it)->_linkTone( *(colorSet->_colors[colorIndex]), percent, grayLevel );
+ }
+ }
+ }
+
+ }
+ }
+ }
+ }
+}
+
+
+void ColorItem::_linkTint( ColorItem& other, int percent )
+{
+ if ( !_linkSrc )
+ {
+ other._listeners.push_back(this);
+ _linkIsTone = false;
+ _linkPercent = percent;
+ if ( _linkPercent > 100 )
+ _linkPercent = 100;
+ if ( _linkPercent < 0 )
+ _linkPercent = 0;
+ _linkGray = 0;
+ _linkSrc = &other;
+
+ ColorItem::_colorDefChanged(&other);
+ }
+}
+
+void ColorItem::_linkTone( ColorItem& other, int percent, int grayLevel )
+{
+ if ( !_linkSrc )
+ {
+ other._listeners.push_back(this);
+ _linkIsTone = true;
+ _linkPercent = percent;
+ if ( _linkPercent > 100 )
+ _linkPercent = 100;
+ if ( _linkPercent < 0 )
+ _linkPercent = 0;
+ _linkGray = grayLevel;
+ _linkSrc = &other;
+
+ ColorItem::_colorDefChanged(&other);
+ }
+}
+
+} // namespace Dialogs
+} // 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:encoding=utf-8:textwidth=99 :
diff --git a/src/ui/dialog/color-item.h b/src/ui/dialog/color-item.h
new file mode 100644
index 000000000..4aac86a30
--- /dev/null
+++ b/src/ui/dialog/color-item.h
@@ -0,0 +1,128 @@
+/** @file
+ * @brief Inkscape color swatch UI item.
+ */
+/* Authors:
+ * Jon A. Cruz
+ *
+ * Copyright (C) 2010 Jon A. Cruz
+ *
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ */
+
+#ifndef SEEN_DIALOGS_COLOR_ITEM_H
+#define SEEN_DIALOGS_COLOR_ITEM_H
+
+#include <gtkmm/tooltips.h>
+
+#include "widgets/ege-paint-def.h"
+#include "ui/previewable.h"
+
+class SPGradient;
+
+namespace Inkscape {
+namespace UI {
+namespace Dialogs {
+
+class ColorItem;
+
+class SwatchPage
+{
+public:
+ SwatchPage();
+ ~SwatchPage();
+
+ Glib::ustring _name;
+ int _prefWidth;
+ std::vector<ColorItem*> _colors;
+};
+
+
+/**
+ * The color swatch you see on screen as a clickable box.
+ */
+class ColorItem : public Inkscape::UI::Previewable
+{
+ friend void _loadPaletteFile( gchar const *filename );
+public:
+ ColorItem( ege::PaintDef::ColorType type );
+ ColorItem( unsigned int r, unsigned int g, unsigned int b,
+ Glib::ustring& name );
+ virtual ~ColorItem();
+ ColorItem(ColorItem const &other);
+ virtual ColorItem &operator=(ColorItem const &other);
+ virtual Gtk::Widget* getPreview(PreviewStyle style,
+ ViewType view,
+ ::PreviewSize size,
+ guint ratio);
+ void buttonClicked(bool secondary = false);
+
+ void setGradient(SPGradient *grad);
+ SPGradient * getGradient() const { return _grad; }
+
+ void setPixData(guchar* px, int width, int height);
+
+ void setState( bool fill, bool stroke );
+ bool isFill() { return _isFill; }
+ bool isStroke() { return _isStroke; }
+
+ ege::PaintDef def;
+
+private:
+
+ static void _dropDataIn( GtkWidget *widget,
+ GdkDragContext *drag_context,
+ gint x, gint y,
+ GtkSelectionData *data,
+ guint info,
+ guint event_time,
+ gpointer user_data);
+
+ static void _dragGetColorData( GtkWidget *widget,
+ GdkDragContext *drag_context,
+ GtkSelectionData *data,
+ guint info,
+ guint time,
+ gpointer user_data);
+
+ static void _wireMagicColors( SwatchPage *colorSet );
+ static void _colorDefChanged(void* data);
+
+ void _updatePreviews();
+ void _regenPreview(EekPreview * preview);
+
+ void _linkTint( ColorItem& other, int percent );
+ void _linkTone( ColorItem& other, int percent, int grayLevel );
+
+ Gtk::Tooltips tips;
+ std::vector<Gtk::Widget*> _previews;
+
+ bool _isFill;
+ bool _isStroke;
+ bool _isLive;
+ bool _linkIsTone;
+ int _linkPercent;
+ int _linkGray;
+ ColorItem* _linkSrc;
+ SPGradient* _grad;
+ guchar *_pixData;
+ int _pixWidth;
+ int _pixHeight;
+ std::vector<ColorItem*> _listeners;
+};
+
+} // namespace Dialogs
+} // namespace UI
+} // namespace Inkscape
+
+#endif // SEEN_DIALOGS_COLOR_ITEM_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:encoding=utf-8:textwidth=99 :
diff --git a/src/ui/dialog/dialog-manager.cpp b/src/ui/dialog/dialog-manager.cpp
index 2116d46c3..30cbed649 100644
--- a/src/ui/dialog/dialog-manager.cpp
+++ b/src/ui/dialog/dialog-manager.cpp
@@ -40,7 +40,6 @@
#include "ui/dialog/icon-preview.h"
#include "ui/dialog/floating-behavior.h"
#include "ui/dialog/dock-behavior.h"
-#include "ui/dialog/spray-option.h"
#include "ui/dialog/print-colors-preview-dialog.h"
#include "preferences.h"
@@ -114,7 +113,6 @@ DialogManager::DialogManager() {
registerFactory("Transformation", &create<Transformation, FloatingBehavior>);
registerFactory("UndoHistory", &create<UndoHistory, FloatingBehavior>);
registerFactory("InputDevices", &create<InputDialog, FloatingBehavior>);
- registerFactory("SprayOptionClass", &create<SprayOptionClass, FloatingBehavior>);
} else {
@@ -142,7 +140,6 @@ DialogManager::DialogManager() {
registerFactory("Transformation", &create<Transformation, DockBehavior>);
registerFactory("UndoHistory", &create<UndoHistory, DockBehavior>);
registerFactory("InputDevices", &create<InputDialog, DockBehavior>);
- registerFactory("SprayOptionClass", &create<SprayOptionClass, DockBehavior>);
}
}
diff --git a/src/ui/dialog/document-properties.cpp b/src/ui/dialog/document-properties.cpp
index a7241ea40..86baa85cd 100644
--- a/src/ui/dialog/document-properties.cpp
+++ b/src/ui/dialog/document-properties.cpp
@@ -222,7 +222,7 @@ DocumentProperties::build_page()
Gtk::Label* label_bor = manage (new Gtk::Label);
label_bor->set_markup (_("<b>Border</b>"));
Gtk::Label *label_for = manage (new Gtk::Label);
- label_for->set_markup (_("<b>Format</b>"));
+ label_for->set_markup (_("<b>Page Size</b>"));
_page_sizer.init();
Gtk::Widget *const widget_array[] =
diff --git a/src/ui/dialog/filedialogimpl-gtkmm.cpp b/src/ui/dialog/filedialogimpl-gtkmm.cpp
index e650cf842..916e3ec97 100644
--- a/src/ui/dialog/filedialogimpl-gtkmm.cpp
+++ b/src/ui/dialog/filedialogimpl-gtkmm.cpp
@@ -521,7 +521,7 @@ bool SVGPreview::set(Glib::ustring &fileName, int dialogType)
return FALSE;
}
long fileLen = info.st_size;
- if (fileLen > 0x150000L)
+ if (fileLen > 0xA00000L)
{
showingNoPreview = false;
showTooLarge(fileLen);
diff --git a/src/ui/dialog/filedialogimpl-win32.cpp b/src/ui/dialog/filedialogimpl-win32.cpp
index d22a368f2..0f3672f25 100644
--- a/src/ui/dialog/filedialogimpl-win32.cpp
+++ b/src/ui/dialog/filedialogimpl-win32.cpp
@@ -61,7 +61,7 @@ namespace Dialog
const int PreviewWidening = 150;
const char PreviewWindowClassName[] = "PreviewWnd";
-const unsigned long MaxPreviewFileSize = 1344; // kB
+const unsigned long MaxPreviewFileSize = 10240; // kB
#define IDC_SHOW_PREVIEW 1000
@@ -454,15 +454,15 @@ UINT_PTR CALLBACK FileOpenDialogImplWin32::GetOpenFileName_hookproc(
pImpl = (FileOpenDialogImplWin32*)ofn->lCustData;
// Subclass the parent
- pImpl->_base_window_proc = (WNDPROC)GetWindowLongPtr(hParentWnd, GWL_WNDPROC);
- SetWindowLongPtr(hParentWnd, GWL_WNDPROC, (LONG_PTR)file_dialog_subclass_proc);
+ pImpl->_base_window_proc = (WNDPROC)GetWindowLongPtr(hParentWnd, GWLP_WNDPROC);
+ SetWindowLongPtr(hParentWnd, GWLP_WNDPROC, reinterpret_cast<LONG_PTR>(file_dialog_subclass_proc));
// Add a button to the toolbar
pImpl->_toolbar_wnd = FindWindowEx(hParentWnd, NULL, "ToolbarWindow32", NULL);
pImpl->_show_preview_button_bitmap = LoadBitmap(
hInstance, MAKEINTRESOURCE(IDC_SHOW_PREVIEW));
- TBADDBITMAP tbAddBitmap = {NULL, (UINT)pImpl->_show_preview_button_bitmap};
+ TBADDBITMAP tbAddBitmap = {NULL, reinterpret_cast<UINT_PTR>(pImpl->_show_preview_button_bitmap)};
const int iBitmapIndex = SendMessage(pImpl->_toolbar_wnd,
TB_ADDBITMAP, 1, (LPARAM)&tbAddBitmap);
@@ -1373,10 +1373,10 @@ void FileOpenDialogImplWin32::render_preview()
if(_preview_bitmap_image) // Is the image a pixbuf?
{
// Set the transformation
- const Matrix matrix = {
+ const Cairo::Matrix matrix(
scaleFactor, 0,
0, scaleFactor,
- svgX, svgY };
+ svgX, svgY);
context->set_matrix (matrix);
// Render the image
diff --git a/src/ui/dialog/filter-effects-dialog.cpp b/src/ui/dialog/filter-effects-dialog.cpp
index 1345ffe55..132e5fd4e 100644
--- a/src/ui/dialog/filter-effects-dialog.cpp
+++ b/src/ui/dialog/filter-effects-dialog.cpp
@@ -1265,7 +1265,7 @@ void FilterEffectsDialog::FilterModifier::update_filters()
SPFilter* f = (SPFilter*)l->data;
row[_columns.filter] = f;
const gchar* lbl = f->label();
- const gchar* id = SP_OBJECT_ID(f);
+ const gchar* id = f->getId();
row[_columns.label] = lbl ? lbl : (id ? id : "filter");
}
@@ -1485,7 +1485,7 @@ void FilterEffectsDialog::PrimitiveList::update()
row[_columns.primitive] = prim;
row[_columns.type_id] = FPConverter.get_id_from_key(prim->repr->name());
row[_columns.type] = _(FPConverter.get_label(row[_columns.type_id]).c_str());
- row[_columns.id] = SP_OBJECT_ID(prim);
+ row[_columns.id] = prim->getId();
if(prim == active_prim) {
get_selection()->select(row);
diff --git a/src/ui/dialog/icon-preview.cpp b/src/ui/dialog/icon-preview.cpp
index 336afc3c5..088f63031 100644
--- a/src/ui/dialog/icon-preview.cpp
+++ b/src/ui/dialog/icon-preview.cpp
@@ -90,7 +90,7 @@ IconPreviewPanel::IconPreviewPanel() :
std::vector<Glib::ustring> pref_sizes = prefs->getAllDirs("/iconpreview/sizes/default");
std::vector<int> rawSizes;
-
+
for (std::vector<Glib::ustring>::iterator i = pref_sizes.begin(); i != pref_sizes.end(); ++i) {
if (prefs->getBool(*i + "/show", true)) {
int sizeVal = prefs->getInt(*i + "/value", -1);
@@ -215,7 +215,7 @@ void IconPreviewPanel::refreshPreview()
while ( items && !target ) {
SPItem* item = SP_ITEM( items->data );
SPObject * obj = SP_OBJECT(item);
- gchar const *id = SP_OBJECT_ID( obj );
+ gchar const *id = obj->getId();
if ( id ) {
target = obj;
}
@@ -248,7 +248,7 @@ void IconPreviewPanel::modeToggled()
void IconPreviewPanel::renderPreview( SPObject* obj )
{
SPDocument * doc = SP_OBJECT_DOCUMENT(obj);
- gchar * id = SP_OBJECT_ID(obj);
+ gchar const * id = obj->getId();
// g_message(" setting up to render '%s' as the icon", id );
diff --git a/src/ui/dialog/inkscape-preferences.cpp b/src/ui/dialog/inkscape-preferences.cpp
index 90516063c..40efc8282 100644
--- a/src/ui/dialog/inkscape-preferences.cpp
+++ b/src/ui/dialog/inkscape-preferences.cpp
@@ -433,15 +433,28 @@ void InkscapePreferences::initPageTools()
this->AddPage(_page_node, _("Node"), iter_tools, PREFS_PAGE_TOOLS_NODE);
AddSelcueCheckbox(_page_node, "/tools/nodes", true);
AddGradientCheckbox(_page_node, "/tools/nodes", true);
- _page_node.add_group_header( _("Path outline:"));
+ _page_node.add_group_header( _("Path outline"));
_t_node_pathoutline_color.init(_("Path outline color"), "/tools/nodes/highlight_color", 0xff0000ff);
- _page_node.add_line( false, _("Path outline color"), _t_node_pathoutline_color, "", _("Selects the color used for showing the path outline."), false);
- _t_node_pathflash_enabled.init ( _("Path outline flash on mouse-over"), "/tools/nodes/pathflash_enabled", false);
+ _page_node.add_line( false, "", _t_node_pathoutline_color, "", _("Selects the color used for showing the path outline."), false);
+ _t_node_show_outline.init(_("Always show outline"), "/tools/nodes/show_outline", false);
+ _page_node.add_line( true, "", _t_node_show_outline, "", _("Show outlines for all paths, not only invisible paths"));
+ _t_node_live_outline.init(_("Update outline when dragging nodes"), "/tools/nodes/live_outline", false);
+ _page_node.add_line( true, "", _t_node_live_outline, "", _("Update the outline when dragging or transforming nodes. If this is off, the outline will only update when completing a drag."));
+ _t_node_live_objects.init(_("Update paths when dragging nodes"), "/tools/nodes/live_objects", false);
+ _page_node.add_line( true, "", _t_node_live_objects, "", _("Update paths when dragging or transforming nodes. If this is off, paths will only be updated when completing a drag."));
+ _t_node_show_path_direction.init(_("Show path direction on outlines"), "/tools/nodes/show_path_direction", false);
+ _page_node.add_line( true, "", _t_node_show_path_direction, "", _("Visualize the direction of selected paths by drawing small arrows in the middle of each outline segment"));
+ _t_node_pathflash_enabled.init ( _("Show temporary path outline"), "/tools/nodes/pathflash_enabled", false);
_page_node.add_line( true, "", _t_node_pathflash_enabled, "", _("When hovering over a path, briefly flash its outline."));
- _t_node_pathflash_unselected.init ( _("Suppress path outline flash when one path selected"), "/tools/nodes/pathflash_unselected", false);
- _page_node.add_line( true, "", _t_node_pathflash_unselected, "", _("If a path is selected, do not continue flashing path outlines."));
+ _t_node_pathflash_selected.init ( _("Show temporary outline for selected paths"), "/tools/nodes/pathflash_selected", false);
+ _page_node.add_line( true, "", _t_node_pathflash_selected, "", _("Show temporary outline even when a path is selected for editing"));
_t_node_pathflash_timeout.init("/tools/nodes/pathflash_timeout", 0, 10000.0, 100.0, 100.0, 1000.0, true, false);
_page_node.add_line( false, _("Flash time"), _t_node_pathflash_timeout, "ms", _("Specifies how long the path outline will be visible after a mouse-over (in milliseconds). Specify 0 to have the outline shown until mouse leaves the path."), false);
+ _page_node.add_group_header(_("Editing preferences"));
+ _t_node_single_node_transform_handles.init(_("Show transform handles for single nodes"), "/tools/nodes/single_node_transform_handles", false);
+ _page_node.add_line( true, "", _t_node_single_node_transform_handles, "", _("Show transform handles even when only a single node is selected."));
+ _t_node_delete_preserves_shape.init(_("Deleting nodes preserves shape"), "/tools/nodes/delete_preserves_shape", true);
+ _page_node.add_line( true, "", _t_node_delete_preserves_shape, "", _("Move handles next to deleted nodes to resemble original shape. Hold Ctrl to get the other behavior."));
//Tweak
this->AddPage(_page_tweak, _("Tweak"), iter_tools, PREFS_PAGE_TOOLS_TWEAK);
@@ -657,6 +670,28 @@ void InkscapePreferences::initPageMasks()
_mask_mask_remove.init ( _("Remove clippath/mask object after applying"), "/options/maskobject/remove", true);
_page_mask.add_line(true, "", _mask_mask_remove, "",
_("After applying, remove the object used as the clipping path or mask from the drawing"));
+
+ _page_mask.add_group_header( _("Before applying clippath/mask:"));
+
+ _mask_grouping_none.init( _("Do not group clipped/masked objects"), "/options/maskobject/grouping", PREFS_MASKOBJECT_GROUPING_NONE, true, 0);
+ _mask_grouping_separate.init( _("Enclose every clipped/masked object in its own group"), "/options/maskobject/grouping", PREFS_MASKOBJECT_GROUPING_SEPARATE, false, &_mask_grouping_none);
+ _mask_grouping_all.init( _("Put all clipped/masked objects into one group"), "/options/maskobject/grouping", PREFS_MASKOBJECT_GROUPING_ALL, false, &_mask_grouping_none);
+
+ _page_mask.add_line(true, "", _mask_grouping_none, "",
+ _("Apply clippath/mask to every object"));
+
+ _page_mask.add_line(true, "", _mask_grouping_separate, "",
+ _("Apply clippath/mask to groups containing single object"));
+
+ _page_mask.add_line(true, "", _mask_grouping_all, "",
+ _("Apply clippath/mask to group containing all objects"));
+
+ _page_mask.add_group_header( _("After releasing clippath/mask:"));
+
+ _mask_ungrouping.init ( _("Ungroup automatically created groups"), "/options/maskobject/ungrouping", true);
+ _page_mask.add_line(true, "", _mask_ungrouping, "",
+ _("Ungroup groups created when setting clip/mask"));
+
this->AddPage(_page_mask, _("Clippaths and masks"), PREFS_PAGE_MASKS);
}
@@ -987,7 +1022,7 @@ void InkscapePreferences::initPageGrids()
_page_grids.add_line( false, "", _grids_notebook, "", "", false);
_grids_notebook.append_page(_grids_xy, CanvasGrid::getName( GRID_RECTANGULAR ));
_grids_notebook.append_page(_grids_axonom, CanvasGrid::getName( GRID_AXONOMETRIC ));
- _grids_xy_units.init("/options/grids/units");
+ _grids_xy_units.init("/options/grids/xy/units");
_grids_xy.add_line( false, _("Grid units:"), _grids_xy_units, "", "", false);
_grids_xy_origin_x.init("/options/grids/xy/origin_x", -10000.0, 10000.0, 0.1, 1.0, 0.0, false, false);
_grids_xy_origin_y.init("/options/grids/xy/origin_y", -10000.0, 10000.0, 0.1, 1.0, 0.0, false, false);
@@ -1069,7 +1104,7 @@ void InkscapePreferences::initPageUI()
_("Chinese/Taiwan (zh_TW)"), _("Croatian (hr)"), _("Czech (cs)"),
_("Danish (da)"), _("Dutch (nl)"), _("Dzongkha (dz)"), _("German (de)"), _("Greek (el)"), _("English (en)"), _("English/Australia (en_AU)"),
_("English/Canada (en_CA)"), _("English/Great Britain (en_GB)"), _("Pig Latin (en_US@piglatin)"),
- _("Esperanto (eo)"), _("Estonian (et)"), _("Finnish (fi)"),
+ _("Esperanto (eo)"), _("Estonian (et)"), _("Farsi (fa)"), _("Finnish (fi)"),
_("French (fr)"), _("Irish (ga)"), _("Galician (gl)"), _("Hebrew (he)"), _("Hungarian (hu)"),
_("Indonesian (id)"), _("Italian (it)"), _("Japanese (ja)"), _("Khmer (km)"), _("Kinyarwanda (rw)"), _("Korean (ko)"), _("Lithuanian (lt)"), _("Macedonian (mk)"),
_("Mongolian (mn)"), _("Nepali (ne)"), _("Norwegian Bokmål (nb)"), _("Norwegian Nynorsk (nn)"), _("Panjabi (pa)"),
@@ -1077,7 +1112,7 @@ void InkscapePreferences::initPageUI()
_("Serbian (sr)"), _("Serbian in Latin script (sr@latin)"), _("Slovak (sk)"), _("Slovenian (sl)"), _("Spanish (es)"), _("Spanish/Mexico (es_MX)"),
_("Swedish (sv)"), _("Thai (th)"), _("Turkish (tr)"), _("Ukrainian (uk)"), _("Vietnamese (vi)")};
Glib::ustring langValues[] = {"", "sq", "am", "ar", "hy", "az", "eu", "be", "bg", "bn", "br", "ca", "ca@valencia", "zh_CN", "zh_TW", "hr", "cs", "da", "nl",
- "dz", "de", "el", "en", "en_AU", "en_CA", "en_GB", "en_US@piglatin", "eo", "et", "fi", "fr", "ga",
+ "dz", "de", "el", "en", "en_AU", "en_CA", "en_GB", "en_US@piglatin", "eo", "et", "fa", "fi", "fr", "ga",
"gl", "he", "hu", "id", "it", "ja", "km", "rw", "ko", "lt", "mk", "mn", "ne", "nb", "nn", "pa",
"pl", "pt", "pt_BR", "ro", "ru", "sr", "sr@latin", "sk", "sl", "es", "es_MX", "sv", "th", "tr", "uk", "vi" };
@@ -1121,6 +1156,12 @@ void InkscapePreferences::initPageUI()
_page_ui.add_line( false, _("Zoom correction factor (in %):"), _ui_zoom_correction, "",
_("Adjust the slider until the length of the ruler on your screen matches its real length. This information is used when zooming to 1:1, 1:2, etc., to display objects in their true sizes"), true);
+
+ _ui_partialdynamic.init( _("Enable dynamic relayout for incomplete sections."), "/options/workarounds/dynamicnotdone", false);
+ _page_ui.add_line( false, "", _ui_partialdynamic, "",
+ _("When on, will allow dynamic layout of components that are not completely finished being refactored."), true);
+
+
this->AddPage(_page_ui, _("Interface"), PREFS_PAGE_UI);
}
diff --git a/src/ui/dialog/inkscape-preferences.h b/src/ui/dialog/inkscape-preferences.h
index 16e62df59..0ba8c965d 100644
--- a/src/ui/dialog/inkscape-preferences.h
+++ b/src/ui/dialog/inkscape-preferences.h
@@ -141,9 +141,15 @@ protected:
PrefRadioButton _t_sel_trans_obj, _t_sel_trans_outl, _t_sel_cue_none, _t_sel_cue_mark,
_t_sel_cue_box, _t_bbox_visual, _t_bbox_geometric;
PrefCheckButton _t_cvg_keep_objects, _t_cvg_convert_whole_groups;
+ PrefCheckButton _t_node_show_outline;
+ PrefCheckButton _t_node_live_outline;
+ PrefCheckButton _t_node_live_objects;
PrefCheckButton _t_node_pathflash_enabled;
- PrefCheckButton _t_node_pathflash_unselected;
+ PrefCheckButton _t_node_pathflash_selected;
PrefSpinButton _t_node_pathflash_timeout;
+ PrefCheckButton _t_node_show_path_direction;
+ PrefCheckButton _t_node_single_node_transform_handles;
+ PrefCheckButton _t_node_delete_preserves_shape;
PrefColorPicker _t_node_pathoutline_color;
PrefRadioButton _win_dockable, _win_floating;
@@ -167,6 +173,8 @@ protected:
PrefCheckButton _mask_mask_on_top;
PrefCheckButton _mask_mask_remove;
+ PrefRadioButton _mask_grouping_none, _mask_grouping_separate, _mask_grouping_all;
+ PrefCheckButton _mask_ungrouping;
PrefRadioButton _blur_quality_best, _blur_quality_better, _blur_quality_normal, _blur_quality_worse, _blur_quality_worst;
PrefRadioButton _filter_quality_best, _filter_quality_better, _filter_quality_normal, _filter_quality_worse, _filter_quality_worst;
@@ -197,6 +205,7 @@ protected:
PrefCombo _misc_small_tools;
PrefCheckButton _ui_colorsliders_top;
PrefSpinButton _misc_recent;
+ PrefCheckButton _ui_partialdynamic;
ZoomCorrRulerSlider _ui_zoom_correction;
//Spellcheck
diff --git a/src/ui/dialog/layers.cpp b/src/ui/dialog/layers.cpp
index a06b6c9b6..98bf236fc 100644
--- a/src/ui/dialog/layers.cpp
+++ b/src/ui/dialog/layers.cpp
@@ -304,7 +304,7 @@ bool LayersPanel::_checkForUpdated(const Gtk::TreePath &/*path*/, const Gtk::Tre
Glib::ustring tmp = row[_model->_colLabel];
if ( layer == row[_model->_colObject] )
{
- row[_model->_colLabel] = layer->label() ? layer->label() : SP_OBJECT_ID(layer);
+ row[_model->_colLabel] = layer->label() ? layer->label() : layer->getId();
row[_model->_colVisible] = SP_IS_ITEM(layer) ? !SP_ITEM(layer)->isHidden() : false;
row[_model->_colLocked] = SP_IS_ITEM(layer) ? SP_ITEM(layer)->isLocked() : false;
@@ -381,7 +381,7 @@ void LayersPanel::_addLayer( SPDocument* doc, SPObject* layer, Gtk::TreeModel::R
Gtk::TreeModel::iterator iter = parentRow ? _store->prepend(parentRow->children()) : _store->prepend();
Gtk::TreeModel::Row row = *iter;
row[_model->_colObject] = child;
- row[_model->_colLabel] = child->label() ? child->label() : SP_OBJECT_ID(child);
+ row[_model->_colLabel] = child->label() ? child->label() : child->getId();
row[_model->_colVisible] = SP_IS_ITEM(child) ? !SP_ITEM(child)->isHidden() : false;
row[_model->_colLocked] = SP_IS_ITEM(child) ? SP_ITEM(child)->isLocked() : false;
diff --git a/src/ui/dialog/spray-option.cpp b/src/ui/dialog/spray-option.cpp
deleted file mode 100644
index a9e037381..000000000
--- a/src/ui/dialog/spray-option.cpp
+++ /dev/null
@@ -1,397 +0,0 @@
-/*Julien LERAY (julien.leray@ecl2010.ec-lyon.fr), interface for the spray tool*/
-
-
-#ifdef HAVE_CONFIG_H
-# include <config.h>
-#endif
-
-#include <gtkmm/spinbutton.h>
-
-#include "desktop-handles.h"
-#include "unclump.h"
-#include "document.h"
-#include "enums.h"
-#include "graphlayout/graphlayout.h"
-#include "inkscape.h"
-#include "macros.h"
-#include "node-context.h"
-#include "preferences.h"
-#include "removeoverlap/removeoverlap.h"
-#include "selection.h"
-#include "shape-editor.h"
-#include "sp-flowtext.h"
-#include "sp-item-transform.h"
-#include "sp-text.h"
-#include "text-editing.h"
-#include "tools-switch.h"
-#include "ui/icon-names.h"
-#include "util/glib-list-iterators.h"
-#include "verbs.h"
-#include "widgets/icon.h"
-
-#include "spray-option.h"
-
-namespace Inkscape {
-namespace UI {
-namespace Dialog {
-
-
-//Classes qui permettent de créer les environnements Gaussienne, Witdh...
-
-
-
-class Action {
-public:
- Action(const Glib::ustring &id,
- const Glib::ustring &/*tiptext*/,
- guint /*row*/,
- guint /*column*/,
- Gtk::Table &parent,
- Gtk::Tooltips &/*tooltips*/,
- SprayOptionClass &dialog):
- _dialog(dialog),
- _id(id),
- _parent(parent) {}
-
- virtual ~Action(){}
- virtual void on_button_click(){}
- SprayOptionClass &_dialog;
-
-private :
-
- Glib::ustring _id;
- Gtk::Table &_parent;
-};
-
-class ActionE : public Action {
-private:
- Gtk::Label _Label;
- Gtk::SpinButton _Gap;
- guint _min, _max;
- Glib::ustring _pref_path;
-
-public:
- ActionE(const Glib::ustring &id,
- const Glib::ustring &tiptext,
- guint row, guint column,
- SprayOptionClass &dialog,
- guint min, guint max,
- Glib::ustring const &pref_path ):
- Action(id, tiptext, row, column,
- dialog._Table(), dialog.tooltips(), dialog),
- _min(min),
- _max(max),
- _pref_path(pref_path)
- {
- dialog._Table().set_col_spacings(3);
-
- double increm = ((double)_max - (double)_min)/10;
- double val_ini = ((double)_max + (double)_min)/2;
- _Gap.set_digits(1);
- _Gap.set_size_request(60, -1);
- _Gap.set_increments(increm , 0);
- _Gap.set_range(_min, _max);
- _Gap.set_value(val_ini);
- dialog.tooltips().set_tip(_Gap,
- tiptext);
- _Gap.signal_changed().connect(sigc::mem_fun(*this, &ActionE::on_button_click)); //rajout douteux
- _Label.set_label(id);
-
- dialog._Table().attach(_Label, column, column+1, row, row+1, Gtk::FILL, Gtk::FILL);
- dialog._Table().attach(_Gap, column+1, column+2, row, row+1, Gtk::EXPAND, Gtk::EXPAND);
- }
-
- virtual void on_button_click(){
- if (!_dialog.getDesktop()) return;
-
- Inkscape::Preferences *prefs = Inkscape::Preferences::get();
-
- prefs->setDouble(_pref_path, SP_VERB_CONTEXT_SPRAY);
-
- double const Gap = _Gap.get_value();
-
-
- prefs->setDouble(_pref_path, Gap);
-
- sp_document_done(sp_desktop_document(_dialog.getDesktop()), SP_VERB_CONTEXT_SPRAY,
- _("Remove overlaps"));
- }
-
-
-};
-
-class ActionF : public Action {
-private:
- Gtk::Label _Label;
- Gtk::Label _Label1;
- Gtk::Label _Label2;
- Gtk::SpinButton _Gap1;
- Gtk::SpinButton _Gap2;
- Glib::ustring _pref1_path;
- Glib::ustring _pref2_path;
-
-public:
- ActionF(const Glib::ustring &id,
- const Glib::ustring &tiptext,
- guint row, guint column,
- SprayOptionClass &dialog,
- Glib::ustring const &pref1_path,
- Glib::ustring const &pref2_path ):
- Action(id, tiptext, row, column,
- dialog._Table(), dialog.tooltips(), dialog),
- _pref1_path(pref1_path),
- _pref2_path(pref2_path)
- {
- dialog.F_Table().set_col_spacings(3);
-
- _Label.set_label(id);
-
- _Gap1.set_digits(1);
- _Gap1.set_size_request(60, -1);
- _Gap1.set_increments(0.1, 0);
- _Gap1.set_range(0, 10);
- _Gap1.set_value(1);
- dialog.tooltips().set_tip(_Gap1,
- _("Minimum"));
-
- _Label1.set_label(Q_("Min"));
-
- _Gap2.set_digits(1);
- _Gap2.set_size_request(60, -1);
- _Gap2.set_increments(0.1, 0);
- _Gap2.set_range(0, 10);
- _Gap2.set_value(1);
- dialog.tooltips().set_tip(_Gap2,
- _("Maximum"));
-
- _Label2.set_label(_("Max:"));
-
- _Gap1.signal_changed().connect(sigc::mem_fun(*this, &ActionF::on_button_click));
- _Gap2.signal_changed().connect(sigc::mem_fun(*this, &ActionF::on_button_click));
-
- dialog.F_Table().attach(_Label, column, column+1, row, row+1, Gtk::FILL, Gtk::FILL);
- dialog.F_Table().attach(_Label1, column+1, column+2, row, row+1, Gtk::FILL, Gtk::FILL);
- dialog.F_Table().attach(_Gap1, column+2, column+3, row, row+1, Gtk::EXPAND, Gtk::EXPAND);
- dialog.F_Table().attach(_Label2, column+3, column+4, row, row+1, Gtk::FILL, Gtk::FILL);
- dialog.F_Table().attach(_Gap2, column+4, column+5, row, row+1, Gtk::EXPAND, Gtk::EXPAND);
-
- }
-
- virtual void on_button_click(){
- if (!_dialog.getDesktop()) return;
-
- Inkscape::Preferences *prefs = Inkscape::Preferences::get();
-
- prefs->setDouble(_pref1_path, SP_VERB_CONTEXT_SPRAY);
- prefs->setDouble(_pref2_path, SP_VERB_CONTEXT_SPRAY);
-
- double const Gap1 = _Gap1.get_value();
- double const Gap2 = _Gap2.get_value();
-
- prefs->setDouble(_pref1_path, Gap1);
- prefs->setDouble(_pref2_path, Gap2);
-
- sp_document_done(sp_desktop_document(_dialog.getDesktop()), SP_VERB_CONTEXT_SPRAY,
- _("Remove overlaps"));
- }
-
-
-};
-
-
-
-void SprayOptionClass::combo_action() {
- Inkscape::Preferences *prefs = Inkscape::Preferences::get();
- cout<<"combo.get_active_row_number = "<<_combo.get_active_row_number()<<endl;
-
- int const distrib = _combo.get_active_row_number();
-
- prefs->setInt("/tools/spray/distribution", distrib);
-
-
- sp_document_done(sp_desktop_document(this->getDesktop()), SP_VERB_CONTEXT_SPRAY,
- _("Remove overlaps"));
-
-}
-
-
-
-
-void SprayOptionClass::action() {
- for (list<Action *>::iterator it = _actionList.begin(); it != _actionList.end(); ++it) {
- (*it)->on_button_click();
- }
- combo_action();
-}
-
-
-
-
-
-
-void on_selection_changed(Inkscape::Application */*inkscape*/, Inkscape::Selection */*selection*/, SprayOptionClass *daad)
-{
- daad->randomize_bbox = Geom::OptRect();
-}
-
-/////////////////////////////////////////////////////////
-//Construction de l'interface
-/////////////////////////////////////////////////////////
-
-
-SprayOptionClass::SprayOptionClass()
- : UI::Widget::Panel ("", "/dialogs/spray", SP_VERB_DIALOG_SPRAY_OPTION),
- _actionList(),
- _distributionFrame(Q_("sprayOptions|Distribution")),
- _Frame(Q_("sprayOptions|Cursor Options")),
- _FFrame(Q_("sprayOptions|Random Options")),
- _distributionTable(),
- _gaussianTable(1, 5, false),
- _ETable(3,2,false),
- _FTable(2,5,false),
- _anchorBox(),
- _unifBox(),
- _gaussianBox(),
- _HBox(),
- _FHBox(),
- _BoutonBox(),
- _distributionBox(),
- _VBox(),
- _FVBox(),
- _ActionBox(),
- _anchorLabel(Q_("sprayOptions|Distribution:")),
- _unifLabel(Q_("sprayOptions|Uniform")),
- _gaussLabel(Q_("sprayOptions|Gaussian")),
- _Label(),
- _FLabel(),
- _unif(),
- _gauss(),
- _combo(),
- _tooltips()
-{
- Inkscape::Preferences *prefs = Inkscape::Preferences::get();
-
- //ComboBoxText
-
- _combo.append_text(Q_("sprayOptions|Uniform"));
- _combo.append_text(Q_("sprayOptions|Gaussian"));
-
- _combo.set_active(prefs->getInt("/tools/spray/distribution", 1));
- _combo.signal_changed().connect(sigc::mem_fun(*this, &SprayOptionClass::combo_action));
-
- _anchorBox.pack_start(_anchorLabel);
- _anchorBox.pack_start(_combo);
-
- _gaussianBox.pack_start(_anchorBox);
-
-
- _distributionBox.pack_start(_gaussianBox);
- _distributionFrame.add(_distributionBox);
-
-
- //Hbox Random
- addFButton(Q_("sprayOptions|Scale:") ,_("Apply a scale factor"), 0, 0, "/tools/spray/scale_min","/tools/spray/scale_max");
- addFButton(Q_("sprayOptions|Rotation:") ,_("Apply rotation"), 1, 0, "/tools/spray/rot_min","/tools/spray/rot_max");
- _FHBox.pack_start(_FLabel);
- _FHBox.pack_start(_FTable);
-
- //Implementation dans la Vbox Cursor
- _FVBox.pack_start(_FHBox);
- _FFrame.add(_FVBox);
-
- //Hbox Cursor
- addEButton(Q_("sprayOptions|Ratio:") ,_("Eccentricity of the ellipse"), 0, 0, 0, 1,"/tools/spray/ratio");
- addEButton(Q_("sprayOptions|Angle:") ,_("Angle of the ellipse"), 1, 0, 0, 5,"/tools/spray/tilt");
- addEButton(Q_("sprayOptions|Width:") ,_("Size of the ellipse"), 2, 0, 0, 1,"/tools/spray/width");
- _HBox.pack_start(_Label);
- _HBox.pack_start(_ETable);
-
- //Implementation dans la Vbox Cursor
- _VBox.pack_start(_HBox);
- _Frame.add(_VBox);
-
- Gtk::Box *contents = _getContents();
- contents->set_spacing(4);
-
-
-
-
-
-
- // Crée dans l'ordre suivant les différentes Frames (cadres de réglages)
-
- contents->pack_start(_distributionFrame, true, true);
- contents->pack_start(_FFrame, true, true);
- contents->pack_start(_Frame, true, true);
-
-
-
- // Connect to the global selection change, to invalidate cached randomize_bbox
- g_signal_connect (G_OBJECT (INKSCAPE), "change_selection", G_CALLBACK (on_selection_changed), this);
- randomize_bbox = Geom::OptRect();
-
- show_all_children();
-
-
-
-}
-
-SprayOptionClass::~SprayOptionClass()
-{
- sp_signal_disconnect_by_data (G_OBJECT (INKSCAPE), this);
-
- for (std::list<Action *>::iterator it = _actionList.begin();
- it != _actionList.end();
- it ++)
- delete *it;
-}
-
-
-
-
-
-
-
-//Fonctions qui lient la demande d'ajout d'une interface graphique à l'action correspondante
-
-void SprayOptionClass::addEButton(const Glib::ustring &id,
- const Glib::ustring &tiptext,
- guint row, guint column,
- guint min, guint max,
- Glib::ustring const &pref_path)
-{
- _actionList.push_back( new ActionE(id, tiptext,row, column,*this,min ,max, pref_path ));
-}
-
-void SprayOptionClass::addFButton(const Glib::ustring &id,
- const Glib::ustring &tiptext,
- guint row, guint column,
- Glib::ustring const &pref1_path,
- Glib::ustring const &pref2_path)
-{
- _actionList.push_back( new ActionF(id, tiptext,row, column,*this,pref1_path, pref2_path ));
-}
-
-
-
-
-
-SprayOptionClass &SprayOptionClass::get_SprayOptionClass()
-{
- return *this;
-}
-
-} // 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:encoding=utf-8:textwidth=99 :
diff --git a/src/ui/dialog/spray-option.h b/src/ui/dialog/spray-option.h
deleted file mode 100644
index 42090a120..000000000
--- a/src/ui/dialog/spray-option.h
+++ /dev/null
@@ -1,145 +0,0 @@
-
-/*Julien LERAY (julien.leray@ecl2010.ec-lyon.fr), interface for the spray tool*/
-
-#ifndef INKSCAPE_UI_DIALOG_SPRAY_OPTION_H
-#define INKSCAPE_UI_DIALOG_SPRAY_OPTION_H
-
-#include <gtkmm/notebook.h>
-#include <glibmm/i18n.h>
-
-#include <list>
-#include <gtkmm/frame.h>
-#include <gtkmm/tooltips.h>
-#include <gtkmm/comboboxtext.h>
-#include <gtkmm/table.h>
-#include <gtkmm/buttonbox.h>
-#include <gtkmm/label.h>
-#include "libnr/nr-dim2.h"
-#include "libnr/nr-rect.h"
-
-
-#include "ui/widget/panel.h"
-#include "ui/widget/notebook-page.h"
-
-#ifdef HAVE_CONFIG_H
-# include <config.h>
-#endif
-
-#include <gtkmm/spinbutton.h>
-#include "desktop-handles.h"
-#include "unclump.h"
-#include "document.h"
-#include "enums.h"
-#include "graphlayout/graphlayout.h"
-#include "inkscape.h"
-#include "macros.h"
-#include "node-context.h"
-#include "preferences.h"
-#include "removeoverlap/removeoverlap.h"
-#include "selection.h"
-#include "shape-editor.h"
-#include "sp-flowtext.h"
-#include "sp-item-transform.h"
-#include "sp-text.h"
-#include "text-editing.h"
-#include "tools-switch.h"
-#include "ui/icon-names.h"
-#include "util/glib-list-iterators.h"
-#include "verbs.h"
-#include "widgets/icon.h"
-
-#include "spray-context.h"
-#include "verbs.h"
-
-#include <iostream>
-using namespace std;
-
-using namespace Inkscape::UI::Widget;
-
-class SPItem;
-
-
-namespace Inkscape {
-namespace UI {
-namespace Dialog {
-
-class Action;
-
-class SprayOptionClass : public Widget::Panel {
-
-private:
-
- SprayOptionClass(SprayOptionClass const &d);
- SprayOptionClass& operator=(SprayOptionClass const &d);
-
-public:
- SprayOptionClass();
- virtual ~SprayOptionClass();
- void test() { cout<<"appel de test !!"<<endl; }
- static SprayOptionClass &getInstance() { return *new SprayOptionClass(); }
-
-
- Gtk::Table &_Table(){return _ETable;}
- Gtk::Table &F_Table(){return _FTable;}
- Gtk::Tooltips &tooltips(){return _tooltips;}
- void action();
- void combo_action();
- Geom::OptRect randomize_bbox;
-
- SprayOptionClass &get_SprayOptionClass();
-
-protected:
-
- void addGaussianButton(guint row, guint col);
- void addEButton(const Glib::ustring &id, const Glib::ustring &tiptext, guint row, guint column,
- guint min, guint max, const Glib::ustring &pref_path);
- void addFButton(const Glib::ustring &id, const Glib::ustring &tiptext, guint row, guint column,
- const Glib::ustring &pref1_path, const Glib::ustring &pref2_path);
-
- std::list<Action *> _actionList;
- Gtk::Frame _distributionFrame;
- Gtk::Frame _Frame;
- Gtk::Frame _FFrame;
- Gtk::Table _distributionTable;
- Gtk::Table _gaussianTable;
- Gtk::Table _ETable;
- Gtk::Table _FTable;
- Gtk::HBox _anchorBox;
- Gtk::HBox _unifBox;
- Gtk::HBox _gaussianBox;
- Gtk::HBox _HBox;
- Gtk::HBox _FHBox;
- Gtk::HBox _BoutonBox;
- Gtk::VBox _distributionBox;
- Gtk::VBox _VBox;
- Gtk::VBox _FVBox;
- Gtk::VBox _ActionBox;
- Gtk::Label _anchorLabel;
- Gtk::Label _unifLabel;
- Gtk::Label _gaussLabel;
- Gtk::Label _Label;
- Gtk::Label _FLabel;
- Gtk::CheckButton _unif;
- Gtk::CheckButton _gauss;
- Gtk::ComboBoxText _combo;
- Gtk::Tooltips _tooltips;
-};
-
-
-} // namespace Dialog
-} // namespace UI
-} // namespace Inkscape
-
-#endif // INKSCAPE_UI_DIALOG_ALIGN_AND_DISTRIBUTE_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:encoding=utf-8:textwidth=99 :
-
diff --git a/src/ui/dialog/svg-fonts-dialog.cpp b/src/ui/dialog/svg-fonts-dialog.cpp
index cb22e029b..998f4e1e1 100644
--- a/src/ui/dialog/svg-fonts-dialog.cpp
+++ b/src/ui/dialog/svg-fonts-dialog.cpp
@@ -252,7 +252,7 @@ void SvgFontsDialog::update_fonts()
row[_columns.spfont] = f;
row[_columns.svgfont] = new SvgFont(f);
const gchar* lbl = f->label();
- const gchar* id = SP_OBJECT_ID(f);
+ const gchar* id = f->getId();
row[_columns.label] = lbl ? lbl : (id ? id : "font");
}
@@ -653,7 +653,7 @@ Gtk::VBox* SvgFontsDialog::glyphs_tab(){
missing_glyph_button.signal_clicked().connect(sigc::mem_fun(*this, &SvgFontsDialog::missing_glyph_description_from_selected_path));
missing_glyph_reset_button.set_label(_("Reset"));
missing_glyph_reset_button.signal_clicked().connect(sigc::mem_fun(*this, &SvgFontsDialog::reset_missing_glyph_description));
-
+
glyphs_vbox.pack_start(*missing_glyph_hbox, false,false);
glyphs_vbox.add(_GlyphsListScroller);
diff --git a/src/ui/dialog/swatches.cpp b/src/ui/dialog/swatches.cpp
index 450d4202d..6f013f4f3 100644
--- a/src/ui/dialog/swatches.cpp
+++ b/src/ui/dialog/swatches.cpp
@@ -1,3 +1,4 @@
+
/** @file
* @brief Color swatches dialog
*/
@@ -22,6 +23,7 @@
#include <glibmm/i18n.h>
#include <gdkmm/pixbuf.h>
+#include "color-item.h"
#include "desktop.h"
#include "desktop-handles.h"
#include "desktop-style.h"
@@ -36,278 +38,36 @@
#include "path-prefix.h"
#include "preferences.h"
#include "sp-item.h"
-#include "svg/svg-color.h"
#include "sp-gradient-fns.h"
#include "sp-gradient.h"
#include "sp-gradient-vector.h"
#include "swatches.h"
#include "style.h"
+#include "ui/previewholder.h"
#include "widgets/gradient-vector.h"
#include "widgets/eek-preview.h"
#include "display/nr-plain-stuff.h"
#include "sp-gradient-reference.h"
-#define USE_DOCUMENT_PALETTE 1
namespace Inkscape {
namespace UI {
namespace Dialogs {
#define VBLOCK 16
+#define PREVIEW_PIXBUF_WIDTH 128
void _loadPaletteFile( gchar const *filename );
-/**
- * The color swatch you see on screen as a clickable box.
- */
-class ColorItem : public Inkscape::UI::Previewable
-{
- friend void _loadPaletteFile( gchar const *filename );
-public:
- ColorItem( ege::PaintDef::ColorType type );
- ColorItem( unsigned int r, unsigned int g, unsigned int b,
- Glib::ustring& name );
- virtual ~ColorItem();
- ColorItem(ColorItem const &other);
- virtual ColorItem &operator=(ColorItem const &other);
- virtual Gtk::Widget* getPreview(PreviewStyle style,
- ViewType view,
- ::PreviewSize size,
- guint ratio);
- void buttonClicked(bool secondary = false);
-
- void setState( bool fill, bool stroke );
- bool isFill() { return _isFill; }
- bool isStroke() { return _isStroke; }
-
- ege::PaintDef def;
- void* ptr;
-
-private:
- static void _dropDataIn( GtkWidget *widget,
- GdkDragContext *drag_context,
- gint x, gint y,
- GtkSelectionData *data,
- guint info,
- guint event_time,
- gpointer user_data);
-
- static void _dragGetColorData( GtkWidget *widget,
- GdkDragContext *drag_context,
- GtkSelectionData *data,
- guint info,
- guint time,
- gpointer user_data);
-
- static void _wireMagicColors( void* p );
- static void _colorDefChanged(void* data);
-
- void _linkTint( ColorItem& other, int percent );
- void _linkTone( ColorItem& other, int percent, int grayLevel );
-
- Gtk::Tooltips tips;
- std::vector<Gtk::Widget*> _previews;
-
- bool _isFill;
- bool _isStroke;
- bool _isLive;
- bool _linkIsTone;
- int _linkPercent;
- int _linkGray;
- ColorItem* _linkSrc;
- std::vector<ColorItem*> _listeners;
-};
-
-
-
-ColorItem::ColorItem(ege::PaintDef::ColorType type) :
- def(type),
- ptr(0),
- _isFill(false),
- _isStroke(false),
- _isLive(false),
- _linkIsTone(false),
- _linkPercent(0),
- _linkGray(0),
- _linkSrc(0)
-{
-}
-
-ColorItem::ColorItem( unsigned int r, unsigned int g, unsigned int b, Glib::ustring& name ) :
- def( r, g, b, name ),
- ptr(0),
- _isFill(false),
- _isStroke(false),
- _isLive(false),
- _linkIsTone(false),
- _linkPercent(0),
- _linkGray(0),
- _linkSrc(0)
-{
-}
-
-ColorItem::~ColorItem()
-{
-}
-
-ColorItem::ColorItem(ColorItem const &other) :
- Inkscape::UI::Previewable()
-{
- if ( this != &other ) {
- *this = other;
- }
-}
-
-ColorItem &ColorItem::operator=(ColorItem const &other)
-{
- if ( this != &other ) {
- def = other.def;
-
- // TODO - correct linkage
- _linkSrc = other._linkSrc;
- g_message("Erk!");
- }
- return *this;
-}
-
-void ColorItem::setState( bool fill, bool stroke )
-{
- if ( (_isFill != fill) || (_isStroke != stroke) ) {
- _isFill = fill;
- _isStroke = stroke;
-
- for ( std::vector<Gtk::Widget*>::iterator it = _previews.begin(); it != _previews.end(); ++it ) {
- Gtk::Widget* widget = *it;
- if ( IS_EEK_PREVIEW(widget->gobj()) ) {
- EekPreview * preview = EEK_PREVIEW(widget->gobj());
-
- int val = eek_preview_get_linked( preview );
- val &= ~(PREVIEW_FILL | PREVIEW_STROKE);
- if ( _isFill ) {
- val |= PREVIEW_FILL;
- }
- if ( _isStroke ) {
- val |= PREVIEW_STROKE;
- }
- eek_preview_set_linked( preview, static_cast<LinkType>(val) );
- }
- }
- }
-}
-
-
-class JustForNow
-{
-public:
- JustForNow() : _prefWidth(0) {}
-
- Glib::ustring _name;
- int _prefWidth;
- std::vector<ColorItem*> _colors;
-};
-
-static std::vector<JustForNow*> possible;
+class DocTrack;
-static std::vector<std::string> mimeStrings;
-static std::map<std::string, guint> mimeToInt;
+std::vector<SwatchPage*> possible;
+static std::map<SPDocument*, SwatchPage*> docPalettes;
+static std::vector<DocTrack*> docTrackings;
+static std::map<SwatchesPanel*, SPDocument*> docPerPanel;
-static std::map<ColorItem*, guchar*> previewMap;
-static std::map<ColorItem*, SPGradient*> gradMap; // very temporary workaround.
-void ColorItem::_dragGetColorData( GtkWidget */*widget*/,
- GdkDragContext */*drag_context*/,
- GtkSelectionData *data,
- guint info,
- guint /*time*/,
- gpointer user_data)
-{
- ColorItem* item = reinterpret_cast<ColorItem*>(user_data);
- std::string key;
- if ( info < mimeStrings.size() ) {
- key = mimeStrings[info];
- } else {
- g_warning("ERROR: unknown value (%d)", info);
- }
-
- if ( !key.empty() ) {
- char* tmp = 0;
- int len = 0;
- int format = 0;
- item->def.getMIMEData(key, tmp, len, format);
- if ( tmp ) {
- GdkAtom dataAtom = gdk_atom_intern( key.c_str(), FALSE );
- gtk_selection_data_set( data, dataAtom, format, (guchar*)tmp, len );
- delete[] tmp;
- }
- }
-}
-
-static void dragBegin( GtkWidget */*widget*/, GdkDragContext* dc, gpointer data )
-{
- ColorItem* item = reinterpret_cast<ColorItem*>(data);
- if ( item )
- {
- using Inkscape::IO::Resource::get_path;
- using Inkscape::IO::Resource::ICONS;
- using Inkscape::IO::Resource::SYSTEM;
- int width = 32;
- int height = 24;
-
- if (item->def.getType() != ege::PaintDef::RGB){
- GError *error = NULL;
- gsize bytesRead = 0;
- gsize bytesWritten = 0;
- gchar *localFilename = g_filename_from_utf8( get_path(SYSTEM, ICONS, "remove-color.png"),
- -1,
- &bytesRead,
- &bytesWritten,
- &error);
- GdkPixbuf* pixbuf = gdk_pixbuf_new_from_file_at_scale(localFilename, width, height, FALSE, &error);
- g_free(localFilename);
- gtk_drag_set_icon_pixbuf( dc, pixbuf, 0, 0 );
- } else {
- GdkPixbuf* pixbuf = 0;
- if ( gradMap.find(item) == gradMap.end() ){
- Glib::RefPtr<Gdk::Pixbuf> thumb = Gdk::Pixbuf::create( Gdk::COLORSPACE_RGB, false, 8, width, height );
- guint32 fillWith = (0xff000000 & (item->def.getR() << 24))
- | (0x00ff0000 & (item->def.getG() << 16))
- | (0x0000ff00 & (item->def.getB() << 8));
- thumb->fill( fillWith );
- pixbuf = thumb->gobj();
- } else {
- SPGradient* grad = gradMap[item];
-
- guchar* px = g_new( guchar, 3 * height * width );
- nr_render_checkerboard_rgb( px, width, height, 3 * width, 0, 0 );
-
- sp_gradient_render_vector_block_rgb( grad,
- px, width, height, 3 * width,
- 0, width, TRUE );
-
- pixbuf = gdk_pixbuf_new_from_data( px, GDK_COLORSPACE_RGB, FALSE, 8,
- width, height, width * 3,
- 0, // add delete function
- 0 );
- }
- gtk_drag_set_icon_pixbuf( dc, pixbuf, 0, 0 );
- }
- }
-
-}
-
-//"drag-drop"
-// gboolean dragDropColorData( GtkWidget *widget,
-// GdkDragContext *drag_context,
-// gint x,
-// gint y,
-// guint time,
-// gpointer user_data)
-// {
-// // TODO finish
-
-// return TRUE;
-// }
static void handleClick( GtkWidget* /*widget*/, gpointer callback_data ) {
ColorItem* item = reinterpret_cast<ColorItem*>(callback_data);
@@ -323,34 +83,10 @@ static void handleSecondaryClick( GtkWidget* /*widget*/, gint /*arg1*/, gpointer
}
}
-static gboolean handleEnterNotify( GtkWidget* /*widget*/, GdkEventCrossing* /*event*/, gpointer callback_data ) {
- ColorItem* item = reinterpret_cast<ColorItem*>(callback_data);
- if ( item ) {
- SPDesktop *desktop = SP_ACTIVE_DESKTOP;
- if ( desktop ) {
- gchar* msg = g_strdup_printf(_("Color: <b>%s</b>; <b>Click</b> to set fill, <b>Shift+click</b> to set stroke"),
- item->def.descr.c_str());
- desktop->tipsMessageContext()->set(Inkscape::INFORMATION_MESSAGE, msg);
- g_free(msg);
- }
- }
- return FALSE;
-}
-
-static gboolean handleLeaveNotify( GtkWidget* /*widget*/, GdkEventCrossing* /*event*/, gpointer callback_data ) {
- ColorItem* item = reinterpret_cast<ColorItem*>(callback_data);
- if ( item ) {
- SPDesktop *desktop = SP_ACTIVE_DESKTOP;
- if ( desktop ) {
- desktop->tipsMessageContext()->clear();
- }
- }
- return FALSE;
-}
-
static GtkWidget* popupMenu = 0;
static std::vector<GtkWidget*> popupExtras;
static ColorItem* bounceTarget = 0;
+static SwatchesPanel* bouncePanel = 0;
static void redirClick( GtkMenuItem *menuitem, gpointer /*user_data*/ )
{
@@ -366,7 +102,6 @@ static void redirSecondaryClick( GtkMenuItem *menuitem, gpointer /*user_data*/ )
}
}
-#if USE_DOCUMENT_PALETTE
static void editGradientImpl( SPGradient* gr )
{
if ( gr ) {
@@ -378,7 +113,7 @@ static void editGradientImpl( SPGradient* gr )
static void editGradient( GtkMenuItem */*menuitem*/, gpointer /*user_data*/ )
{
if ( bounceTarget ) {
- SwatchesPanel* swp = bounceTarget->ptr ? reinterpret_cast<SwatchesPanel*>(bounceTarget->ptr) : 0;
+ SwatchesPanel* swp = bouncePanel;
SPDesktop* desktop = swp ? swp->getDesktop() : 0;
SPDocument *doc = desktop ? desktop->doc() : 0;
if (doc) {
@@ -386,7 +121,7 @@ static void editGradient( GtkMenuItem */*menuitem*/, gpointer /*user_data*/ )
const GSList *gradients = sp_document_get_resource_list(doc, "gradient");
for (const GSList *item = gradients; item; item = item->next) {
SPGradient* grad = SP_GRADIENT(item->data);
- if ( targetName == grad->id ) {
+ if ( targetName == grad->getId() ) {
editGradientImpl( grad );
break;
}
@@ -398,7 +133,7 @@ static void editGradient( GtkMenuItem */*menuitem*/, gpointer /*user_data*/ )
static void addNewGradient( GtkMenuItem */*menuitem*/, gpointer /*user_data*/ )
{
if ( bounceTarget ) {
- SwatchesPanel* swp = bounceTarget->ptr ? reinterpret_cast<SwatchesPanel*>(bounceTarget->ptr) : 0;
+ SwatchesPanel* swp = bouncePanel;
SPDesktop* desktop = swp ? swp->getDesktop() : 0;
SPDocument *doc = desktop ? desktop->doc() : 0;
if (doc) {
@@ -420,20 +155,35 @@ static void addNewGradient( GtkMenuItem */*menuitem*/, gpointer /*user_data*/ )
editGradientImpl( gr );
- // Work-around for timing of gradient addition change. Must follow edit.
- if ( swp ) {
- swp->handleGradientsChange();
- }
}
}
}
-#endif // USE_DOCUMENT_PALETTE
-static gboolean handleButtonPress( GtkWidget* /*widget*/, GdkEventButton* event, gpointer user_data)
+static SwatchesPanel* findContainingPanel( GtkWidget *widget )
+{
+ SwatchesPanel *swp = 0;
+
+ std::map<GtkWidget*, SwatchesPanel*> rawObjects;
+ for (std::map<SwatchesPanel*, SPDocument*>::iterator it = docPerPanel.begin(); it != docPerPanel.end(); ++it) {
+ rawObjects[GTK_WIDGET(it->first->gobj())] = it->first;
+ }
+
+ for (GtkWidget* curr = widget; curr && !swp; curr = gtk_widget_get_parent(curr)) {
+ if (rawObjects.find(curr) != rawObjects.end()) {
+ swp = rawObjects[curr];
+ }
+ }
+
+ return swp;
+}
+
+gboolean colorItemHandleButtonPress( GtkWidget* widget, GdkEventButton* event, gpointer user_data)
{
gboolean handled = FALSE;
if ( (event->button == 3) && (event->type == GDK_BUTTON_PRESS) ) {
+ SwatchesPanel* swp = findContainingPanel( widget );
+
if ( !popupMenu ) {
popupMenu = gtk_menu_new();
GtkWidget* child = 0;
@@ -455,7 +205,6 @@ static gboolean handleButtonPress( GtkWidget* /*widget*/, GdkEventButton* event,
user_data);
gtk_menu_shell_append(GTK_MENU_SHELL(popupMenu), child);
-#if USE_DOCUMENT_PALETTE
child = gtk_separator_menu_item_new();
gtk_menu_shell_append(GTK_MENU_SHELL(popupMenu), child);
popupExtras.push_back(child);
@@ -489,20 +238,19 @@ static gboolean handleButtonPress( GtkWidget* /*widget*/, GdkEventButton* event,
gtk_menu_shell_append(GTK_MENU_SHELL(popupMenu), child);
//popupExtras.push_back(child);
gtk_widget_set_sensitive( child, FALSE );
-#endif // USE_DOCUMENT_PALETTE
gtk_widget_show_all(popupMenu);
}
ColorItem* item = reinterpret_cast<ColorItem*>(user_data);
if ( item ) {
- SwatchesPanel* swp = item->ptr ? reinterpret_cast<SwatchesPanel*>(item->ptr) : 0;
bool show = swp && (swp->getSelectedIndex() == 0);
for ( std::vector<GtkWidget*>::iterator it = popupExtras.begin(); it != popupExtras.end(); ++ it) {
gtk_widget_set_sensitive(*it, show);
}
bounceTarget = item;
+ bouncePanel = swp;
if ( popupMenu ) {
gtk_menu_popup(GTK_MENU(popupMenu), NULL, NULL, NULL, NULL, event->button, event->time);
handled = TRUE;
@@ -513,374 +261,6 @@ static gboolean handleButtonPress( GtkWidget* /*widget*/, GdkEventButton* event,
return handled;
}
-static void dieDieDie( GtkObject *obj, gpointer user_data )
-{
- g_message("die die die %p %p", obj, user_data );
-}
-
-#include "color.h" // for SP_RGBA32_U_COMPOSE
-
-void ColorItem::_dropDataIn( GtkWidget */*widget*/,
- GdkDragContext */*drag_context*/,
- gint /*x*/, gint /*y*/,
- GtkSelectionData */*data*/,
- guint /*info*/,
- guint /*event_time*/,
- gpointer /*user_data*/)
-{
-}
-
-static bool bruteForce( SPDocument* document, Inkscape::XML::Node* node, Glib::ustring const& match, int r, int g, int b )
-{
- bool changed = false;
-
- if ( node ) {
- gchar const * val = node->attribute("inkscape:x-fill-tag");
- if ( val && (match == val) ) {
- SPObject *obj = document->getObjectByRepr( node );
-
- gchar c[64] = {0};
- sp_svg_write_color( c, sizeof(c), SP_RGBA32_U_COMPOSE( r, g, b, 0xff ) );
- SPCSSAttr *css = sp_repr_css_attr_new();
- sp_repr_css_set_property( css, "fill", c );
-
- sp_desktop_apply_css_recursive( (SPItem*)obj, css, true );
- ((SPItem*)obj)->updateRepr();
-
- changed = true;
- }
-
- val = node->attribute("inkscape:x-stroke-tag");
- if ( val && (match == val) ) {
- SPObject *obj = document->getObjectByRepr( node );
-
- gchar c[64] = {0};
- sp_svg_write_color( c, sizeof(c), SP_RGBA32_U_COMPOSE( r, g, b, 0xff ) );
- SPCSSAttr *css = sp_repr_css_attr_new();
- sp_repr_css_set_property( css, "stroke", c );
-
- sp_desktop_apply_css_recursive( (SPItem*)obj, css, true );
- ((SPItem*)obj)->updateRepr();
-
- changed = true;
- }
-
- Inkscape::XML::Node* first = node->firstChild();
- changed |= bruteForce( document, first, match, r, g, b );
-
- changed |= bruteForce( document, node->next(), match, r, g, b );
- }
-
- return changed;
-}
-
-void ColorItem::_colorDefChanged(void* data)
-{
- ColorItem* item = reinterpret_cast<ColorItem*>(data);
- if ( item ) {
- for ( std::vector<Gtk::Widget*>::iterator it = item->_previews.begin(); it != item->_previews.end(); ++it ) {
- Gtk::Widget* widget = *it;
- if ( IS_EEK_PREVIEW(widget->gobj()) ) {
- EekPreview * preview = EEK_PREVIEW(widget->gobj());
- eek_preview_set_color( preview,
- (item->def.getR() << 8) | item->def.getR(),
- (item->def.getG() << 8) | item->def.getG(),
- (item->def.getB() << 8) | item->def.getB() );
-
- eek_preview_set_linked( preview, (LinkType)((item->_linkSrc ? PREVIEW_LINK_IN:0)
- | (item->_listeners.empty() ? 0:PREVIEW_LINK_OUT)
- | (item->_isLive ? PREVIEW_LINK_OTHER:0)) );
-
- widget->queue_draw();
- }
- }
-
- for ( std::vector<ColorItem*>::iterator it = item->_listeners.begin(); it != item->_listeners.end(); ++it ) {
- guint r = item->def.getR();
- guint g = item->def.getG();
- guint b = item->def.getB();
-
- if ( (*it)->_linkIsTone ) {
- r = ( ((*it)->_linkPercent * (*it)->_linkGray) + ((100 - (*it)->_linkPercent) * r) ) / 100;
- g = ( ((*it)->_linkPercent * (*it)->_linkGray) + ((100 - (*it)->_linkPercent) * g) ) / 100;
- b = ( ((*it)->_linkPercent * (*it)->_linkGray) + ((100 - (*it)->_linkPercent) * b) ) / 100;
- } else {
- r = ( ((*it)->_linkPercent * 255) + ((100 - (*it)->_linkPercent) * r) ) / 100;
- g = ( ((*it)->_linkPercent * 255) + ((100 - (*it)->_linkPercent) * g) ) / 100;
- b = ( ((*it)->_linkPercent * 255) + ((100 - (*it)->_linkPercent) * b) ) / 100;
- }
-
- (*it)->def.setRGB( r, g, b );
- }
-
-
- // Look for objects using this color
- {
- SPDesktop *desktop = SP_ACTIVE_DESKTOP;
- if ( desktop ) {
- SPDocument* document = sp_desktop_document( desktop );
- Inkscape::XML::Node *rroot = sp_document_repr_root( document );
- if ( rroot ) {
-
- // Find where this thing came from
- Glib::ustring paletteName;
- bool found = false;
- int index = 0;
- for ( std::vector<JustForNow*>::iterator it2 = possible.begin(); it2 != possible.end() && !found; ++it2 ) {
- JustForNow* curr = *it2;
- index = 0;
- for ( std::vector<ColorItem*>::iterator zz = curr->_colors.begin(); zz != curr->_colors.end(); ++zz ) {
- if ( item == *zz ) {
- found = true;
- paletteName = curr->_name;
- break;
- } else {
- index++;
- }
- }
- }
-
- if ( !paletteName.empty() ) {
- gchar* str = g_strdup_printf("%d|", index);
- paletteName.insert( 0, str );
- g_free(str);
- str = 0;
-
- if ( bruteForce( document, rroot, paletteName, item->def.getR(), item->def.getG(), item->def.getB() ) ) {
- sp_document_done( document , SP_VERB_DIALOG_SWATCHES,
- _("Change color definition"));
- }
- }
- }
- }
- }
- }
-}
-
-
-Gtk::Widget* ColorItem::getPreview(PreviewStyle style, ViewType view, ::PreviewSize size, guint ratio)
-{
- Gtk::Widget* widget = 0;
- if ( style == PREVIEW_STYLE_BLURB) {
- Gtk::Label *lbl = new Gtk::Label(def.descr);
- lbl->set_alignment(Gtk::ALIGN_LEFT, Gtk::ALIGN_CENTER);
- widget = lbl;
- } else {
-// Glib::ustring blank(" ");
-// if ( size == Inkscape::ICON_SIZE_MENU || size == Inkscape::ICON_SIZE_DECORATION ) {
-// blank = " ";
-// }
-
- GtkWidget* eekWidget = eek_preview_new();
- EekPreview * preview = EEK_PREVIEW(eekWidget);
- Gtk::Widget* newBlot = Glib::wrap(eekWidget);
-
- if ( previewMap.find(this) == previewMap.end() ){
- eek_preview_set_color( preview, (def.getR() << 8) | def.getR(),
- (def.getG() << 8) | def.getG(),
- (def.getB() << 8) | def.getB());
- } else {
- guchar* px = previewMap[this];
- int width = 128;
- int height = 16;
- GdkPixbuf* pixbuf = gdk_pixbuf_new_from_data( px, GDK_COLORSPACE_RGB, FALSE, 8,
- width, height, width * 3,
- 0, // add delete function
- 0 );
- eek_preview_set_pixbuf( preview, pixbuf );
- }
- if ( def.getType() != ege::PaintDef::RGB ) {
- using Inkscape::IO::Resource::get_path;
- using Inkscape::IO::Resource::ICONS;
- using Inkscape::IO::Resource::SYSTEM;
- GError *error = NULL;
- gsize bytesRead = 0;
- gsize bytesWritten = 0;
- gchar *localFilename = g_filename_from_utf8( get_path(SYSTEM, ICONS, "remove-color.png"),
- -1,
- &bytesRead,
- &bytesWritten,
- &error);
- GdkPixbuf* pixbuf = gdk_pixbuf_new_from_file(localFilename, &error);
- if (!pixbuf) {
- g_warning("Null pixbuf for %p [%s]", localFilename, localFilename );
- }
- g_free(localFilename);
-
- eek_preview_set_pixbuf( preview, pixbuf );
- }
-
- eek_preview_set_details( preview, (::PreviewStyle)style, (::ViewType)view, (::PreviewSize)size, ratio );
- eek_preview_set_linked( preview, (LinkType)((_linkSrc ? PREVIEW_LINK_IN:0)
- | (_listeners.empty() ? 0:PREVIEW_LINK_OUT)
- | (_isLive ? PREVIEW_LINK_OTHER:0)) );
-
- def.addCallback( _colorDefChanged, this );
-
- GValue val = {0, {{0}, {0}}};
- g_value_init( &val, G_TYPE_BOOLEAN );
- g_value_set_boolean( &val, FALSE );
- g_object_set_property( G_OBJECT(preview), "focus-on-click", &val );
-
-/*
- Gtk::Button *btn = new Gtk::Button(blank);
- Gdk::Color color;
- color.set_rgb((_r << 8)|_r, (_g << 8)|_g, (_b << 8)|_b);
- btn->modify_bg(Gtk::STATE_NORMAL, color);
- btn->modify_bg(Gtk::STATE_ACTIVE, color);
- btn->modify_bg(Gtk::STATE_PRELIGHT, color);
- btn->modify_bg(Gtk::STATE_SELECTED, color);
-
- Gtk::Widget* newBlot = btn;
-*/
-
- tips.set_tip((*newBlot), def.descr);
-
-/*
- newBlot->signal_clicked().connect( sigc::mem_fun(*this, &ColorItem::buttonClicked) );
-
- sigc::signal<void> type_signal_something;
-*/
-
- g_signal_connect( G_OBJECT(newBlot->gobj()),
- "clicked",
- G_CALLBACK(handleClick),
- this);
-
- g_signal_connect( G_OBJECT(newBlot->gobj()),
- "alt-clicked",
- G_CALLBACK(handleSecondaryClick),
- this);
-
- g_signal_connect( G_OBJECT(newBlot->gobj()),
- "button-press-event",
- G_CALLBACK(handleButtonPress),
- this);
-
- {
- std::vector<std::string> listing = def.getMIMETypes();
- int entryCount = listing.size();
- GtkTargetEntry* entries = new GtkTargetEntry[entryCount];
- GtkTargetEntry* curr = entries;
- for ( std::vector<std::string>::iterator it = listing.begin(); it != listing.end(); ++it ) {
- curr->target = g_strdup(it->c_str());
- curr->flags = 0;
- if ( mimeToInt.find(*it) == mimeToInt.end() ){
- // these next lines are order-dependent:
- mimeToInt[*it] = mimeStrings.size();
- mimeStrings.push_back(*it);
- }
- curr->info = mimeToInt[curr->target];
- curr++;
- }
- gtk_drag_source_set( GTK_WIDGET(newBlot->gobj()),
- GDK_BUTTON1_MASK,
- entries, entryCount,
- GdkDragAction(GDK_ACTION_MOVE | GDK_ACTION_COPY) );
- for ( int i = 0; i < entryCount; i++ ) {
- g_free(entries[i].target);
- }
- delete[] entries;
- }
-
- g_signal_connect( G_OBJECT(newBlot->gobj()),
- "drag-data-get",
- G_CALLBACK(ColorItem::_dragGetColorData),
- this);
-
- g_signal_connect( G_OBJECT(newBlot->gobj()),
- "drag-begin",
- G_CALLBACK(dragBegin),
- this );
-
- g_signal_connect( G_OBJECT(newBlot->gobj()),
- "enter-notify-event",
- G_CALLBACK(handleEnterNotify),
- this);
-
- g_signal_connect( G_OBJECT(newBlot->gobj()),
- "leave-notify-event",
- G_CALLBACK(handleLeaveNotify),
- this);
-
-// g_signal_connect( G_OBJECT(newBlot->gobj()),
-// "drag-drop",
-// G_CALLBACK(dragDropColorData),
-// this);
-
- if ( def.isEditable() )
- {
-// gtk_drag_dest_set( GTK_WIDGET(newBlot->gobj()),
-// GTK_DEST_DEFAULT_ALL,
-// destColorTargets,
-// G_N_ELEMENTS(destColorTargets),
-// GdkDragAction(GDK_ACTION_COPY | GDK_ACTION_MOVE) );
-
-
-// g_signal_connect( G_OBJECT(newBlot->gobj()),
-// "drag-data-received",
-// G_CALLBACK(_dropDataIn),
-// this );
- }
-
- g_signal_connect( G_OBJECT(newBlot->gobj()),
- "destroy",
- G_CALLBACK(dieDieDie),
- this);
-
-
- widget = newBlot;
- }
-
- _previews.push_back( widget );
-
- return widget;
-}
-
-void ColorItem::buttonClicked(bool secondary)
-{
- SPDesktop *desktop = SP_ACTIVE_DESKTOP;
- if (desktop) {
- char const * attrName = secondary ? "stroke" : "fill";
-
- SPCSSAttr *css = sp_repr_css_attr_new();
- Glib::ustring descr;
- switch (def.getType()) {
- case ege::PaintDef::CLEAR: {
- // TODO actually make this clear
- sp_repr_css_set_property( css, attrName, "none" );
- descr = secondary? _("Remove stroke color") : _("Remove fill color");
- break;
- }
- case ege::PaintDef::NONE: {
- sp_repr_css_set_property( css, attrName, "none" );
- descr = secondary? _("Set stroke color to none") : _("Set fill color to none");
- break;
- }
- case ege::PaintDef::RGB: {
- Glib::ustring colorspec;
- if ( gradMap.find(this) == gradMap.end() ){
- gchar c[64];
- guint32 rgba = (def.getR() << 24) | (def.getG() << 16) | (def.getB() << 8) | 0xff;
- sp_svg_write_color(c, sizeof(c), rgba);
- colorspec = c;
- } else {
- SPGradient* grad = gradMap[this];
- colorspec = "url(#";
- colorspec += grad->id;
- colorspec += ")";
- }
- sp_repr_css_set_property( css, attrName, colorspec.c_str() );
- descr = secondary? _("Set stroke color from swatch") : _("Set fill color from swatch");
- break;
- }
- }
- sp_desktop_set_style(desktop, css);
- sp_repr_css_attr_unref(css);
-
- sp_document_done( sp_desktop_document(desktop), SP_VERB_DIALOG_SWATCHES, descr.c_str() );
- }
-}
static char* trim( char* str ) {
char* ret = str;
@@ -915,149 +295,6 @@ bool parseNum( char*& str, int& val ) {
}
-static bool getBlock( std::string& dst, guchar ch, std::string const str )
-{
- bool good = false;
- std::string::size_type pos = str.find(ch);
- if ( pos != std::string::npos )
- {
- std::string::size_type pos2 = str.find( '(', pos );
- if ( pos2 != std::string::npos ) {
- std::string::size_type endPos = str.find( ')', pos2 );
- if ( endPos != std::string::npos ) {
- dst = str.substr( pos2 + 1, (endPos - pos2 - 1) );
- good = true;
- }
- }
- }
- return good;
-}
-
-static bool popVal( guint64& numVal, std::string& str )
-{
- bool good = false;
- std::string::size_type endPos = str.find(',');
- if ( endPos == std::string::npos ) {
- endPos = str.length();
- }
-
- if ( endPos != std::string::npos && endPos > 0 ) {
- std::string xxx = str.substr( 0, endPos );
- const gchar* ptr = xxx.c_str();
- gchar* endPtr = 0;
- numVal = g_ascii_strtoull( ptr, &endPtr, 10 );
- if ( (numVal == G_MAXUINT64) && (ERANGE == errno) ) {
- // overflow
- } else if ( (numVal == 0) && (endPtr == ptr) ) {
- // failed conversion
- } else {
- good = true;
- str.erase( 0, endPos + 1 );
- }
- }
-
- return good;
-}
-
-void ColorItem::_wireMagicColors( void* p )
-{
- JustForNow* onceMore = reinterpret_cast<JustForNow*>(p);
- if ( onceMore )
- {
- for ( std::vector<ColorItem*>::iterator it = onceMore->_colors.begin(); it != onceMore->_colors.end(); ++it )
- {
- std::string::size_type pos = (*it)->def.descr.find("*{");
- if ( pos != std::string::npos )
- {
- std::string subby = (*it)->def.descr.substr( pos + 2 );
- std::string::size_type endPos = subby.find("}*");
- if ( endPos != std::string::npos )
- {
- subby.erase( endPos );
- //g_message("FOUND MAGIC at '%s'", (*it)->def.descr.c_str());
- //g_message(" '%s'", subby.c_str());
-
- if ( subby.find('E') != std::string::npos )
- {
- (*it)->def.setEditable( true );
- }
-
- if ( subby.find('L') != std::string::npos )
- {
- (*it)->_isLive = true;
- }
-
- std::string part;
- // Tint. index + 1 more val.
- if ( getBlock( part, 'T', subby ) ) {
- guint64 colorIndex = 0;
- if ( popVal( colorIndex, part ) ) {
- guint64 percent = 0;
- if ( popVal( percent, part ) ) {
- (*it)->_linkTint( *(onceMore->_colors[colorIndex]), percent );
- }
- }
- }
-
- // Shade/tone. index + 1 or 2 more val.
- if ( getBlock( part, 'S', subby ) ) {
- guint64 colorIndex = 0;
- if ( popVal( colorIndex, part ) ) {
- guint64 percent = 0;
- if ( popVal( percent, part ) ) {
- guint64 grayLevel = 0;
- if ( !popVal( grayLevel, part ) ) {
- grayLevel = 0;
- }
- (*it)->_linkTone( *(onceMore->_colors[colorIndex]), percent, grayLevel );
- }
- }
- }
-
- }
- }
- }
- }
-}
-
-
-void ColorItem::_linkTint( ColorItem& other, int percent )
-{
- if ( !_linkSrc )
- {
- other._listeners.push_back(this);
- _linkIsTone = false;
- _linkPercent = percent;
- if ( _linkPercent > 100 )
- _linkPercent = 100;
- if ( _linkPercent < 0 )
- _linkPercent = 0;
- _linkGray = 0;
- _linkSrc = &other;
-
- ColorItem::_colorDefChanged(&other);
- }
-}
-
-void ColorItem::_linkTone( ColorItem& other, int percent, int grayLevel )
-{
- if ( !_linkSrc )
- {
- other._listeners.push_back(this);
- _linkIsTone = true;
- _linkPercent = percent;
- if ( _linkPercent > 100 )
- _linkPercent = 100;
- if ( _linkPercent < 0 )
- _linkPercent = 0;
- _linkGray = grayLevel;
- _linkSrc = &other;
-
- ColorItem::_colorDefChanged(&other);
- }
-}
-
-
void _loadPaletteFile( gchar const *filename )
{
char block[1024];
@@ -1069,7 +306,7 @@ void _loadPaletteFile( gchar const *filename )
bool inHeader = true;
bool hasErr = false;
- JustForNow *onceMore = new JustForNow();
+ SwatchPage *onceMore = new SwatchPage();
do {
result = fgets( block, sizeof(block), f );
@@ -1245,46 +482,45 @@ SwatchesPanel::SwatchesPanel(gchar const* prefsPath) :
_remove(0),
_currentIndex(0),
_currentDesktop(0),
- _currentDocument(0),
- _ptr(0)
+ _currentDocument(0)
{
Gtk::RadioMenuItem* hotItem = 0;
_holder = new PreviewHolder();
_clear = new ColorItem( ege::PaintDef::CLEAR );
- _clear->ptr = this;
_remove = new ColorItem( ege::PaintDef::NONE );
- _remove->ptr = this;
-#if USE_DOCUMENT_PALETTE
- {
- JustForNow *docPalette = new JustForNow();
+ if (docPalettes.empty()) {
+ SwatchPage *docPalette = new SwatchPage();
docPalette->_name = "Auto";
- possible.push_back(docPalette);
-
- _ptr = docPalette;
+ docPalettes[0] = docPalette;
}
-#endif // USE_DOCUMENT_PALETTE
+
loadEmUp();
if ( !possible.empty() ) {
- JustForNow* first = 0;
+ SwatchPage* first = 0;
int index = 0;
Glib::ustring targetName;
if ( !_prefs_path.empty() ) {
Inkscape::Preferences *prefs = Inkscape::Preferences::get();
targetName = prefs->getString(_prefs_path + "/palette");
if (!targetName.empty()) {
- for ( std::vector<JustForNow*>::iterator iter = possible.begin(); iter != possible.end(); ++iter ) {
- if ( (*iter)->_name == targetName ) {
- first = *iter;
- break;
- }
+ if (targetName == "Auto") {
+ first = docPalettes[0];
+ } else {
index++;
+ for ( std::vector<SwatchPage*>::iterator iter = possible.begin(); iter != possible.end(); ++iter ) {
+ if ( (*iter)->_name == targetName ) {
+ first = *iter;
+ break;
+ }
+ index++;
+ }
}
}
}
if ( !first ) {
- first = possible.front();
+ first = docPalettes[0];
_currentIndex = 0;
} else {
_currentIndex = index;
@@ -1295,8 +531,9 @@ SwatchesPanel::SwatchesPanel(gchar const* prefsPath) :
Gtk::RadioMenuItem::Group groupOne;
int i = 0;
- for ( std::vector<JustForNow*>::iterator it = possible.begin(); it != possible.end(); it++ ) {
- JustForNow* curr = *it;
+ std::vector<SwatchPage*> swatchSets = _getSwatchSets();
+ for ( std::vector<SwatchPage*>::iterator it = swatchSets.begin(); it != swatchSets.end(); it++ ) {
+ SwatchPage* curr = *it;
Gtk::RadioMenuItem* single = manage(new Gtk::RadioMenuItem(groupOne, curr->_name));
if ( curr == first ) {
hotItem = single;
@@ -1320,11 +557,10 @@ SwatchesPanel::SwatchesPanel(gchar const* prefsPath) :
SwatchesPanel::~SwatchesPanel()
{
+ _trackDocument( this, 0 );
+
_documentConnection.disconnect();
- _resourceConnection.disconnect();
_selChanged.disconnect();
- _setModified.disconnect();
- _subselChanged.disconnect();
if ( _clear ) {
delete _clear;
@@ -1354,8 +590,6 @@ void SwatchesPanel::setDesktop( SPDesktop* desktop )
if ( _currentDesktop ) {
_documentConnection.disconnect();
_selChanged.disconnect();
- _setModified.disconnect();
- _subselChanged.disconnect();
}
_currentDesktop = desktop;
@@ -1382,104 +616,235 @@ void SwatchesPanel::setDesktop( SPDesktop* desktop )
}
}
-void SwatchesPanel::_setDocument( SPDocument *document )
+
+class DocTrack
{
- if ( document != _currentDocument ) {
- if ( _currentDocument ) {
- _resourceConnection.disconnect();
+public:
+ DocTrack(SPDocument *doc, sigc::connection &gradientRsrcChanged, sigc::connection &defsChanged, sigc::connection &defsModified) :
+ doc(doc),
+ gradientRsrcChanged(gradientRsrcChanged),
+ defsChanged(defsChanged),
+ defsModified(defsModified)
+ {
+ }
+
+ ~DocTrack()
+ {
+ if (doc) {
+ gradientRsrcChanged.disconnect();
+ defsChanged.disconnect();
+ defsModified.disconnect();
}
- _currentDocument = document;
- if ( _currentDocument ) {
- _resourceConnection = sp_document_resources_changed_connect(document,
- "gradient",
- sigc::mem_fun(*this, &SwatchesPanel::handleGradientsChange));
+ }
+
+ SPDocument *doc;
+ sigc::connection gradientRsrcChanged;
+ sigc::connection defsChanged;
+ sigc::connection defsModified;
+
+private:
+ DocTrack(DocTrack const &); // no copy
+ DocTrack &operator=(DocTrack const &); // no assign
+};
+
+void SwatchesPanel::_trackDocument( SwatchesPanel *panel, SPDocument *document )
+{
+ SPDocument *oldDoc = 0;
+ if (docPerPanel.find(panel) != docPerPanel.end()) {
+ oldDoc = docPerPanel[panel];
+ if (!oldDoc) {
+ docPerPanel.erase(panel); // Should not be needed, but clean up just in case.
+ }
+ }
+ if (oldDoc != document) {
+ if (oldDoc) {
+ docPerPanel[panel] = 0;
+ bool found = false;
+ for (std::map<SwatchesPanel*, SPDocument*>::iterator it = docPerPanel.begin(); (it != docPerPanel.end()) && !found; ++it) {
+ found = (it->second == document);
+ }
+ if (!found) {
+ for (std::vector<DocTrack*>::iterator it = docTrackings.begin(); it != docTrackings.end(); ++it){
+ if ((*it)->doc == oldDoc) {
+ delete *it;
+ docTrackings.erase(it);
+ break;
+ }
+ }
+ }
+ }
+
+ if (document) {
+ bool found = false;
+ for (std::map<SwatchesPanel*, SPDocument*>::iterator it = docPerPanel.begin(); (it != docPerPanel.end()) && !found; ++it) {
+ found = (it->second == document);
+ }
+ docPerPanel[panel] = document;
+ if (!found) {
+ sigc::connection conn1 = sp_document_resources_changed_connect( document, "gradient", sigc::bind(sigc::ptr_fun(&SwatchesPanel::handleGradientsChange), document) );
+ sigc::connection conn2 = SP_DOCUMENT_DEFS(document)->connectRelease( sigc::hide(sigc::bind(sigc::ptr_fun(&SwatchesPanel::handleDefsModified), document)) );
+ sigc::connection conn3 = SP_DOCUMENT_DEFS(document)->connectModified( sigc::hide(sigc::hide(sigc::bind(sigc::ptr_fun(&SwatchesPanel::handleDefsModified), document))) );
+
+ DocTrack *dt = new DocTrack(document, conn1, conn2, conn3);
+ docTrackings.push_back(dt);
+
+ if (docPalettes.find(document) == docPalettes.end()) {
+ SwatchPage *docPalette = new SwatchPage();
+ docPalette->_name = "Auto";
+ docPalettes[document] = docPalette;
+ }
+ }
}
+ }
- handleGradientsChange();
+ std::set<SPDocument*> docs;
+ for (std::map<SwatchesPanel*, SPDocument*>::iterator it = docPerPanel.begin(); it != docPerPanel.end(); ++it) {
+ docs.insert(it->second);
}
}
-void SwatchesPanel::handleGradientsChange()
+void SwatchesPanel::_setDocument( SPDocument *document )
+{
+ if ( document != _currentDocument ) {
+ _trackDocument(this, document);
+ _currentDocument = document;
+ handleGradientsChange( document );
+ }
+}
+
+static void recalcSwatchContents(SPDocument* doc,
+ std::vector<ColorItem*> &tmpColors,
+ std::map<ColorItem*, guchar*> &previewMappings,
+ std::map<ColorItem*, SPGradient*> &gradMappings)
{
std::vector<SPGradient*> newList;
- const GSList *gradients = sp_document_get_resource_list(_currentDocument, "gradient");
+ const GSList *gradients = sp_document_get_resource_list(doc, "gradient");
for (const GSList *item = gradients; item; item = item->next) {
SPGradient* grad = SP_GRADIENT(item->data);
- if ( grad->has_stops ) {
+ if ( grad->isSwatch() ) {
newList.push_back(SP_GRADIENT(item->data));
}
}
-#if USE_DOCUMENT_PALETTE
- if ( _ptr ) {
- JustForNow *docPalette = reinterpret_cast<JustForNow *>(_ptr);
- // TODO delete pointed to objects
- docPalette->_colors.clear();
- if ( !newList.empty() ) {
- for ( std::vector<SPGradient*>::iterator it = newList.begin(); it != newList.end(); ++it )
- {
- SPGradient* grad = *it;
- if ( grad->repr->attribute("osb:paint") ) {
- sp_gradient_ensure_vector( grad );
- SPGradientStop first = grad->vector.stops[0];
- SPColor color = first.color;
- guint32 together = color.toRGBA32(first.opacity);
-
- // At the moment we can't trust the count of 1 vs 2 stops.
- SPGradientStop second = (*it)->vector.stops[1];
- SPColor color2 = second.color;
- guint32 together2 = color2.toRGBA32(second.opacity);
-
- if ( (grad->vector.stops.size() <= 2) && (together == together2) ) {
- // Treat as solid-color
- Glib::ustring name( grad->id );
- unsigned int r = SP_RGBA32_R_U(together);
- unsigned int g = SP_RGBA32_G_U(together);
- unsigned int b = SP_RGBA32_B_U(together);
- ColorItem* item = new ColorItem( r, g, b, name );
- item->ptr = this;
- docPalette->_colors.push_back(item);
- gradMap[item] = grad;
- } else {
- // Treat as gradient
- Glib::ustring name( grad->id );
- unsigned int r = SP_RGBA32_R_U(together);
- unsigned int g = SP_RGBA32_G_U(together);
- unsigned int b = SP_RGBA32_B_U(together);
- ColorItem* item = new ColorItem( r, g, b, name );
- item->ptr = this;
- docPalette->_colors.push_back(item);
-
- gint width = 128;
- gint height = VBLOCK;
- guchar* px = g_new( guchar, 3 * height * width );
- nr_render_checkerboard_rgb( px, width, VBLOCK, 3 * width, 0, 0 );
-
- sp_gradient_render_vector_block_rgb( grad,
- px, width, height, 3 * width,
- 0, width, TRUE );
-
- previewMap[item] = px;
- gradMap[item] = grad;
- }
+ if ( !newList.empty() ) {
+ for ( std::vector<SPGradient*>::iterator it = newList.begin(); it != newList.end(); ++it )
+ {
+ SPGradient* grad = *it;
+ sp_gradient_ensure_vector( grad );
+ SPGradientStop first = grad->vector.stops[0];
+ SPColor color = first.color;
+ guint32 together = color.toRGBA32(first.opacity);
+
+ SPGradientStop second = (*it)->vector.stops[1];
+ SPColor color2 = second.color;
+
+ Glib::ustring name( grad->getId() );
+ unsigned int r = SP_RGBA32_R_U(together);
+ unsigned int g = SP_RGBA32_G_U(together);
+ unsigned int b = SP_RGBA32_B_U(together);
+ ColorItem* item = new ColorItem( r, g, b, name );
+
+ gint width = PREVIEW_PIXBUF_WIDTH;
+ gint height = VBLOCK;
+ guchar* px = g_new( guchar, 3 * height * width );
+ nr_render_checkerboard_rgb( px, width, height, 3 * width, 0, 0 );
+
+ sp_gradient_render_vector_block_rgb( grad,
+ px, width, height, 3 * width,
+ 0, width, TRUE );
+
+ previewMappings[item] = px;
+
+ tmpColors.push_back(item);
+ gradMappings[item] = grad;
+ }
+ }
+}
+
+void SwatchesPanel::handleGradientsChange(SPDocument *document)
+{
+ SwatchPage *docPalette = (docPalettes.find(document) != docPalettes.end()) ? docPalettes[document] : 0;
+ if (docPalette) {
+ std::vector<ColorItem*> tmpColors;
+ std::map<ColorItem*, guchar*> tmpPrevs;
+ std::map<ColorItem*, SPGradient*> tmpGrads;
+ recalcSwatchContents(document, tmpColors, tmpPrevs, tmpGrads);
+
+ for (std::map<ColorItem*, guchar*>::iterator it = tmpPrevs.begin(); it != tmpPrevs.end(); ++it) {
+ it->first->setPixData(it->second, PREVIEW_PIXBUF_WIDTH, VBLOCK);
+ }
+
+ for (std::map<ColorItem*, SPGradient*>::iterator it = tmpGrads.begin(); it != tmpGrads.end(); ++it) {
+ it->first->setGradient(it->second);
+ }
+
+ docPalette->_colors.swap(tmpColors);
+ for (std::vector<ColorItem*>::iterator it = tmpColors.begin(); it != tmpColors.end(); ++it) {
+ delete *it;
+ }
+
+
+ // Figure out which SwatchesPanel instances are affected and update them.
+
+ for (std::map<SwatchesPanel*, SPDocument*>::iterator it = docPerPanel.begin(); it != docPerPanel.end(); ++it) {
+ if (it->second == document) {
+ SwatchesPanel* swp = it->first;
+ std::vector<SwatchPage*> pages = swp->_getSwatchSets();
+ SwatchPage* curr = pages[swp->_currentIndex];
+ if (curr == docPalette) {
+ swp->_rebuild();
}
}
}
- JustForNow* curr = possible[_currentIndex];
- if (curr == docPalette) {
- _rebuild();
+ }
+}
+
+void SwatchesPanel::handleDefsModified(SPDocument *document)
+{
+ SwatchPage *docPalette = (docPalettes.find(document) != docPalettes.end()) ? docPalettes[document] : 0;
+ if (docPalette) {
+ std::vector<ColorItem*> tmpColors;
+ std::map<ColorItem*, guchar*> tmpPrevs;
+ std::map<ColorItem*, SPGradient*> tmpGrads;
+ recalcSwatchContents(document, tmpColors, tmpPrevs, tmpGrads);
+
+ int cap = std::min(docPalette->_colors.size(), tmpColors.size());
+ for (int i = 0; i < cap; i++) {
+ ColorItem* newColor = tmpColors[i];
+ ColorItem* oldColor = docPalette->_colors[i];
+ if ( (newColor->def.getType() != oldColor->def.getType()) ||
+ (newColor->def.getR() != oldColor->def.getR()) ||
+ (newColor->def.getG() != oldColor->def.getG()) ||
+ (newColor->def.getB() != oldColor->def.getB()) ) {
+ oldColor->def.setRGB(newColor->def.getR(), newColor->def.getG(), newColor->def.getB());
+ }
+ if (tmpGrads.find(newColor) != tmpGrads.end()) {
+ oldColor->setGradient(tmpGrads[newColor]);
+ }
+ if ( tmpPrevs.find(newColor) != tmpPrevs.end() ) {
+ oldColor->setPixData(tmpPrevs[newColor], PREVIEW_PIXBUF_WIDTH, VBLOCK);
+ }
}
}
-#endif // USE_DOCUMENT_PALETTE
}
-void SwatchesPanel::_updateFromSelection()
+std::vector<SwatchPage*> SwatchesPanel::_getSwatchSets() const
{
-#if USE_DOCUMENT_PALETTE
- if ( _ptr ) {
- JustForNow *docPalette = reinterpret_cast<JustForNow *>(_ptr);
+ std::vector<SwatchPage*> tmp;
+ if (docPalettes.find(_currentDocument) != docPalettes.end()) {
+ tmp.push_back(docPalettes[_currentDocument]);
+ }
+ tmp.insert(tmp.end(), possible.begin(), possible.end());
+
+ return tmp;
+}
+
+void SwatchesPanel::_updateFromSelection()
+{
+ SwatchPage *docPalette = (docPalettes.find(_currentDocument) != docPalettes.end()) ? docPalettes[_currentDocument] : 0;
+ if ( docPalette ) {
Glib::ustring fillId;
Glib::ustring strokeId;
@@ -1554,7 +919,6 @@ void SwatchesPanel::_updateFromSelection()
item->setState( isFill, isStroke );
}
}
-#endif // USE_DOCUMENT_PALETTE
}
void SwatchesPanel::_handleAction( int setId, int itemId )
@@ -1562,12 +926,13 @@ void SwatchesPanel::_handleAction( int setId, int itemId )
switch( setId ) {
case 3:
{
- if ( itemId >= 0 && itemId < static_cast<int>(possible.size()) ) {
+ std::vector<SwatchPage*> pages = _getSwatchSets();
+ if ( itemId >= 0 && itemId < static_cast<int>(pages.size()) ) {
_currentIndex = itemId;
if ( !_prefs_path.empty() ) {
Inkscape::Preferences *prefs = Inkscape::Preferences::get();
- prefs->setString(_prefs_path + "/palette", possible[_currentIndex]->_name);
+ prefs->setString(_prefs_path + "/palette", pages[_currentIndex]->_name);
}
_rebuild();
@@ -1579,7 +944,8 @@ void SwatchesPanel::_handleAction( int setId, int itemId )
void SwatchesPanel::_rebuild()
{
- JustForNow* curr = possible[_currentIndex];
+ std::vector<SwatchPage*> pages = _getSwatchSets();
+ SwatchPage* curr = pages[_currentIndex];
_holder->clear();
if ( curr->_prefWidth > 0 ) {
diff --git a/src/ui/dialog/swatches.h b/src/ui/dialog/swatches.h
index 35bcd8c78..b18fd6cad 100644
--- a/src/ui/dialog/swatches.h
+++ b/src/ui/dialog/swatches.h
@@ -11,17 +11,18 @@
#define SEEN_DIALOGS_SWATCHES_H
#include <gtkmm/textview.h>
-#include <gtkmm/tooltips.h>
#include "ui/widget/panel.h"
-#include "ui/previewholder.h"
-#include "widgets/ege-paint-def.h"
namespace Inkscape {
namespace UI {
+
+class PreviewHolder;
+
namespace Dialogs {
class ColorItem;
+class SwatchPage;
/**
* A panel that displays paint swatches.
@@ -40,7 +41,6 @@ public:
virtual SPDesktop* getDesktop() {return _currentDesktop;}
virtual int getSelectedIndex() {return _currentIndex;} // temporary
- virtual void handleGradientsChange(); // temporary
protected:
virtual void _updateFromSelection();
@@ -48,23 +48,25 @@ protected:
virtual void _setDocument( SPDocument *document );
virtual void _rebuild();
+ virtual std::vector<SwatchPage*> _getSwatchSets() const;
+
private:
SwatchesPanel(SwatchesPanel const &); // no copy
SwatchesPanel &operator=(SwatchesPanel const &); // no assign
+ static void _trackDocument( SwatchesPanel *panel, SPDocument *document );
+ static void handleGradientsChange(SPDocument *document);
+ static void handleDefsModified(SPDocument *document);
+
PreviewHolder* _holder;
ColorItem* _clear;
ColorItem* _remove;
int _currentIndex;
SPDesktop* _currentDesktop;
SPDocument* _currentDocument;
- void* _ptr;
sigc::connection _documentConnection;
- sigc::connection _resourceConnection;
sigc::connection _selChanged;
- sigc::connection _setModified;
- sigc::connection _subselChanged;
};
} //namespace Dialogs
diff --git a/src/ui/icon-names.h b/src/ui/icon-names.h
index 76e76ea34..d36d4a41a 100644
--- a/src/ui/icon-names.h
+++ b/src/ui/icon-names.h
@@ -350,6 +350,8 @@
"paint-pattern"
#define INKSCAPE_ICON_PAINT_SOLID \
"paint-solid"
+#define INKSCAPE_ICON_PAINT_SWATCH \
+ "paint-swatch"
#define INKSCAPE_ICON_PAINT_UNKNOWN \
"paint-unknown"
#define INKSCAPE_ICON_PATH_BREAK_APART \
diff --git a/src/ui/tool/Makefile_insert b/src/ui/tool/Makefile_insert
new file mode 100644
index 000000000..2a72dd645
--- /dev/null
+++ b/src/ui/tool/Makefile_insert
@@ -0,0 +1,32 @@
+## Makefile.am fragment sourced by src/Makefile.am.
+
+ink_common_sources += \
+ ui/tool/control-point.cpp \
+ ui/tool/control-point.h \
+ ui/tool/control-point-selection.cpp \
+ ui/tool/control-point-selection.h \
+ ui/tool/commit-events.h \
+ ui/tool/curve-drag-point.cpp \
+ ui/tool/curve-drag-point.h \
+ ui/tool/event-utils.cpp \
+ ui/tool/event-utils.h \
+ ui/tool/manipulator.cpp \
+ ui/tool/manipulator.h \
+ ui/tool/modifier-tracker.cpp \
+ ui/tool/modifier-tracker.h \
+ ui/tool/multi-path-manipulator.cpp \
+ ui/tool/multi-path-manipulator.h \
+ ui/tool/node.cpp \
+ ui/tool/node.h \
+ ui/tool/node-types.h \
+ ui/tool/node-tool.cpp \
+ ui/tool/node-tool.h \
+ ui/tool/path-manipulator.cpp \
+ ui/tool/path-manipulator.h \
+ ui/tool/selectable-control-point.cpp \
+ ui/tool/selectable-control-point.h \
+ ui/tool/selector.cpp \
+ ui/tool/selector.h \
+ ui/tool/shape-record.h \
+ ui/tool/transform-handle-set.cpp \
+ ui/tool/transform-handle-set.h
diff --git a/src/ui/tool/commit-events.h b/src/ui/tool/commit-events.h
new file mode 100644
index 000000000..d99872766
--- /dev/null
+++ b/src/ui/tool/commit-events.h
@@ -0,0 +1,51 @@
+/** @file
+ * Commit events.
+ */
+/* Authors:
+ * Krzysztof Kosiński <tweenk.pl@gmail.com>
+ *
+ * Copyright (C) 2009 Authors
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ */
+
+#ifndef SEEN_UI_TOOL_COMMIT_EVENTS_H
+#define SEEN_UI_TOOL_COMMIT_EVENTS_H
+
+namespace Inkscape {
+namespace UI {
+
+/// This is used to provide sensible messages on the undo stack.
+enum CommitEvent {
+ COMMIT_MOUSE_MOVE,
+ COMMIT_KEYBOARD_MOVE_X,
+ COMMIT_KEYBOARD_MOVE_Y,
+ COMMIT_MOUSE_SCALE,
+ COMMIT_MOUSE_SCALE_UNIFORM,
+ COMMIT_KEYBOARD_SCALE_UNIFORM,
+ COMMIT_KEYBOARD_SCALE_X,
+ COMMIT_KEYBOARD_SCALE_Y,
+ COMMIT_MOUSE_ROTATE,
+ COMMIT_KEYBOARD_ROTATE,
+ COMMIT_MOUSE_SKEW_X,
+ COMMIT_MOUSE_SKEW_Y,
+ COMMIT_KEYBOARD_SKEW_X,
+ COMMIT_KEYBOARD_SKEW_Y,
+ COMMIT_FLIP_X,
+ COMMIT_FLIP_Y
+};
+
+} // namespace UI
+} // namespace Inkscape
+
+#endif
+
+/*
+ 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:encoding=utf-8:textwidth=99 :
diff --git a/src/ui/tool/control-point-selection.cpp b/src/ui/tool/control-point-selection.cpp
new file mode 100644
index 000000000..f880d2ddf
--- /dev/null
+++ b/src/ui/tool/control-point-selection.cpp
@@ -0,0 +1,613 @@
+/** @file
+ * Node selection - implementation
+ */
+/* Authors:
+ * Krzysztof Kosiński <tweenk.pl@gmail.com>
+ *
+ * Copyright (C) 2009 Authors
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ */
+
+#include <boost/none.hpp>
+#include <2geom/transforms.h>
+#include "desktop.h"
+#include "preferences.h"
+#include "ui/tool/control-point-selection.h"
+#include "ui/tool/event-utils.h"
+#include "ui/tool/selectable-control-point.h"
+#include "ui/tool/transform-handle-set.h"
+
+namespace Inkscape {
+namespace UI {
+
+/**
+ * @class ControlPointSelection
+ * @brief Group of selected control points.
+ *
+ * Some operations can be performed on all selected points regardless of their type, therefore
+ * this class is also a Manipulator. It handles the transformations of points using
+ * the keyboard.
+ *
+ * The exposed interface is similar to that of an STL set. Internally, a hash map is used.
+ * @todo Correct iterators (that don't expose the connection list)
+ */
+
+/** @var ControlPointSelection::signal_update
+ * Fires when the display needs to be updated to reflect changes.
+ */
+/** @var ControlPointSelection::signal_point_changed
+ * Fires when a control point is added to or removed from the selection.
+ * The first param contains a pointer to the control point that changed sel. state.
+ * The second says whether the point is currently selected.
+ */
+/** @var ControlPointSelection::signal_commit
+ * Fires when a change that needs to be committed to XML happens.
+ */
+
+ControlPointSelection::ControlPointSelection(SPDesktop *d, SPCanvasGroup *th_group)
+ : Manipulator(d)
+ , _handles(new TransformHandleSet(d, th_group))
+ , _dragging(false)
+ , _handles_visible(true)
+ , _one_node_handles(false)
+{
+ signal_update.connect( sigc::bind(
+ sigc::mem_fun(*this, &ControlPointSelection::_updateTransformHandles),
+ true));
+ ControlPoint::signal_mouseover_change.connect(
+ sigc::hide(
+ sigc::mem_fun(*this, &ControlPointSelection::_mouseoverChanged)));
+ _handles->signal_transform.connect(
+ sigc::mem_fun(*this, &ControlPointSelection::transform));
+ _handles->signal_commit.connect(
+ sigc::mem_fun(*this, &ControlPointSelection::_commitHandlesTransform));
+}
+
+ControlPointSelection::~ControlPointSelection()
+{
+ clear();
+ delete _handles;
+}
+
+/** Add a control point to the selection. */
+std::pair<ControlPointSelection::iterator, bool> ControlPointSelection::insert(const value_type &x)
+{
+ iterator found = _points.find(x);
+ if (found != _points.end()) {
+ return std::pair<iterator, bool>(found, false);
+ }
+
+ found = _points.insert(x).first;
+
+ x->updateState();
+ _pointChanged(x, true);
+
+ return std::pair<iterator, bool>(found, true);
+}
+
+/** Remove a point from the selection. */
+void ControlPointSelection::erase(iterator pos)
+{
+ SelectableControlPoint *erased = *pos;
+ _points.erase(pos);
+ erased->updateState();
+ _pointChanged(erased, false);
+}
+ControlPointSelection::size_type ControlPointSelection::erase(const key_type &k)
+{
+ iterator pos = _points.find(k);
+ if (pos == _points.end()) return 0;
+ erase(pos);
+ return 1;
+}
+void ControlPointSelection::erase(iterator first, iterator last)
+{
+ while (first != last) erase(first++);
+}
+
+/** Remove all points from the selection, making it empty. */
+void ControlPointSelection::clear()
+{
+ for (iterator i = begin(); i != end(); )
+ erase(i++);
+}
+
+/** Select all points that this selection can contain. */
+void ControlPointSelection::selectAll()
+{
+ for (set_type::iterator i = _all_points.begin(); i != _all_points.end(); ++i) {
+ insert(*i);
+ }
+}
+/** Select all points inside the given rectangle (in desktop coordinates). */
+void ControlPointSelection::selectArea(Geom::Rect const &r)
+{
+ for (set_type::iterator i = _all_points.begin(); i != _all_points.end(); ++i) {
+ if (r.contains(**i))
+ insert(*i);
+ }
+}
+/** Unselect all selected points and select all unselected points. */
+void ControlPointSelection::invertSelection()
+{
+ for (set_type::iterator i = _all_points.begin(); i != _all_points.end(); ++i) {
+ if ((*i)->selected()) erase(*i);
+ else insert(*i);
+ }
+}
+void ControlPointSelection::spatialGrow(SelectableControlPoint *origin, int dir)
+{
+ bool grow = (dir > 0);
+ Geom::Point p = origin->position();
+ double best_dist = grow ? HUGE_VAL : 0;
+ SelectableControlPoint *match = NULL;
+ for (set_type::iterator i = _all_points.begin(); i != _all_points.end(); ++i) {
+ bool selected = (*i)->selected();
+ if (grow && !selected) {
+ double dist = Geom::distance((*i)->position(), p);
+ if (dist < best_dist) {
+ best_dist = dist;
+ match = *i;
+ }
+ }
+ if (!grow && selected) {
+ double dist = Geom::distance((*i)->position(), p);
+ // use >= to also deselect the origin node when it's the last one selected
+ if (dist >= best_dist) {
+ best_dist = dist;
+ match = *i;
+ }
+ }
+ }
+ if (match) {
+ if (grow) insert(match);
+ else erase(match);
+ }
+}
+
+/** Transform all selected control points by the given affine transformation. */
+void ControlPointSelection::transform(Geom::Matrix const &m)
+{
+ for (iterator i = _points.begin(); i != _points.end(); ++i) {
+ SelectableControlPoint *cur = *i;
+ cur->transform(m);
+ }
+ _updateBounds();
+ // TODO preserving the rotation radius needs some rethinking...
+ if (_rot_radius) (*_rot_radius) *= m.descrim();
+ if (_mouseover_rot_radius) (*_mouseover_rot_radius) *= m.descrim();
+ signal_update.emit();
+}
+
+/** Align control points on the specified axis. */
+void ControlPointSelection::align(Geom::Dim2 axis)
+{
+ if (empty()) return;
+ Geom::Dim2 d = static_cast<Geom::Dim2>((axis + 1) % 2);
+
+ Geom::OptInterval bound;
+ for (iterator i = _points.begin(); i != _points.end(); ++i) {
+ bound.unionWith(Geom::OptInterval((*i)->position()[d]));
+ }
+
+ double new_coord = bound->middle();
+ for (iterator i = _points.begin(); i != _points.end(); ++i) {
+ Geom::Point pos = (*i)->position();
+ pos[d] = new_coord;
+ (*i)->move(pos);
+ }
+}
+
+/** Equdistantly distribute control points by moving them in the specified dimension. */
+void ControlPointSelection::distribute(Geom::Dim2 d)
+{
+ if (empty()) return;
+
+ // this needs to be a multimap, otherwise it will fail when some points have the same coord
+ typedef std::multimap<double, SelectableControlPoint*> SortMap;
+
+ SortMap sm;
+ Geom::OptInterval bound;
+ // first we insert all points into a multimap keyed by the aligned coord to sort them
+ // simultaneously we compute the extent of selection
+ for (iterator i = _points.begin(); i != _points.end(); ++i) {
+ Geom::Point pos = (*i)->position();
+ sm.insert(std::make_pair(pos[d], (*i)));
+ bound.unionWith(Geom::OptInterval(pos[d]));
+ }
+
+ // now we iterate over the multimap and set aligned positions.
+ double step = size() == 1 ? 0 : bound->extent() / (size() - 1);
+ double start = bound->min();
+ unsigned num = 0;
+ for (SortMap::iterator i = sm.begin(); i != sm.end(); ++i, ++num) {
+ Geom::Point pos = i->second->position();
+ pos[d] = start + num * step;
+ i->second->move(pos);
+ }
+}
+
+/** Get the bounds of the selection.
+ * @return Smallest rectangle containing the positions of all selected points,
+ * or nothing if the selection is empty */
+Geom::OptRect ControlPointSelection::pointwiseBounds()
+{
+ return _bounds;
+}
+
+Geom::OptRect ControlPointSelection::bounds()
+{
+ return size() == 1 ? (*_points.begin())->bounds() : _bounds;
+}
+
+void ControlPointSelection::showTransformHandles(bool v, bool one_node)
+{
+ _one_node_handles = one_node;
+ _handles_visible = v;
+ _updateTransformHandles(false);
+}
+
+void ControlPointSelection::hideTransformHandles()
+{
+ _handles->setVisible(false);
+}
+void ControlPointSelection::restoreTransformHandles()
+{
+ _updateTransformHandles(true);
+}
+
+void ControlPointSelection::toggleTransformHandlesMode()
+{
+ if (_handles->mode() == TransformHandleSet::MODE_SCALE) {
+ _handles->setMode(TransformHandleSet::MODE_ROTATE_SKEW);
+ if (size() == 1) _handles->rotationCenter().setVisible(false);
+ } else {
+ _handles->setMode(TransformHandleSet::MODE_SCALE);
+ }
+}
+
+void ControlPointSelection::_pointGrabbed(SelectableControlPoint *point)
+{
+ hideTransformHandles();
+ _dragging = true;
+ _grabbed_point = point;
+ _farthest_point = point;
+ double maxdist = 0;
+ for (iterator i = _points.begin(); i != _points.end(); ++i) {
+ _original_positions.insert(std::make_pair(*i, (*i)->position()));
+ double dist = Geom::distance(*_grabbed_point, **i);
+ if (dist > maxdist) {
+ maxdist = dist;
+ _farthest_point = *i;
+ }
+ }
+}
+
+void ControlPointSelection::_pointDragged(Geom::Point &new_pos, GdkEventMotion *event)
+{
+ Geom::Point abs_delta = new_pos - _original_positions[_grabbed_point];
+ double fdist = Geom::distance(_original_positions[_grabbed_point], _original_positions[_farthest_point]);
+ if (held_alt(*event) && fdist > 0) {
+ // sculpting
+ for (iterator i = _points.begin(); i != _points.end(); ++i) {
+ SelectableControlPoint *cur = (*i);
+ double dist = Geom::distance(_original_positions[cur], _original_positions[_grabbed_point]);
+ double deltafrac = 0.5 + 0.5 * cos(M_PI * dist/fdist);
+ cur->move(_original_positions[cur] + abs_delta * deltafrac);
+ }
+ } else {
+ Geom::Point delta = new_pos - _grabbed_point->position();
+ for (iterator i = _points.begin(); i != _points.end(); ++i) {
+ SelectableControlPoint *cur = (*i);
+ cur->move(_original_positions[cur] + abs_delta);
+ }
+ _handles->rotationCenter().move(_handles->rotationCenter().position() + delta);
+ }
+ signal_update.emit();
+}
+
+void ControlPointSelection::_pointUngrabbed()
+{
+ _original_positions.clear();
+ _dragging = false;
+ _grabbed_point = _farthest_point = NULL;
+ _updateBounds();
+ restoreTransformHandles();
+ signal_commit.emit(COMMIT_MOUSE_MOVE);
+}
+
+bool ControlPointSelection::_pointClicked(SelectableControlPoint *p, GdkEventButton *event)
+{
+ // clicking a selected node should toggle the transform handles between rotate and scale mode,
+ // if they are visible
+ if (held_no_modifiers(*event) && _handles_visible && p->selected()) {
+ toggleTransformHandlesMode();
+ return true;
+ }
+ return false;
+}
+
+void ControlPointSelection::_pointChanged(SelectableControlPoint *p, bool selected)
+{
+ _updateBounds();
+ _updateTransformHandles(false);
+ if (_bounds)
+ _handles->rotationCenter().move(_bounds->midpoint());
+
+ signal_point_changed.emit(p, selected);
+}
+
+void ControlPointSelection::_mouseoverChanged()
+{
+ _mouseover_rot_radius = boost::none;
+}
+
+void ControlPointSelection::_updateBounds()
+{
+ _rot_radius = boost::none;
+ _bounds = Geom::OptRect();
+ for (iterator i = _points.begin(); i != _points.end(); ++i) {
+ SelectableControlPoint *cur = (*i);
+ Geom::Point p = cur->position();
+ if (!_bounds) {
+ _bounds = Geom::Rect(p, p);
+ } else {
+ _bounds->expandTo(p);
+ }
+ }
+}
+
+void ControlPointSelection::_updateTransformHandles(bool preserve_center)
+{
+ if (_dragging) return;
+
+ if (_handles_visible && size() > 1) {
+ _handles->setBounds(*bounds(), preserve_center);
+ _handles->setVisible(true);
+ } else if (_one_node_handles && size() == 1) { // only one control point in selection
+ SelectableControlPoint *p = *begin();
+ _handles->setBounds(p->bounds());
+ _handles->rotationCenter().move(p->position());
+ _handles->rotationCenter().setVisible(false);
+ _handles->setVisible(true);
+ } else {
+ _handles->setVisible(false);
+ }
+}
+
+/** Moves the selected points along the supplied unit vector according to
+ * the modifier state of the supplied event. */
+bool ControlPointSelection::_keyboardMove(GdkEventKey const &event, Geom::Point const &dir)
+{
+ if (held_control(event)) return false;
+ unsigned num = 1 + combine_key_events(shortcut_key(event), 0);
+
+ Geom::Point delta = dir * num;
+ if (held_shift(event)) delta *= 10;
+ if (held_alt(event)) {
+ delta /= _desktop->current_zoom();
+ } else {
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ double nudge = prefs->getDoubleLimited("/options/nudgedistance/value", 2, 0, 1000);
+ delta *= nudge;
+ }
+
+ transform(Geom::Translate(delta));
+ if (fabs(dir[Geom::X]) > 0) {
+ signal_commit.emit(COMMIT_KEYBOARD_MOVE_X);
+ } else {
+ signal_commit.emit(COMMIT_KEYBOARD_MOVE_Y);
+ }
+ return true;
+}
+
+/** @brief Computes the distance to the farthest corner of the bounding box.
+ * Used to determine what it means to "rotate by one pixel". */
+double ControlPointSelection::_rotationRadius(Geom::Point const &rc)
+{
+ if (empty()) return 1.0; // some safe value
+ Geom::Rect b = *bounds();
+ double maxlen = 0;
+ for (unsigned i = 0; i < 4; ++i) {
+ double len = Geom::distance(b.corner(i), rc);
+ if (len > maxlen) maxlen = len;
+ }
+ return maxlen;
+}
+
+/** Rotates the selected points in the given direction according to the modifier state
+ * from the supplied event.
+ * @param event Key event to take modifier state from
+ * @param dir Direction of rotation (math convention: 1 = counterclockwise, -1 = clockwise)
+ */
+bool ControlPointSelection::_keyboardRotate(GdkEventKey const &event, int dir)
+{
+ if (empty()) return false;
+
+ Geom::Point rc;
+
+ // rotate around the mouseovered point, or the selection's rotation center
+ // if nothing is mouseovered
+ double radius;
+ SelectableControlPoint *scp =
+ dynamic_cast<SelectableControlPoint*>(ControlPoint::mouseovered_point);
+ if (scp) {
+ rc = scp->position();
+ if (!_mouseover_rot_radius) {
+ _mouseover_rot_radius = _rotationRadius(rc);
+ }
+ radius = *_mouseover_rot_radius;
+ } else {
+ rc = _handles->rotationCenter();
+ if (!_rot_radius) {
+ _rot_radius = _rotationRadius(rc);
+ }
+ radius = *_rot_radius;
+ }
+
+ double angle;
+ if (held_alt(event)) {
+ // Rotate by "one pixel". We interpret this as rotating by an angle that causes
+ // the topmost point of a circle circumscribed about the selection's bounding box
+ // to move on an arc 1 screen pixel long.
+ angle = atan2(1.0 / _desktop->current_zoom(), radius) * dir;
+ } else {
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ int snaps = prefs->getIntLimited("/options/rotationsnapsperpi/value", 12, 1, 1000);
+ angle = M_PI * dir / snaps;
+ }
+
+ // translate to origin, rotate, translate back to original position
+ Geom::Matrix m = Geom::Translate(-rc)
+ * Geom::Rotate(angle) * Geom::Translate(rc);
+ transform(m);
+ signal_commit.emit(COMMIT_KEYBOARD_ROTATE);
+ return true;
+}
+
+
+bool ControlPointSelection::_keyboardScale(GdkEventKey const &event, int dir)
+{
+ if (empty()) return false;
+
+ double maxext = bounds()->maxExtent();
+ if (Geom::are_near(maxext, 0)) return false;
+
+ Geom::Point center;
+ SelectableControlPoint *scp =
+ dynamic_cast<SelectableControlPoint*>(ControlPoint::mouseovered_point);
+ if (scp) {
+ center = scp->position();
+ } else {
+ center = _handles->rotationCenter().position();
+ }
+
+ double length_change;
+ if (held_alt(event)) {
+ // Scale by "one pixel". It means shrink/grow 1px for the larger dimension
+ // of the bounding box.
+ length_change = 1.0 / _desktop->current_zoom() * dir;
+ } else {
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ length_change = prefs->getDoubleLimited("/options/defaultscale/value", 2, 1, 1000);
+ length_change *= dir;
+ }
+ double scale = (maxext + length_change) / maxext;
+
+ Geom::Matrix m = Geom::Translate(-center) * Geom::Scale(scale) * Geom::Translate(center);
+ transform(m);
+ signal_commit.emit(COMMIT_KEYBOARD_SCALE_UNIFORM);
+ return true;
+}
+
+bool ControlPointSelection::_keyboardFlip(Geom::Dim2 d)
+{
+ if (empty()) return false;
+
+ Geom::Scale scale_transform(1, 1);
+ if (d == Geom::X) {
+ scale_transform = Geom::Scale(-1, 1);
+ } else {
+ scale_transform = Geom::Scale(1, -1);
+ }
+
+ SelectableControlPoint *scp =
+ dynamic_cast<SelectableControlPoint*>(ControlPoint::mouseovered_point);
+ Geom::Point center = scp ? scp->position() : _handles->rotationCenter().position();
+
+ Geom::Matrix m = Geom::Translate(-center) * scale_transform * Geom::Translate(center);
+ transform(m);
+ signal_commit.emit(d == Geom::X ? COMMIT_FLIP_X : COMMIT_FLIP_Y);
+ return true;
+}
+
+void ControlPointSelection::_commitHandlesTransform(CommitEvent ce)
+{
+ _updateBounds();
+ _updateTransformHandles(true);
+ signal_commit.emit(ce);
+}
+
+bool ControlPointSelection::event(GdkEvent *event)
+{
+ // implement generic event handling that should apply for all control point selections here;
+ // for example, keyboard moves and transformations. This way this functionality doesn't need
+ // to be duplicated in many places
+ // Later split out so that it can be reused in object selection
+
+ switch (event->type) {
+ case GDK_KEY_PRESS:
+ // do not handle key events if the selection is empty
+ if (empty()) break;
+
+ switch(shortcut_key(event->key)) {
+ // moves
+ case GDK_Up:
+ case GDK_KP_Up:
+ case GDK_KP_8:
+ return _keyboardMove(event->key, Geom::Point(0, 1));
+ case GDK_Down:
+ case GDK_KP_Down:
+ case GDK_KP_2:
+ return _keyboardMove(event->key, Geom::Point(0, -1));
+ case GDK_Right:
+ case GDK_KP_Right:
+ case GDK_KP_6:
+ return _keyboardMove(event->key, Geom::Point(1, 0));
+ case GDK_Left:
+ case GDK_KP_Left:
+ case GDK_KP_4:
+ return _keyboardMove(event->key, Geom::Point(-1, 0));
+
+ // rotates
+ case GDK_bracketleft:
+ return _keyboardRotate(event->key, 1);
+ case GDK_bracketright:
+ return _keyboardRotate(event->key, -1);
+
+ // scaling
+ case GDK_less:
+ case GDK_comma:
+ return _keyboardScale(event->key, -1);
+ case GDK_greater:
+ case GDK_period:
+ return _keyboardScale(event->key, 1);
+
+ // TODO: skewing
+
+ // flipping
+ // NOTE: H is horizontal flip, while Shift+H switches transform handle mode!
+ case GDK_h:
+ case GDK_H:
+ if (held_shift(event->key)) {
+ toggleTransformHandlesMode();
+ return true;
+ }
+ // any modifiers except shift should cause no action
+ if (held_any_modifiers(event->key)) break;
+ return _keyboardFlip(Geom::X);
+ case GDK_v:
+ case GDK_V:
+ if (held_any_modifiers(event->key)) break;
+ return _keyboardFlip(Geom::Y);
+ default: break;
+ }
+ break;
+ default: break;
+ }
+ return false;
+}
+
+} // 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:encoding=utf-8:textwidth=99 :
diff --git a/src/ui/tool/control-point-selection.h b/src/ui/tool/control-point-selection.h
new file mode 100644
index 000000000..514ecb2e3
--- /dev/null
+++ b/src/ui/tool/control-point-selection.h
@@ -0,0 +1,161 @@
+/** @file
+ * Node selection - stores a set of nodes and applies transformations
+ * to them
+ */
+/* Authors:
+ * Krzysztof Kosiński <tweenk.pl@gmail.com>
+ *
+ * Copyright (C) 2009 Authors
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ */
+
+#ifndef SEEN_UI_TOOL_NODE_SELECTION_H
+#define SEEN_UI_TOOL_NODE_SELECTION_H
+
+#include <memory>
+#include <boost/optional.hpp>
+#include <sigc++/sigc++.h>
+#include <2geom/forward.h>
+#include <2geom/point.h>
+#include <2geom/rect.h>
+#include "display/display-forward.h"
+#include "util/accumulators.h"
+#include "util/unordered-containers.h"
+#include "ui/tool/commit-events.h"
+#include "ui/tool/manipulator.h"
+
+class SPDesktop;
+
+namespace Inkscape {
+namespace UI {
+class TransformHandleSet;
+class SelectableControlPoint;
+}
+}
+
+namespace Inkscape {
+namespace UI {
+
+class ControlPointSelection : public Manipulator, public sigc::trackable {
+public:
+ ControlPointSelection(SPDesktop *d, SPCanvasGroup *th_group);
+ ~ControlPointSelection();
+ typedef INK_UNORDERED_SET<SelectableControlPoint *> set_type;
+ typedef set_type Set; // convenience alias
+
+ typedef set_type::iterator iterator;
+ typedef set_type::const_iterator const_iterator;
+ typedef set_type::size_type size_type;
+ typedef SelectableControlPoint *value_type;
+ typedef SelectableControlPoint *key_type;
+
+ // size
+ bool empty() { return _points.empty(); }
+ size_type size() { return _points.size(); }
+
+ // iterators
+ iterator begin() { return _points.begin(); }
+ const_iterator begin() const { return _points.begin(); }
+ iterator end() { return _points.end(); }
+ const_iterator end() const { return _points.end(); }
+
+ // insert
+ std::pair<iterator, bool> insert(const value_type& x);
+ template <class InputIterator>
+ void insert(InputIterator first, InputIterator last) {
+ for (; first != last; ++first) {
+ insert(*first);
+ }
+ }
+
+ // erase
+ void clear();
+ void erase(iterator pos);
+ size_type erase(const key_type& k);
+ void erase(iterator first, iterator last);
+
+ // find
+ iterator find(const key_type &k) {
+ return _points.find(k);
+ }
+
+ // Sometimes it is very useful to keep a list of all selectable points.
+ set_type const &allPoints() const { return _all_points; }
+ set_type &allPoints() { return _all_points; }
+ // ...for example in these methods. Another useful case is snapping.
+ void selectAll();
+ void selectArea(Geom::Rect const &);
+ void invertSelection();
+ void spatialGrow(SelectableControlPoint *origin, int dir);
+
+ virtual bool event(GdkEvent *);
+
+ void transform(Geom::Matrix const &m);
+ void align(Geom::Dim2 d);
+ void distribute(Geom::Dim2 d);
+
+ Geom::OptRect pointwiseBounds();
+ Geom::OptRect bounds();
+
+ bool transformHandlesEnabled() { return _handles_visible; }
+ void showTransformHandles(bool v, bool one_node);
+ // the two methods below do not modify the state; they are for use in manipulators
+ // that need to temporarily hide the handles, for example when moving a node
+ void hideTransformHandles();
+ void restoreTransformHandles();
+ void toggleTransformHandlesMode();
+
+ sigc::signal<void> signal_update;
+ sigc::signal<void, SelectableControlPoint *, bool> signal_point_changed;
+ sigc::signal<void, CommitEvent> signal_commit;
+private:
+ // The functions below are invoked from SelectableControlPoint.
+ // Previously they were connected to handlers when selecting, but this
+ // creates problems when dragging a point that was not selected.
+ void _pointGrabbed(SelectableControlPoint *);
+ void _pointDragged(Geom::Point &, GdkEventMotion *);
+ void _pointUngrabbed();
+ bool _pointClicked(SelectableControlPoint *, GdkEventButton *);
+ void _pointChanged(SelectableControlPoint *, bool);
+ void _mouseoverChanged();
+
+ void _updateTransformHandles(bool preserve_center);
+ void _updateBounds();
+ bool _keyboardMove(GdkEventKey const &, Geom::Point const &);
+ bool _keyboardRotate(GdkEventKey const &, int);
+ bool _keyboardScale(GdkEventKey const &, int);
+ bool _keyboardFlip(Geom::Dim2);
+ void _keyboardTransform(Geom::Matrix const &);
+ void _commitHandlesTransform(CommitEvent ce);
+ double _rotationRadius(Geom::Point const &);
+
+ set_type _points;
+ set_type _all_points;
+ INK_UNORDERED_MAP<SelectableControlPoint *, Geom::Point> _original_positions;
+ boost::optional<double> _rot_radius;
+ boost::optional<double> _mouseover_rot_radius;
+ Geom::OptRect _bounds;
+ TransformHandleSet *_handles;
+ SelectableControlPoint *_grabbed_point, *_farthest_point;
+ unsigned _dragging : 1;
+ unsigned _handles_visible : 1;
+ unsigned _one_node_handles : 1;
+
+ friend class SelectableControlPoint;
+};
+
+} // namespace UI
+} // namespace Inkscape
+
+#endif
+
+/*
+ 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:encoding=utf-8:textwidth=99 :
diff --git a/src/ui/tool/control-point.cpp b/src/ui/tool/control-point.cpp
new file mode 100644
index 000000000..b74e3bc9c
--- /dev/null
+++ b/src/ui/tool/control-point.cpp
@@ -0,0 +1,581 @@
+/** @file
+ * Desktop-bound visual control object - implementation
+ */
+/* Authors:
+ * Krzysztof Kosiński <tweenk.pl@gmail.com>
+ *
+ * Copyright (C) 2009 Authors
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ */
+
+#include <iostream>
+#include <gdkmm.h>
+#include <gtkmm.h>
+#include <2geom/point.h>
+#include "desktop.h"
+#include "desktop-handles.h"
+#include "display/snap-indicator.h"
+#include "event-context.h"
+#include "message-context.h"
+#include "preferences.h"
+#include "ui/tool/control-point.h"
+#include "ui/tool/event-utils.h"
+
+namespace Inkscape {
+namespace UI {
+
+// class and member documentation goes here...
+
+/**
+ * @class ControlPoint
+ * @brief Draggable point, the workhorse of on-canvas editing.
+ *
+ * Control points (formerly known as knots) are graphical representations of some significant
+ * point in the drawing. The drawing can be changed by dragging the point and the things that are
+ * attached to it with the mouse. Example things that could be edited with draggable points
+ * are gradient stops, the place where text is attached to a path, text kerns, nodes and handles
+ * in a path, and many more.
+ *
+ * @par Control point event handlers
+ * @par
+ * The control point has several virtual methods which allow you to react to things that
+ * happen to it. The most important ones are the grabbed, dragged, ungrabbed and moved functions.
+ * When a drag happens, the order of calls is as follows:
+ * - <tt>grabbed()</tt>
+ * - <tt>dragged()</tt>
+ * - <tt>dragged()</tt>
+ * - <tt>dragged()</tt>
+ * - ...
+ * - <tt>dragged()</tt>
+ * - <tt>ungrabbed()</tt>
+ *
+ * The control point can also respond to clicks and double clicks. On a double click,
+ * clicked() is called, followed by doubleclicked(). When deriving from SelectableControlPoint,
+ * you need to manually call the superclass version at the appropriate point in your handler.
+ *
+ * @par Which method to override?
+ * @par
+ * You might wonder which hook to use when you want to do things when the point is relocated.
+ * Here are some tips:
+ * - If the point is used to edit an object, override the move() method.
+ * - If the point can usually be dragged wherever you like but can optionally be constrained
+ * to axes or the like, add a handler for <tt>signal_dragged</tt> that modifies its new
+ * position argument.
+ * - If the point has additional canvas items tied to it (like handle lines), override
+ * the setPosition() method.
+ */
+
+/**
+ * @enum ControlPoint::State
+ * Enumeration representing the possible states of the control point, used to determine
+ * its appearance.
+ * @var ControlPoint::STATE_NORMAL
+ * Normal state
+ * @var ControlPoint::STATE_MOUSEOVER
+ * Mouse is hovering over the control point
+ * @var ControlPoint::STATE_CLICKED
+ * First mouse button pressed over the control point
+ */
+
+// Default colors for control points
+static ControlPoint::ColorSet default_color_set = {
+ {0xffffff00, 0x01000000}, // normal fill, stroke
+ {0xff0000ff, 0x01000000}, // mouseover fill, stroke
+ {0x0000ffff, 0x01000000} // clicked fill, stroke
+};
+
+/** Holds the currently mouseovered control point. */
+ControlPoint *ControlPoint::mouseovered_point = 0;
+
+/** Emitted when the mouseovered point changes. The parameter is the new mouseovered point.
+ * When a point ceases to be mouseovered, the parameter will be NULL. */
+sigc::signal<void, ControlPoint*> ControlPoint::signal_mouseover_change;
+
+/** Stores the window point over which the cursor was during the last mouse button press */
+Geom::Point ControlPoint::_drag_event_origin(Geom::infinity(), Geom::infinity());
+
+/** Stores the desktop point from which the last drag was initiated */
+Geom::Point ControlPoint::_drag_origin(Geom::infinity(), Geom::infinity());
+
+/** Events which should be captured when a handle is being dragged. */
+int const ControlPoint::_grab_event_mask = (GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK |
+ GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_KEY_PRESS_MASK |
+ GDK_KEY_RELEASE_MASK);
+
+bool ControlPoint::_drag_initiated = false;
+bool ControlPoint::_event_grab = false;
+
+/** A color set which you can use to create an invisible control that can still receive events.
+ * @relates ControlPoint */
+ControlPoint::ColorSet invisible_cset = {
+ {0x00000000, 0x00000000},
+ {0x00000000, 0x00000000},
+ {0x00000000, 0x00000000}
+};
+
+/**
+ * Create a regular control point.
+ * Derive to have constructors with a reasonable number of parameters.
+ *
+ * @param d Desktop for this control
+ * @param initial_pos Initial position of the control point in desktop coordinates
+ * @param anchor Where is the control point rendered relative to its desktop coordinates
+ * @param shape Shape of the control point: square, diamond, circle...
+ * @param size Pixel size of the visual representation
+ * @param cset Colors of the point
+ * @param group The canvas group the point's canvas item should be created in
+ */
+ControlPoint::ControlPoint(SPDesktop *d, Geom::Point const &initial_pos,
+ Gtk::AnchorType anchor, SPCtrlShapeType shape,
+ unsigned int size, ColorSet *cset, SPCanvasGroup *group)
+ : _desktop (d)
+ , _canvas_item (NULL)
+ , _cset (cset ? cset : &default_color_set)
+ , _state (STATE_NORMAL)
+ , _position (initial_pos)
+{
+ _canvas_item = sp_canvas_item_new(
+ group ? group : sp_desktop_controls (_desktop), SP_TYPE_CTRL,
+ "anchor", (GtkAnchorType) anchor, "size", (gdouble) size, "shape", shape,
+ "filled", TRUE, "fill_color", _cset->normal.fill,
+ "stroked", TRUE, "stroke_color", _cset->normal.stroke,
+ "mode", SP_CTRL_MODE_XOR, NULL);
+ _commonInit();
+}
+
+/**
+ * Create a control point with a pixbuf-based visual representation.
+ *
+ * @param d Desktop for this control
+ * @param initial_pos Initial position of the control point in desktop coordinates
+ * @param anchor Where is the control point rendered relative to its desktop coordinates
+ * @param pixbuf Pixbuf to be used as the visual representation
+ * @param cset Colors of the point
+ * @param group The canvas group the point's canvas item should be created in
+ */
+ControlPoint::ControlPoint(SPDesktop *d, Geom::Point const &initial_pos,
+ Gtk::AnchorType anchor, Glib::RefPtr<Gdk::Pixbuf> pixbuf,
+ ColorSet *cset, SPCanvasGroup *group)
+ : _desktop (d)
+ , _canvas_item (NULL)
+ , _cset(cset ? cset : &default_color_set)
+ , _position (initial_pos)
+{
+ _canvas_item = sp_canvas_item_new(
+ group ? group : sp_desktop_controls(_desktop), SP_TYPE_CTRL,
+ "anchor", (GtkAnchorType) anchor, "size", (gdouble) pixbuf->get_width(),
+ "shape", SP_CTRL_SHAPE_BITMAP, "pixbuf", pixbuf->gobj(),
+ "filled", TRUE, "fill_color", _cset->normal.fill,
+ "stroked", TRUE, "stroke_color", _cset->normal.stroke,
+ "mode", SP_CTRL_MODE_XOR, NULL);
+ _commonInit();
+}
+
+ControlPoint::~ControlPoint()
+{
+ // avoid storing invalid points in mouseovered_point
+ if (this == mouseovered_point) {
+ _clearMouseover();
+ }
+
+ g_signal_handler_disconnect(G_OBJECT(_canvas_item), _event_handler_connection);
+ //sp_canvas_item_hide(_canvas_item);
+ gtk_object_destroy(_canvas_item);
+}
+
+void ControlPoint::_commonInit()
+{
+ SP_CTRL(_canvas_item)->moveto(_position);
+ _event_handler_connection = g_signal_connect(G_OBJECT(_canvas_item), "event",
+ G_CALLBACK(_event_handler), this);
+}
+
+/** Relocate the control point without side effects.
+ * Overload this method only if there is an additional graphical representation
+ * that must be updated (like the lines that connect handles to nodes). If you override it,
+ * you must also call the superclass implementation of the method.
+ * @todo Investigate whether this method should be protected */
+void ControlPoint::setPosition(Geom::Point const &pos)
+{
+ _position = pos;
+ SP_CTRL(_canvas_item)->moveto(pos);
+}
+
+/** Move the control point to new position with side effects.
+ * This is called after each drag. Override this method if only some positions make sense
+ * for a control point (like a point that must always be on a path and can't modify it),
+ * or when moving a control point changes the positions of other points. */
+void ControlPoint::move(Geom::Point const &pos)
+{
+ setPosition(pos);
+}
+
+/** Apply an arbitrary affine transformation to a control point. This is used
+ * by ControlPointSelection, and is important for things like nodes with handles.
+ * The default implementation simply moves the point according to the transform. */
+void ControlPoint::transform(Geom::Matrix const &m) {
+ move(position() * m);
+}
+
+bool ControlPoint::visible() const
+{
+ return sp_canvas_item_is_visible(_canvas_item);
+}
+
+/** Set the visibility of the control point. An invisible point is not drawn on the canvas
+ * and cannot receive any events. If you want to have an invisible point that can respond
+ * to events, use <tt>invisible_cset</tt> as its color set. */
+void ControlPoint::setVisible(bool v)
+{
+ if (v) sp_canvas_item_show(_canvas_item);
+ else sp_canvas_item_hide(_canvas_item);
+}
+
+Glib::ustring ControlPoint::format_tip(char const *format, ...)
+{
+ va_list args;
+ va_start(args, format);
+ char *dyntip = g_strdup_vprintf(format, args);
+ va_end(args);
+ Glib::ustring ret = dyntip;
+ g_free(dyntip);
+ return ret;
+}
+
+unsigned int ControlPoint::_size() const
+{
+ double ret;
+ g_object_get(_canvas_item, "size", &ret, NULL);
+ return static_cast<unsigned int>(ret);
+}
+
+SPCtrlShapeType ControlPoint::_shape() const
+{
+ SPCtrlShapeType ret;
+ g_object_get(_canvas_item, "shape", &ret, NULL);
+ return ret;
+}
+
+GtkAnchorType ControlPoint::_anchor() const
+{
+ GtkAnchorType ret;
+ g_object_get(_canvas_item, "anchor", &ret, NULL);
+ return ret;
+}
+
+Glib::RefPtr<Gdk::Pixbuf> ControlPoint::_pixbuf()
+{
+ GdkPixbuf *ret;
+ g_object_get(_canvas_item, "pixbuf", &ret, NULL);
+ return Glib::wrap(ret);
+}
+
+// Same for setters.
+
+void ControlPoint::_setSize(unsigned int size)
+{
+ g_object_set(_canvas_item, "size", (gdouble) size, NULL);
+}
+
+void ControlPoint::_setShape(SPCtrlShapeType shape)
+{
+ g_object_set(_canvas_item, "shape", shape, NULL);
+}
+
+void ControlPoint::_setAnchor(GtkAnchorType anchor)
+{
+ g_object_set(_canvas_item, "anchor", anchor, NULL);
+}
+
+void ControlPoint::_setPixbuf(Glib::RefPtr<Gdk::Pixbuf> p)
+{
+ g_object_set(_canvas_item, "pixbuf", Glib::unwrap(p), NULL);
+}
+
+// re-routes events into the virtual function
+int ControlPoint::_event_handler(SPCanvasItem */*item*/, GdkEvent *event, ControlPoint *point)
+{
+ return point->_eventHandler(event) ? TRUE : FALSE;
+}
+
+// main event callback, which emits all other callbacks.
+bool ControlPoint::_eventHandler(GdkEvent *event)
+{
+ // NOTE the static variables below are shared for all points!
+ // TODO handle clicks and drags from other buttons too
+
+ // offset from the pointer hotspot to the center of the grabbed knot in desktop coords
+ static Geom::Point pointer_offset;
+ // number of last doubleclicked button
+ static unsigned next_release_doubleclick = 0;
+
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ int drag_tolerance = prefs->getIntLimited("/options/dragtolerance/value", 0, 0, 100);
+
+ switch(event->type)
+ {
+ case GDK_BUTTON_PRESS:
+ next_release_doubleclick = 0;
+ if (event->button.button == 1) {
+ // 1st mouse button click. internally, start dragging, but do not emit signals
+ // or change position until drag tolerance is exceeded.
+ _drag_event_origin[Geom::X] = event->button.x;
+ _drag_event_origin[Geom::Y] = event->button.y;
+ pointer_offset = _position - _desktop->w2d(_drag_event_origin);
+ _drag_initiated = false;
+ // route all events to this handler
+ sp_canvas_item_grab(_canvas_item, _grab_event_mask, NULL, event->button.time);
+ _event_grab = true;
+ _setState(STATE_CLICKED);
+ return true;
+ }
+ return false;
+
+ case GDK_2BUTTON_PRESS:
+ // store the button number for next release
+ next_release_doubleclick = event->button.button;
+ return true;
+
+ case GDK_MOTION_NOTIFY:
+ combine_motion_events(_desktop->canvas, event->motion, 0);
+ if (_event_grab && !_desktop->event_context->space_panning) {
+ _desktop->snapindicator->remove_snaptarget();
+ bool transferred = false;
+ if (!_drag_initiated) {
+ bool t = fabs(event->motion.x - _drag_event_origin[Geom::X]) <= drag_tolerance &&
+ fabs(event->motion.y - _drag_event_origin[Geom::Y]) <= drag_tolerance;
+ if (t) return true;
+
+ // if we are here, it means the tolerance was just exceeded.
+ _drag_origin = _position;
+ transferred = grabbed(&event->motion);
+ // _drag_initiated might change during the above virtual call
+ if (!_drag_initiated) {
+ // this guarantees smooth redraws while dragging
+ sp_canvas_force_full_redraw_after_interruptions(_desktop->canvas, 5);
+ _drag_initiated = true;
+ }
+ }
+ if (!transferred) {
+ // dragging in progress
+ Geom::Point new_pos = _desktop->w2d(event_point(event->motion)) + pointer_offset;
+
+ // the new position is passed by reference and can be changed in the handlers.
+ dragged(new_pos, &event->motion);
+ move(new_pos);
+ _updateDragTip(&event->motion); // update dragging tip after moving to new position
+
+ _desktop->scroll_to_point(new_pos);
+ _desktop->set_coordinate_status(_position);
+ sp_event_context_snap_delay_handler(_desktop->event_context, NULL,
+ (gpointer) this, &event->motion,
+ DelayedSnapEvent::CONTROL_POINT_HANDLER);
+ }
+ return true;
+ }
+ break;
+
+ case GDK_BUTTON_RELEASE:
+ if (_event_grab && event->button.button == 1) {
+ // If we have any pending snap event, then invoke it now!
+ // (This is needed because we might not have snapped on the latest GDK_MOTION_NOTIFY event
+ // if the mouse speed was too high. This is inherent to the snap-delay mechanism.
+ // We must snap at some point in time though, and this is our last chance)
+ // PS: For other contexts this is handled already in sp_event_context_item_handler or
+ // sp_event_context_root_handler
+ if (_desktop->event_context->_delayed_snap_event) {
+ sp_event_context_snap_watchdog_callback(_desktop->event_context->_delayed_snap_event);
+ }
+
+ sp_canvas_item_ungrab(_canvas_item, event->button.time);
+ _setMouseover(this, event->button.state);
+ _event_grab = false;
+
+ if (_drag_initiated) {
+ sp_canvas_end_forced_full_redraws(_desktop->canvas);
+ }
+
+ if (_drag_initiated) {
+ // it is the end of a drag
+ _drag_initiated = false;
+ ungrabbed(&event->button);
+ return true;
+ } else {
+ // it is the end of a click
+ if (next_release_doubleclick) {
+ return doubleclicked(&event->button);
+ } else {
+ return clicked(&event->button);
+ }
+ }
+ }
+ break;
+
+ case GDK_ENTER_NOTIFY:
+ _setMouseover(this, event->crossing.state);
+ return true;
+ case GDK_LEAVE_NOTIFY:
+ _clearMouseover();
+ return true;
+
+ case GDK_GRAB_BROKEN:
+ if (_event_grab && !event->grab_broken.keyboard) {
+ {
+ ungrabbed(NULL);
+ if (_drag_initiated)
+ sp_canvas_end_forced_full_redraws(_desktop->canvas);
+ }
+ _setState(STATE_NORMAL);
+ _event_grab = false;
+ _drag_initiated = false;
+ return true;
+ }
+ break;
+
+ // update tips on modifier state change
+ // TODO add ESC keybinding as drag cancel
+ case GDK_KEY_PRESS:
+ case GDK_KEY_RELEASE:
+ if (mouseovered_point != this) return false;
+ if (_drag_initiated) {
+ return true; // this prevents the tool from overwriting the drag tip
+ } else {
+ unsigned state = state_after_event(event);
+ if (state != event->key.state) {
+ // we need to return true if there was a tip available, otherwise the tool's
+ // handler will process this event and set the tool's message, overwriting
+ // the point's message
+ return _updateTip(state);
+ }
+ }
+ break;
+
+ default: break;
+ }
+
+ return false;
+}
+
+void ControlPoint::_setMouseover(ControlPoint *p, unsigned state)
+{
+ bool visible = p->visible();
+ if (visible) { // invisible points shouldn't get mouseovered
+ p->_setState(STATE_MOUSEOVER);
+ }
+ p->_updateTip(state);
+
+ if (visible && mouseovered_point != p) {
+ mouseovered_point = p;
+ signal_mouseover_change.emit(mouseovered_point);
+ }
+}
+
+bool ControlPoint::_updateTip(unsigned state)
+{
+ Glib::ustring tip = _getTip(state);
+ if (!tip.empty()) {
+ _desktop->event_context->defaultMessageContext()->set(Inkscape::NORMAL_MESSAGE,
+ tip.data());
+ return true;
+ } else {
+ _desktop->event_context->defaultMessageContext()->clear();
+ return false;
+ }
+}
+
+bool ControlPoint::_updateDragTip(GdkEventMotion *event)
+{
+ if (!_hasDragTips()) return false;
+ Glib::ustring tip = _getDragTip(event);
+ if (!tip.empty()) {
+ _desktop->event_context->defaultMessageContext()->set(Inkscape::NORMAL_MESSAGE,
+ tip.data());
+ return true;
+ } else {
+ _desktop->event_context->defaultMessageContext()->clear();
+ return false;
+ }
+}
+
+void ControlPoint::_clearMouseover()
+{
+ if (mouseovered_point) {
+ mouseovered_point->_desktop->event_context->defaultMessageContext()->clear();
+ mouseovered_point->_setState(STATE_NORMAL);
+ mouseovered_point = 0;
+ signal_mouseover_change.emit(mouseovered_point);
+ }
+}
+
+/** Transfer the grab to another point. This method allows one to create a draggable point
+ * that should be dragged instead of the one that received the grabbed signal.
+ * This is used to implement dragging out handles in the new node tool, for example.
+ *
+ * This method will NOT emit the ungrab signal of @c prev_point, because this would complicate
+ * using it with selectable control points. If you use this method while dragging, you must emit
+ * the ungrab signal yourself.
+ *
+ * Note that this will break horribly if you try to transfer grab between points in different
+ * desktops, which doesn't make much sense anyway. */
+void ControlPoint::transferGrab(ControlPoint *prev_point, GdkEventMotion *event)
+{
+ if (!_event_grab) return;
+
+ grabbed(event);
+ sp_canvas_item_ungrab(prev_point->_canvas_item, event->time);
+ sp_canvas_item_grab(_canvas_item, _grab_event_mask, NULL, event->time);
+
+ if (!_drag_initiated) {
+ sp_canvas_force_full_redraw_after_interruptions(_desktop->canvas, 5);
+ _drag_initiated = true;
+ }
+
+ prev_point->_setState(STATE_NORMAL);
+ _setMouseover(this, event->state);
+}
+
+/**
+ * @brief Change the state of the knot
+ * Alters the appearance of the knot to match one of the states: normal, mouseover
+ * or clicked.
+ */
+void ControlPoint::_setState(State state)
+{
+ ColorEntry current = {0, 0};
+ switch(state) {
+ case STATE_NORMAL:
+ current = _cset->normal; break;
+ case STATE_MOUSEOVER:
+ current = _cset->mouseover; break;
+ case STATE_CLICKED:
+ current = _cset->clicked; break;
+ };
+ _setColors(current);
+ _state = state;
+}
+void ControlPoint::_setColors(ColorEntry colors)
+{
+ g_object_set(_canvas_item, "fill_color", colors.fill, "stroke_color", colors.stroke, NULL);
+}
+
+// dummy implementations for handlers
+// they are here to avoid unused param warnings
+bool ControlPoint::grabbed(GdkEventMotion *) { return false; }
+void ControlPoint::dragged(Geom::Point &, GdkEventMotion *) {}
+void ControlPoint::ungrabbed(GdkEventButton *) {}
+bool ControlPoint::clicked(GdkEventButton *) { return false; }
+bool ControlPoint::doubleclicked(GdkEventButton *) { return false; }
+
+} // 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:encoding=utf-8:textwidth=99 :
diff --git a/src/ui/tool/control-point.h b/src/ui/tool/control-point.h
new file mode 100644
index 000000000..48c70748b
--- /dev/null
+++ b/src/ui/tool/control-point.h
@@ -0,0 +1,202 @@
+/** @file
+ * Desktop-bound visual control object
+ */
+/* Authors:
+ * Krzysztof Kosiński <tweenk.pl@gmail.com>
+ *
+ * Copyright (C) 2009 Authors
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ */
+
+#ifndef SEEN_UI_TOOL_CONTROL_POINT_H
+#define SEEN_UI_TOOL_CONTROL_POINT_H
+
+#include <boost/utility.hpp>
+#include <sigc++/sigc++.h>
+#include <gdkmm.h>
+#include <gtkmm.h>
+#include <2geom/point.h>
+
+#include "display/display-forward.h"
+#include "forward.h"
+#include "util/accumulators.h"
+#include "display/sodipodi-ctrl.h"
+
+namespace Inkscape {
+namespace UI {
+
+// most of the documentation is in the .cpp file
+
+class ControlPoint : boost::noncopyable, public sigc::trackable {
+public:
+ typedef Inkscape::Util::ReverseInterruptible RInt;
+ typedef Inkscape::Util::Interruptible Int;
+ // these have to be public, because GCC doesn't allow protected types in constructors,
+ // even if the constructors are protected themselves.
+ struct ColorEntry {
+ guint32 fill;
+ guint32 stroke;
+ };
+ struct ColorSet {
+ ColorEntry normal;
+ ColorEntry mouseover;
+ ColorEntry clicked;
+ };
+ enum State {
+ STATE_NORMAL,
+ STATE_MOUSEOVER,
+ STATE_CLICKED
+ };
+
+ virtual ~ControlPoint();
+
+ /// @name Adjust the position of the control point
+ /// @{
+ /** Current position of the control point. */
+ Geom::Point const &position() const { return _position; }
+ operator Geom::Point const &() { return _position; }
+ virtual void move(Geom::Point const &pos);
+ virtual void setPosition(Geom::Point const &pos);
+ virtual void transform(Geom::Matrix const &m);
+ /// @}
+
+ /// @name Toggle the point's visibility
+ /// @{
+ bool visible() const;
+ virtual void setVisible(bool v);
+ /// @}
+
+ /// @name Transfer grab from another event handler
+ /// @{
+ void transferGrab(ControlPoint *from, GdkEventMotion *event);
+ /// @}
+
+ /// @name Receive notifications about control point events
+ /// @{
+ /*sigc::signal<void, Geom::Point const &, Geom::Point &, GdkEventMotion*> signal_dragged;
+ sigc::signal<bool, GdkEventButton*>::accumulated<RInt> signal_clicked;
+ sigc::signal<bool, GdkEventButton*>::accumulated<RInt> signal_doubleclicked;
+ sigc::signal<bool, GdkEventMotion*>::accumulated<Int> signal_grabbed;
+ sigc::signal<void, GdkEventButton*> signal_ungrabbed;*/
+ /// @}
+
+ /// @name Inspect the state of the control point
+ /// @{
+ State state() { return _state; }
+ bool mouseovered() { return this == mouseovered_point; }
+ /// @}
+
+ static ControlPoint *mouseovered_point;
+ static sigc::signal<void, ControlPoint*> signal_mouseover_change;
+ static Glib::ustring format_tip(char const *format, ...) G_GNUC_PRINTF(1,2);
+
+ // temporarily public, until snap delay is refactored a little
+ virtual bool _eventHandler(GdkEvent *event);
+
+protected:
+ ControlPoint(SPDesktop *d, Geom::Point const &initial_pos, Gtk::AnchorType anchor,
+ SPCtrlShapeType shape, unsigned int size, ColorSet *cset = 0, SPCanvasGroup *group = 0);
+ ControlPoint(SPDesktop *d, Geom::Point const &initial_pos, Gtk::AnchorType anchor,
+ Glib::RefPtr<Gdk::Pixbuf> pixbuf, ColorSet *cset = 0, SPCanvasGroup *group = 0);
+
+ /// @name Handle control point events in subclasses
+ /// @{
+ /**
+ * Called when the user moves the point beyond the drag tolerance with the first button held
+ * down. Return true if you called transferGrab() during this method.
+ * @param event Motion event when drag tolerance was exceeded */
+ virtual bool grabbed(GdkEventMotion *event);
+ /**
+ * Called while dragging, but before moving the knot to new position.
+ * @param pos Old position, always equal to position()
+ * @param new_pos New position (after drag). This is passed as a non-const reference,
+ * so you can change it from the handler - that's how constrained dragging is implemented.
+ * @param event Motion event */
+ virtual void dragged(Geom::Point &new_pos, GdkEventMotion *event);
+ /**
+ * @var ControlPoint::signal_ungrabbed
+ * Emitted when the control point finishes a drag.
+ * @param event Button release event
+ */
+ virtual void ungrabbed(GdkEventButton *event);
+ /**
+ * Called when the control point is clicked, at mouse button release. Your override should
+ * return true if the click had some effect. If it did nothing, return false. Improperly
+ * implementing this method can cause the default context menu not to appear when a control
+ * point is right-clicked.
+ * @param event Button release event */
+ virtual bool clicked(GdkEventButton *event);
+ /**
+ * Called when the control point is doubleclicked, at mouse button release.
+ * @param event Button release event */
+ virtual bool doubleclicked(GdkEventButton *);
+ /// @}
+
+ /// @name Manipulate the control point's appearance in subclasses
+ /// @{
+ virtual void _setState(State state);
+ void _setColors(ColorEntry c);
+
+ unsigned int _size() const;
+ SPCtrlShapeType _shape() const;
+ GtkAnchorType _anchor() const;
+ Glib::RefPtr<Gdk::Pixbuf> _pixbuf();
+
+ void _setSize(unsigned int size);
+ void _setShape(SPCtrlShapeType shape);
+ void _setAnchor(GtkAnchorType anchor);
+ void _setPixbuf(Glib::RefPtr<Gdk::Pixbuf>);
+ /// @}
+
+ virtual Glib::ustring _getTip(unsigned /*state*/) { return ""; }
+ virtual Glib::ustring _getDragTip(GdkEventMotion */*event*/) { return ""; }
+ virtual bool _hasDragTips() { return false; }
+
+ SPDesktop *const _desktop; ///< The desktop this control point resides on.
+ SPCanvasItem * _canvas_item; ///< Visual representation of the control point.
+ ColorSet *_cset; ///< Colors used to represent the point
+ State _state;
+
+ static int const _grab_event_mask;
+ static Geom::Point const &_last_click_event_point() { return _drag_event_origin; }
+ static Geom::Point const &_last_drag_origin() { return _drag_origin; }
+
+private:
+ ControlPoint(ControlPoint const &other);
+ void operator=(ControlPoint const &other);
+
+ static int _event_handler(SPCanvasItem *item, GdkEvent *event, ControlPoint *point);
+ static void _setMouseover(ControlPoint *, unsigned state);
+ static void _clearMouseover();
+ bool _updateTip(unsigned state);
+ bool _updateDragTip(GdkEventMotion *event);
+ void _setDefaultColors();
+ void _commonInit();
+
+ Geom::Point _position; ///< Current position in desktop coordinates
+ gulong _event_handler_connection;
+
+ static Geom::Point _drag_event_origin;
+ static Geom::Point _drag_origin;
+ static bool _event_grab;
+ static bool _drag_initiated;
+};
+
+extern ControlPoint::ColorSet invisible_cset;
+
+
+} // namespace UI
+} // namespace Inkscape
+
+#endif
+
+/*
+ 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:encoding=utf-8:textwidth=99 :
diff --git a/src/ui/tool/curve-drag-point.cpp b/src/ui/tool/curve-drag-point.cpp
new file mode 100644
index 000000000..e761daf20
--- /dev/null
+++ b/src/ui/tool/curve-drag-point.cpp
@@ -0,0 +1,196 @@
+/** @file
+ * Control point that is dragged during path drag
+ */
+/* Authors:
+ * Krzysztof Kosiński <tweenk.pl@gmail.com>
+ *
+ * Copyright (C) 2009 Authors
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ */
+
+#include <glib/gi18n.h>
+#include <2geom/bezier-curve.h>
+#include "desktop.h"
+#include "ui/tool/control-point-selection.h"
+#include "ui/tool/curve-drag-point.h"
+#include "ui/tool/event-utils.h"
+#include "ui/tool/multi-path-manipulator.h"
+#include "ui/tool/path-manipulator.h"
+#include "ui/tool/node.h"
+
+namespace Inkscape {
+namespace UI {
+
+/**
+ * @class CurveDragPoint
+ * An invisible point used to drag curves. This point is used by PathManipulator to allow editing
+ * of path segments by dragging them. It is defined in a separate file so that the node tool
+ * can check if the mouseovered control point is a curve drag point and update the cursor
+ * accordingly, without the need to drag in the full PathManipulator header.
+ */
+
+// This point should be invisible to the user - use the invisible_cset from control-point.h
+// TODO make some methods from path-manipulator.cpp public so that this point doesn't have
+// to be declared as a friend
+
+bool CurveDragPoint::_drags_stroke = false;
+
+CurveDragPoint::CurveDragPoint(PathManipulator &pm)
+ : ControlPoint(pm._multi_path_manipulator._path_data.node_data.desktop, Geom::Point(),
+ Gtk::ANCHOR_CENTER, SP_CTRL_SHAPE_CIRCLE, 1.0, &invisible_cset,
+ pm._multi_path_manipulator._path_data.dragpoint_group)
+ , _pm(pm)
+{
+ setVisible(false);
+}
+
+bool CurveDragPoint::_eventHandler(GdkEvent *event)
+{
+ // do not process any events when the manipulator is empty
+ if (_pm.empty()) {
+ setVisible(false);
+ return false;
+ }
+ return ControlPoint::_eventHandler(event);
+}
+
+bool CurveDragPoint::grabbed(GdkEventMotion */*event*/)
+{
+ _pm._selection.hideTransformHandles();
+ NodeList::iterator second = first.next();
+
+ // move the handles to 1/3 the length of the segment for line segments
+ if (first->front()->isDegenerate() && second->back()->isDegenerate()) {
+
+ // delta is a vector equal 1/3 of distance from first to second
+ Geom::Point delta = (second->position() - first->position()) / 3.0;
+ first->front()->move(first->front()->position() + delta);
+ second->back()->move(second->back()->position() - delta);
+
+ _pm.update();
+ }
+ return false;
+}
+
+void CurveDragPoint::dragged(Geom::Point &new_pos, GdkEventMotion *)
+{
+ NodeList::iterator second = first.next();
+ // Magic Bezier Drag Equations follow!
+ // "weight" describes how the influence of the drag should be distributed
+ // among the handles; 0 = front handle only, 1 = back handle only.
+ double weight, t = _t;
+ if (t <= 1.0 / 6.0) weight = 0;
+ else if (t <= 0.5) weight = (pow((6 * t - 1) / 2.0, 3)) / 2;
+ else if (t <= 5.0 / 6.0) weight = (1 - pow((6 * (1-t) - 1) / 2.0, 3)) / 2 + 0.5;
+ else weight = 1;
+
+ Geom::Point delta = new_pos - position();
+ Geom::Point offset0 = ((1-weight)/(3*t*(1-t)*(1-t))) * delta;
+ Geom::Point offset1 = (weight/(3*t*t*(1-t))) * delta;
+
+ first->front()->move(first->front()->position() + offset0);
+ second->back()->move(second->back()->position() + offset1);
+
+ _pm.update();
+}
+
+void CurveDragPoint::ungrabbed(GdkEventButton *)
+{
+ _pm._updateDragPoint(_desktop->d2w(position()));
+ _pm._commit(_("Drag curve"));
+ _pm._selection.restoreTransformHandles();
+}
+
+bool CurveDragPoint::clicked(GdkEventButton *event)
+{
+ // This check is probably redundant
+ if (!first || event->button != 1) return false;
+ // the next iterator can be invalid if we click very near the end of path
+ NodeList::iterator second = first.next();
+ if (!second) return false;
+
+ // insert nodes on Ctrl+Alt+click
+ if (held_control(*event) && held_alt(*event)) {
+ _insertNode(false);
+ return true;
+ }
+
+ if (held_shift(*event)) {
+ // if both nodes of the segment are selected, deselect;
+ // otherwise add to selection
+ if (first->selected() && second->selected()) {
+ _pm._selection.erase(first.ptr());
+ _pm._selection.erase(second.ptr());
+ } else {
+ _pm._selection.insert(first.ptr());
+ _pm._selection.insert(second.ptr());
+ }
+ } else {
+ // without Shift, take selection
+ _pm._selection.clear();
+ _pm._selection.insert(first.ptr());
+ _pm._selection.insert(second.ptr());
+ }
+ return true;
+}
+
+bool CurveDragPoint::doubleclicked(GdkEventButton *event)
+{
+ if (event->button != 1 || !first || !first.next()) return false;
+ _insertNode(true);
+ return true;
+}
+
+void CurveDragPoint::_insertNode(bool take_selection)
+{
+ // The purpose of this call is to make way for the just created node.
+ // Otherwise clicks on the new node would only work after the user moves the mouse a bit.
+ // PathManipulator will restore visibility when necessary.
+ setVisible(false);
+ NodeList::iterator inserted = _pm.subdivideSegment(first, _t);
+ if (take_selection) {
+ _pm._selection.clear();
+ }
+ _pm._selection.insert(inserted.ptr());
+
+ _pm.update();
+ _pm._commit(_("Add node"));
+}
+
+Glib::ustring CurveDragPoint::_getTip(unsigned state)
+{
+ if (_pm.empty()) return "";
+ if (!first || !first.next()) return "";
+ bool linear = first->front()->isDegenerate() && first.next()->back()->isDegenerate();
+ if (state_held_shift(state)) {
+ return C_("Path segment tip",
+ "<b>Shift:</b> click to toggle segment selection");
+ }
+ if (state_held_control(state) && state_held_alt(state)) {
+ return C_("Path segment tip",
+ "<b>Ctrl+Alt:</b> click to insert a node");
+ }
+ if (linear) {
+ return C_("Path segment tip",
+ "<b>Linear segment:</b> drag to convert to a Bezier segment, "
+ "doubleclick to insert node, click to select (more: Shift, Ctrl+Alt)");
+ } else {
+ return C_("Path segment tip",
+ "<b>Bezier segment:</b> drag to shape the segment, doubleclick to insert node, "
+ "click to select (more: Shift, Ctrl+Alt)");
+ }
+}
+
+} // 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:encoding=utf-8:textwidth=99 :
diff --git a/src/ui/tool/curve-drag-point.h b/src/ui/tool/curve-drag-point.h
new file mode 100644
index 000000000..288ae6a8e
--- /dev/null
+++ b/src/ui/tool/curve-drag-point.h
@@ -0,0 +1,62 @@
+/** @file
+ * Control point that is dragged during path drag
+ */
+/* Authors:
+ * Krzysztof Kosiński <tweenk.pl@gmail.com>
+ *
+ * Copyright (C) 2009 Authors
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ */
+
+#ifndef SEEN_UI_TOOL_CURVE_DRAG_POINT_H
+#define SEEN_UI_TOOL_CURVE_DRAG_POINT_H
+
+#include "ui/tool/control-point.h"
+#include "ui/tool/node.h"
+
+class SPDesktop;
+namespace Inkscape {
+namespace UI {
+
+class PathManipulator;
+struct PathSharedData;
+
+class CurveDragPoint : public ControlPoint {
+public:
+ CurveDragPoint(PathManipulator &pm);
+ void setSize(double sz) { _setSize(sz); }
+ void setTimeValue(double t) { _t = t; }
+ void setIterator(NodeList::iterator i) { first = i; }
+ virtual bool _eventHandler(GdkEvent *event);
+protected:
+ virtual Glib::ustring _getTip(unsigned state);
+ virtual void dragged(Geom::Point &, GdkEventMotion *);
+ virtual bool grabbed(GdkEventMotion *);
+ virtual void ungrabbed(GdkEventButton *);
+ virtual bool clicked(GdkEventButton *);
+ virtual bool doubleclicked(GdkEventButton *);
+
+private:
+ void _insertNode(bool take_selection);
+ double _t;
+ PathManipulator &_pm;
+ NodeList::iterator first;
+ static bool _drags_stroke;
+ static Geom::Point _stroke_drag_origin;
+};
+
+} // namespace UI
+} // namespace Inkscape
+
+#endif
+
+/*
+ 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:encoding=utf-8:textwidth=99 :
diff --git a/src/ui/tool/event-utils.cpp b/src/ui/tool/event-utils.cpp
new file mode 100644
index 000000000..91b2cdb04
--- /dev/null
+++ b/src/ui/tool/event-utils.cpp
@@ -0,0 +1,156 @@
+/** @file
+ * Collection of shorthands to deal with GDK events.
+ */
+/* Authors:
+ * Krzysztof Kosiński <tweenk.pl@gmail.com>
+ *
+ * Copyright (C) 2009 Authors
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ */
+
+#include <cstring>
+#include <gdk/gdk.h>
+#include <gdk/gdkkeysyms.h>
+#include "display/sp-canvas.h"
+#include "ui/tool/event-utils.h"
+
+namespace Inkscape {
+namespace UI {
+
+
+guint shortcut_key(GdkEventKey const &event)
+{
+ guint shortcut_key = 0;
+ gdk_keymap_translate_keyboard_state(
+ gdk_keymap_get_for_display(gdk_display_get_default()),
+ event.hardware_keycode,
+ (GdkModifierType) event.state,
+ 0 /*event->key.group*/,
+ &shortcut_key, NULL, NULL, NULL);
+ return shortcut_key;
+}
+
+unsigned combine_key_events(guint keyval, gint mask)
+{
+ GdkEvent *event_next;
+ gint i = 0;
+
+ event_next = gdk_event_get();
+ // while the next event is also a key notify with the same keyval and mask,
+ while (event_next && (event_next->type == GDK_KEY_PRESS || event_next->type == GDK_KEY_RELEASE)
+ && event_next->key.keyval == keyval
+ && (!mask || event_next->key.state & mask)) {
+ if (event_next->type == GDK_KEY_PRESS)
+ i ++;
+ // kill it
+ gdk_event_free(event_next);
+ // get next
+ event_next = gdk_event_get();
+ }
+ // otherwise, put it back onto the queue
+ if (event_next) gdk_event_put(event_next);
+
+ return i;
+}
+
+unsigned combine_motion_events(SPCanvas *canvas, GdkEventMotion &event, gint mask)
+{
+ GdkEvent *event_next;
+ gint i = 0;
+ event.x -= canvas->x0;
+ event.y -= canvas->y0;
+
+ event_next = gdk_event_get();
+ // while the next event is also a motion notify
+ while (event_next && event_next->type == GDK_MOTION_NOTIFY
+ && (!mask || event_next->motion.state & mask))
+ {
+ if (event_next->motion.device == event.device) {
+ GdkEventMotion &next = event_next->motion;
+ event.send_event = next.send_event;
+ event.time = next.time;
+ event.x = next.x;
+ event.y = next.y;
+ event.state = next.state;
+ event.is_hint = next.is_hint;
+ event.x_root = next.x_root;
+ event.y_root = next.y_root;
+ if (event.axes && next.axes) {
+ memcpy(event.axes, next.axes, event.device->num_axes);
+ }
+ }
+
+ // kill it
+ gdk_event_free(event_next);
+ event_next = gdk_event_get();
+ i++;
+ }
+ // otherwise, put it back onto the queue
+ if (event_next)
+ gdk_event_put(event_next);
+ event.x += canvas->x0;
+ event.y += canvas->y0;
+
+ return i;
+}
+
+/** Returns the modifier state valid after this event. Use this when you process events
+ * that change the modifier state. Currently handles only Shift, Ctrl, Alt. */
+unsigned state_after_event(GdkEvent *event)
+{
+ unsigned state = 0;
+ switch (event->type) {
+ case GDK_KEY_PRESS:
+ state = event->key.state;
+ switch(shortcut_key(event->key)) {
+ case GDK_Shift_L:
+ case GDK_Shift_R:
+ state |= GDK_SHIFT_MASK;
+ break;
+ case GDK_Control_L:
+ case GDK_Control_R:
+ state |= GDK_CONTROL_MASK;
+ break;
+ case GDK_Alt_L:
+ case GDK_Alt_R:
+ state |= GDK_MOD1_MASK;
+ break;
+ default: break;
+ }
+ break;
+ case GDK_KEY_RELEASE:
+ state = event->key.state;
+ switch(shortcut_key(event->key)) {
+ case GDK_Shift_L:
+ case GDK_Shift_R:
+ state &= ~GDK_SHIFT_MASK;
+ break;
+ case GDK_Control_L:
+ case GDK_Control_R:
+ state &= ~GDK_CONTROL_MASK;
+ break;
+ case GDK_Alt_L:
+ case GDK_Alt_R:
+ state &= ~GDK_MOD1_MASK;
+ break;
+ default: break;
+ }
+ break;
+ default: break;
+ }
+ return state;
+}
+
+} // 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:encoding=utf-8:textwidth=99 :
diff --git a/src/ui/tool/event-utils.h b/src/ui/tool/event-utils.h
new file mode 100644
index 000000000..784855f56
--- /dev/null
+++ b/src/ui/tool/event-utils.h
@@ -0,0 +1,132 @@
+/** @file
+ * Collection of shorthands to deal with GDK events.
+ */
+/* Authors:
+ * Krzysztof Kosiński <tweenk.pl@gmail.com>
+ *
+ * Copyright (C) 2009 Authors
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ */
+
+#ifndef SEEN_UI_TOOL_EVENT_UTILS_H
+#define SEEN_UI_TOOL_EVENT_UTILS_H
+
+#include <gdk/gdk.h>
+#include <2geom/point.h>
+
+struct SPCanvas;
+
+namespace Inkscape {
+namespace UI {
+
+inline bool state_held_shift(unsigned state) {
+ return state & GDK_SHIFT_MASK;
+}
+inline bool state_held_control(unsigned state) {
+ return state & GDK_CONTROL_MASK;
+}
+inline bool state_held_alt(unsigned state) {
+ return state & GDK_MOD1_MASK;
+}
+inline bool state_held_only_shift(unsigned state) {
+ return (state & GDK_SHIFT_MASK) && !(state & (GDK_CONTROL_MASK | GDK_MOD1_MASK));
+}
+inline bool state_held_only_control(unsigned state) {
+ return (state & GDK_CONTROL_MASK) && !(state & (GDK_SHIFT_MASK | GDK_MOD1_MASK));
+}
+inline bool state_held_only_alt(unsigned state) {
+ return (state & GDK_MOD1_MASK) && !(state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK));
+}
+inline bool state_held_any_modifiers(unsigned state) {
+ return state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK | GDK_MOD1_MASK);
+}
+inline bool state_held_no_modifiers(unsigned state) {
+ return !state_held_any_modifiers(state);
+}
+template <unsigned button>
+inline bool state_held_button(unsigned state) {
+ return (button == 0 || button > 5) ? false : state & (GDK_BUTTON1_MASK << (button-1));
+}
+
+
+/** Checks whether Shift was held when the event was generated. */
+template <typename E>
+inline bool held_shift(E const &event) {
+ return state_held_shift(event.state);
+}
+
+/** Checks whether Control was held when the event was generated. */
+template <typename E>
+inline bool held_control(E const &event) {
+ return state_held_control(event.state);
+}
+
+/** Checks whether Alt was held when the event was generated. */
+template <typename E>
+inline bool held_alt(E const &event) {
+ return state_held_alt(event.state);
+}
+
+/** True if from the set of Ctrl, Shift and Alt only Ctrl was held when the event
+ * was generated. */
+template <typename E>
+inline bool held_only_control(E const &event) {
+ return state_held_only_control(event.state);
+}
+
+/** True if from the set of Ctrl, Shift and Alt only Shift was held when the event
+ * was generated. */
+template <typename E>
+inline bool held_only_shift(E const &event) {
+ return state_held_only_shift(event.state);
+}
+
+/** True if from the set of Ctrl, Shift and Alt only Alt was held when the event
+ * was generated. */
+template <typename E>
+inline bool held_only_alt(E const &event) {
+ return state_held_only_alt(event.state);
+}
+
+template <typename E>
+inline bool held_no_modifiers(E const &event) {
+ return state_held_no_modifiers(event.state);
+}
+
+template <typename E>
+inline bool held_any_modifiers(E const &event) {
+ return state_held_any_modifiers(event.state);
+}
+
+template <typename E>
+inline Geom::Point event_point(E const &event) {
+ return Geom::Point(event.x, event.y);
+}
+
+/** Use like this:
+ * @code if (held_button<2>(event->motion)) { ... @endcode */
+template <unsigned button, typename E>
+inline bool held_button(E const &event) {
+ return state_held_button<button>(event.state);
+}
+
+guint shortcut_key(GdkEventKey const &event);
+unsigned combine_key_events(guint keyval, gint mask);
+unsigned combine_motion_events(SPCanvas *canvas, GdkEventMotion &event, gint mask);
+unsigned state_after_event(GdkEvent *event);
+
+} // namespace UI
+} // namespace Inkscape
+
+#endif
+
+/*
+ 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:encoding=utf-8:textwidth=99 :
diff --git a/src/ui/tool/manipulator.cpp b/src/ui/tool/manipulator.cpp
new file mode 100644
index 000000000..b532fcab4
--- /dev/null
+++ b/src/ui/tool/manipulator.cpp
@@ -0,0 +1,89 @@
+/** @file
+ * Manipulator base class and manipulator group - implementation
+ */
+/* Authors:
+ * Krzysztof Kosiński <tweenk.pl@gmail.com>
+ *
+ * Copyright (C) 2009 Authors
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ */
+
+#include "ui/tool/manipulator.h"
+#include "ui/tool/node.h"
+
+namespace Inkscape {
+namespace UI {
+
+/*
+void Manipulator::_grabEvents()
+{
+ if (_group) _group->_grabEvents(boost::shared_ptr<Manipulator>(this));
+}
+void Manipulator::_ungrabEvents()
+{
+ if (_group) _group->_ungrabEvents(boost::shared_ptr<Manipulator>(this));
+}
+
+ManipulatorGroup::ManipulatorGroup(SPDesktop *d) :
+ _desktop(d)
+{
+}
+ManipulatorGroup::~ManipulatorGroup()
+{
+}
+
+void ManipulatorGroup::_grabEvents(boost::shared_ptr<Manipulator> m)
+{
+ if (!_grab) _grab = m;
+}
+void ManipulatorGroup::_ungrabEvents(boost::shared_ptr<Manipulator> m)
+{
+ if (_grab == m) _grab.reset();
+}
+
+void ManipulatorGroup::add(boost::shared_ptr<Manipulator> m)
+{
+ m->_group = this;
+ push_back(m);
+}
+void ManipulatorGroup::remove(boost::shared_ptr<Manipulator> m)
+{
+ for (std::list<boost::shared_ptr<Manipulator> >::iterator i = begin(); i != end(); ++i) {
+ if ((*i) == m) {
+ erase(i);
+ break;
+ }
+ }
+ m->_group = 0;
+}
+
+void ManipulatorGroup::clear()
+{
+ std::list<boost::shared_ptr<Manipulator> >::clear();
+}
+
+bool ManipulatorGroup::event(GdkEvent *event)
+{
+ if (_grab) {
+ return _grab->event(event);
+ }
+
+ for (std::list<boost::shared_ptr<Manipulator> >::iterator i = begin(); i != end(); ++i) {
+ if ((*i)->event(event) || _grab) return true;
+ }
+ return false;
+}*/
+
+} // 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:encoding=utf-8:textwidth=99 :
diff --git a/src/ui/tool/manipulator.h b/src/ui/tool/manipulator.h
new file mode 100644
index 000000000..799dad0d3
--- /dev/null
+++ b/src/ui/tool/manipulator.h
@@ -0,0 +1,165 @@
+/** @file
+ * Manipulator - edits something on-canvas
+ */
+/* Authors:
+ * Krzysztof Kosiński <tweenk.pl@gmail.com>
+ *
+ * Copyright (C) 2009 Authors
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ */
+
+#ifndef SEEN_UI_TOOL_MANIPULATOR_H
+#define SEEN_UI_TOOL_MANIPULATOR_H
+
+#include <set>
+#include <map>
+#include <sigc++/sigc++.h>
+#include <glib.h>
+#include <gdk/gdk.h>
+#include <boost/shared_ptr.hpp>
+
+class SPDesktop;
+namespace Inkscape {
+namespace UI {
+
+class ManipulatorGroup;
+class ControlPointSelection;
+
+/**
+ * @brief Tool component that processes events and does something in response to them.
+ * Note: this class is probably redundant.
+ */
+class Manipulator {
+friend class ManipulatorGroup;
+public:
+ Manipulator(SPDesktop *d)
+ : _desktop(d)
+ {}
+ virtual ~Manipulator() {}
+
+ /// Handle input event. Returns true if handled.
+ virtual bool event(GdkEvent *)=0;
+protected:
+ SPDesktop *const _desktop;
+};
+
+/**
+ * @brief Tool component that edits something on the canvas using selectable control points.
+ * Note: this class is probably redundant.
+ */
+class PointManipulator : public Manipulator, public sigc::trackable {
+public:
+ PointManipulator(SPDesktop *d, ControlPointSelection &sel)
+ : Manipulator(d)
+ , _selection(sel)
+ {}
+protected:
+ ControlPointSelection &_selection;
+};
+
+/** Manipulator that aggregates several manipulators of the same type.
+ * The order of invoking events on the member manipulators is undefined.
+ * To make this class more useful, derive from it and add actions that can be performed
+ * on all manipulators in the set.
+ *
+ * This is not used at the moment and is probably useless. */
+template <typename T>
+class MultiManipulator : public PointManipulator {
+public:
+ //typedef typename T::ItemType ItemType;
+ typedef typename std::pair<void*, boost::shared_ptr<T> > MapPair;
+ typedef typename std::map<void*, boost::shared_ptr<T> > MapType;
+
+ MultiManipulator(SPDesktop *d, ControlPointSelection &sel)
+ : PointManipulator(d, sel)
+ {}
+ void addItem(void *item) {
+ boost::shared_ptr<T> m(_createManipulator(item));
+ _mmap.insert(MapPair(item, m));
+ }
+ void removeItem(void *item) {
+ _mmap.erase(item);
+ }
+ void clear() {
+ _mmap.clear();
+ }
+ bool contains(void *item) {
+ return _mmap.find(item) != _mmap.end();
+ }
+ bool empty() {
+ return _mmap.empty();
+ }
+ void setItems(GSList const *list) {
+ std::set<void*> to_remove;
+ for (typename MapType::iterator mi = _mmap.begin(); mi != _mmap.end(); ++mi) {
+ to_remove.insert(mi->first);
+ }
+ for (GSList *i = const_cast<GSList*>(list); i; i = i->next) {
+ if (_isItemType(i->data)) {
+ // erase returns the number of items removed
+ // if nothing was removed, it means this item did not have a manipulator - add it
+ if (!to_remove.erase(i->data)) addItem(i->data);
+ }
+ }
+ typedef typename std::set<void*>::iterator RmIter;
+ for (RmIter ri = to_remove.begin(); ri != to_remove.end(); ++ri) {
+ removeItem(*ri);
+ }
+ }
+
+ /** Invoke a method on all managed manipulators.
+ * Example:
+ * @code m.invokeForAll(&SomeManipulator::someMethod); @endcode
+ */
+ template <typename R>
+ void invokeForAll(R (T::*method)()) {
+ for (typename MapType::iterator i = _mmap.begin(); i != _mmap.end(); ++i) {
+ ((i->second.get())->*method)();
+ }
+ }
+ template <typename R, typename A>
+ void invokeForAll(R (T::*method)(A), A a) {
+ for (typename MapType::iterator i = _mmap.begin(); i != _mmap.end(); ++i) {
+ ((i->second.get())->*method)(a);
+ }
+ }
+ template <typename R, typename A>
+ void invokeForAll(R (T::*method)(A const &), A const &a) {
+ for (typename MapType::iterator i = _mmap.begin(); i != _mmap.end(); ++i) {
+ ((i->second.get())->*method)(a);
+ }
+ }
+ template <typename R, typename A, typename B>
+ void invokeForAll(R (T::*method)(A,B), A a, B b) {
+ for (typename MapType::iterator i = _mmap.begin(); i != _mmap.end(); ++i) {
+ ((i->second.get())->*method)(a, b);
+ }
+ }
+
+ virtual bool event(GdkEvent *event) {
+ for (typename MapType::iterator i = _mmap.begin(); i != _mmap.end(); ++i) {
+ if ((*i).second->event(event)) return true;
+ }
+ return false;
+ }
+protected:
+ virtual T *_createManipulator(void *item) = 0;
+ virtual bool _isItemType(void *item) = 0;
+ MapType _mmap;
+};
+
+} // namespace UI
+} // namespace Inkscape
+
+#endif
+
+/*
+ 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:encoding=utf-8:textwidth=99 :
diff --git a/src/ui/tool/modifier-tracker.cpp b/src/ui/tool/modifier-tracker.cpp
new file mode 100644
index 000000000..8c6033bc7
--- /dev/null
+++ b/src/ui/tool/modifier-tracker.cpp
@@ -0,0 +1,93 @@
+/** @file
+ * Fine-grained modifier tracker for event handling.
+ */
+/* Authors:
+ * Krzysztof Kosiński <tweenk.pl@gmail.com>
+ *
+ * Copyright (C) 2009 Authors
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ */
+
+#include <gdk/gdk.h>
+#include <gdk/gdkkeysyms.h>
+#include "ui/tool/event-utils.h"
+#include "ui/tool/modifier-tracker.h"
+
+namespace Inkscape {
+namespace UI {
+
+ModifierTracker::ModifierTracker()
+ : _left_shift(false)
+ , _right_shift(false)
+ , _left_ctrl(false)
+ , _right_ctrl(false)
+ , _left_alt(false)
+ , _right_alt(false)
+{}
+
+bool ModifierTracker::event(GdkEvent *event)
+{
+ switch (event->type) {
+ case GDK_KEY_PRESS:
+ switch (shortcut_key(event->key)) {
+ case GDK_Shift_L:
+ _left_shift = true;
+ break;
+ case GDK_Shift_R:
+ _right_shift = true;
+ break;
+ case GDK_Control_L:
+ _left_ctrl = true;
+ break;
+ case GDK_Control_R:
+ _right_ctrl = true;
+ break;
+ case GDK_Alt_L:
+ _left_alt = true;
+ break;
+ case GDK_Alt_R:
+ _right_alt = true;
+ break;
+ }
+ break;
+ case GDK_KEY_RELEASE:
+ switch (shortcut_key(event->key)) {
+ case GDK_Shift_L:
+ _left_shift = false;
+ break;
+ case GDK_Shift_R:
+ _right_shift = false;
+ break;
+ case GDK_Control_L:
+ _left_ctrl = false;
+ break;
+ case GDK_Control_R:
+ _right_ctrl = false;
+ break;
+ case GDK_Alt_L:
+ _left_alt = false;
+ break;
+ case GDK_Alt_R:
+ _right_alt = false;
+ break;
+ }
+ break;
+ default: break;
+ }
+
+ return false;
+}
+
+} // 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:encoding=utf-8:textwidth=99 :
diff --git a/src/ui/tool/modifier-tracker.h b/src/ui/tool/modifier-tracker.h
new file mode 100644
index 000000000..55538ead6
--- /dev/null
+++ b/src/ui/tool/modifier-tracker.h
@@ -0,0 +1,54 @@
+/** @file
+ * Fine-grained modifier tracker for event handling.
+ */
+/* Authors:
+ * Krzysztof Kosiński <tweenk.pl@gmail.com>
+ *
+ * Copyright (C) 2009 Authors
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ */
+
+#ifndef SEEN_UI_TOOL_MODIFIER_TRACKER_H
+#define SEEN_UI_TOOL_MODIFIER_TRACKER_H
+
+#include <gdk/gdk.h>
+
+namespace Inkscape {
+namespace UI {
+
+class ModifierTracker {
+public:
+ ModifierTracker();
+ bool event(GdkEvent *);
+
+ bool leftShift() const { return _left_shift; }
+ bool rightShift() const { return _right_shift; }
+ bool leftControl() const { return _left_ctrl; }
+ bool rightControl() const { return _right_ctrl; }
+ bool leftAlt() const { return _left_alt; }
+ bool rightAlt() const { return _right_alt; }
+
+private:
+ bool _left_shift;
+ bool _right_shift;
+ bool _left_ctrl;
+ bool _right_ctrl;
+ bool _left_alt;
+ bool _right_alt;
+};
+
+} // namespace UI
+} // namespace Inkscape
+
+#endif // SEEN_UI_TOOL_MODIFIER_TRACKER_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:encoding=utf-8:textwidth=99 :
diff --git a/src/ui/tool/multi-path-manipulator.cpp b/src/ui/tool/multi-path-manipulator.cpp
new file mode 100644
index 000000000..2025a12d7
--- /dev/null
+++ b/src/ui/tool/multi-path-manipulator.cpp
@@ -0,0 +1,717 @@
+/** @file
+ * Multi path manipulator - implementation
+ */
+/* Authors:
+ * Krzysztof Kosiński <tweenk.pl@gmail.com>
+ *
+ * Copyright (C) 2009 Authors
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ */
+
+#include <boost/shared_ptr.hpp>
+#include <glib.h>
+#include <glibmm/i18n.h>
+#include "desktop.h"
+#include "desktop-handles.h"
+#include "document.h"
+#include "live_effects/lpeobject.h"
+#include "message-stack.h"
+#include "preferences.h"
+#include "sp-path.h"
+#include "ui/tool/control-point-selection.h"
+#include "ui/tool/event-utils.h"
+#include "ui/tool/node.h"
+#include "ui/tool/multi-path-manipulator.h"
+#include "ui/tool/path-manipulator.h"
+#include "util/unordered-containers.h"
+
+#ifdef USE_GNU_HASHES
+namespace __gnu_cxx {
+template<>
+struct hash<Inkscape::UI::NodeList::iterator> {
+ size_t operator()(Inkscape::UI::NodeList::iterator const &n) const {
+ return reinterpret_cast<size_t>(n.ptr());
+ }
+};
+} // namespace __gnu_cxx
+#endif // USE_GNU_HASHES
+
+namespace Inkscape {
+namespace UI {
+
+namespace {
+
+struct hash_nodelist_iterator
+ : public std::unary_function<NodeList::iterator, std::size_t>
+{
+ std::size_t operator()(NodeList::iterator i) const {
+ return INK_HASH<NodeList::iterator::pointer>()(&*i);
+ }
+};
+
+typedef std::pair<NodeList::iterator, NodeList::iterator> IterPair;
+typedef std::vector<IterPair> IterPairList;
+typedef INK_UNORDERED_SET<NodeList::iterator, hash_nodelist_iterator> IterSet;
+typedef std::multimap<double, IterPair> DistanceMap;
+typedef std::pair<double, IterPair> DistanceMapItem;
+
+/** Find pairs of selected endnodes suitable for joining. */
+void find_join_iterators(ControlPointSelection &sel, IterPairList &pairs)
+{
+ IterSet join_iters;
+ DistanceMap dists;
+
+ // find all endnodes in selection
+ for (ControlPointSelection::iterator i = sel.begin(); i != sel.end(); ++i) {
+ Node *node = dynamic_cast<Node*>(*i);
+ if (!node) continue;
+ NodeList::iterator iter = NodeList::get_iterator(node);
+ if (!iter.next() || !iter.prev()) join_iters.insert(iter);
+ }
+
+ if (join_iters.size() < 2) return;
+
+ // Below we find the closest pairs. The algorithm is O(N^3).
+ // We can go down to O(N^2 log N) by using O(N^2) memory, by putting all pairs
+ // with their distances in a multimap (not worth it IMO).
+ while (join_iters.size() >= 2) {
+ double closest = DBL_MAX;
+ IterPair closest_pair;
+ for (IterSet::iterator i = join_iters.begin(); i != join_iters.end(); ++i) {
+ for (IterSet::iterator j = join_iters.begin(); j != i; ++j) {
+ double dist = Geom::distance(**i, **j);
+ if (dist < closest) {
+ closest = dist;
+ closest_pair = std::make_pair(*i, *j);
+ }
+ }
+ }
+ pairs.push_back(closest_pair);
+ join_iters.erase(closest_pair.first);
+ join_iters.erase(closest_pair.second);
+ }
+}
+
+/** After this function, first should be at the end of path and second at the beginnning.
+ * @returns True if the nodes are in the same subpath */
+bool prepare_join(IterPair &join_iters)
+{
+ if (&NodeList::get(join_iters.first) == &NodeList::get(join_iters.second)) {
+ if (join_iters.first.next()) // if first is begin, swap the iterators
+ std::swap(join_iters.first, join_iters.second);
+ return true;
+ }
+
+ NodeList &sp_first = NodeList::get(join_iters.first);
+ NodeList &sp_second = NodeList::get(join_iters.second);
+ if (join_iters.first.next()) { // first is begin
+ if (join_iters.second.next()) { // second is begin
+ sp_first.reverse();
+ } else { // second is end
+ std::swap(join_iters.first, join_iters.second);
+ }
+ } else { // first is end
+ if (join_iters.second.next()) { // second is begin
+ // do nothing
+ } else { // second is end
+ sp_second.reverse();
+ }
+ }
+ return false;
+}
+} // anonymous namespace
+
+
+MultiPathManipulator::MultiPathManipulator(PathSharedData &data, sigc::connection &chg)
+ : PointManipulator(data.node_data.desktop, *data.node_data.selection)
+ , _path_data(data)
+ , _changed(chg)
+{
+ _selection.signal_commit.connect(
+ sigc::mem_fun(*this, &MultiPathManipulator::_commit));
+ _selection.signal_point_changed.connect(
+ sigc::hide( sigc::hide(
+ signal_coords_changed.make_slot())));
+}
+
+MultiPathManipulator::~MultiPathManipulator()
+{
+ _mmap.clear();
+}
+
+/** Remove empty manipulators. */
+void MultiPathManipulator::cleanup()
+{
+ for (MapType::iterator i = _mmap.begin(); i != _mmap.end(); ) {
+ if (i->second->empty()) _mmap.erase(i++);
+ else ++i;
+ }
+}
+
+/** @brief Change the set of items to edit.
+ *
+ * This method attempts to preserve as much of the state as possible. */
+void MultiPathManipulator::setItems(std::set<ShapeRecord> const &s)
+{
+ std::set<ShapeRecord> shapes(s);
+
+ // iterate over currently edited items, modifying / removing them as necessary
+ for (MapType::iterator i = _mmap.begin(); i != _mmap.end();) {
+ std::set<ShapeRecord>::iterator si = shapes.find(i->first);
+ if (si == shapes.end()) {
+ // This item is no longer supposed to be edited - remove its manipulator
+ _mmap.erase(i++);
+ } else {
+ ShapeRecord const &sr = i->first;
+ ShapeRecord const &sr_new = *si;
+ // if the shape record differs, replace the key only and modify other values
+ if (sr.edit_transform != sr_new.edit_transform ||
+ sr.role != sr_new.role)
+ {
+ boost::shared_ptr<PathManipulator> hold(i->second);
+ if (sr.edit_transform != sr_new.edit_transform)
+ hold->setControlsTransform(sr_new.edit_transform);
+ if (sr.role != sr_new.role) {
+ //hold->setOutlineColor(_getOutlineColor(sr_new.role));
+ }
+ _mmap.erase(sr);
+ _mmap.insert(std::make_pair(sr_new, hold));
+ }
+ shapes.erase(si); // remove the processed record
+ ++i;
+ }
+ }
+
+ // add newly selected items
+ for (std::set<ShapeRecord>::iterator i = shapes.begin(); i != shapes.end(); ++i) {
+ ShapeRecord const &r = *i;
+ if (!SP_IS_PATH(r.item) && !IS_LIVEPATHEFFECT(r.item)) continue;
+ boost::shared_ptr<PathManipulator> newpm(new PathManipulator(*this, (SPPath*) r.item,
+ r.edit_transform, _getOutlineColor(r.role), r.lpe_key));
+ newpm->showHandles(_show_handles);
+ // always show outlines for clips and masks
+ newpm->showOutline(_show_outline || r.role != SHAPE_ROLE_NORMAL);
+ newpm->showPathDirection(_show_path_direction);
+ newpm->setLiveOutline(_live_outline);
+ newpm->setLiveObjects(_live_objects);
+ _mmap.insert(std::make_pair(r, newpm));
+ }
+}
+
+void MultiPathManipulator::selectSubpaths()
+{
+ if (_selection.empty()) {
+ _selection.selectAll();
+ } else {
+ invokeForAll(&PathManipulator::selectSubpaths);
+ }
+}
+
+void MultiPathManipulator::shiftSelection(int dir)
+{
+ invokeForAll(&PathManipulator::shiftSelection, dir);
+}
+
+void MultiPathManipulator::invertSelectionInSubpaths()
+{
+ invokeForAll(&PathManipulator::invertSelectionInSubpaths);
+}
+
+void MultiPathManipulator::setNodeType(NodeType type)
+{
+ if (_selection.empty()) return;
+ for (ControlPointSelection::iterator i = _selection.begin(); i != _selection.end(); ++i) {
+ Node *node = dynamic_cast<Node*>(*i);
+ if (node) node->setType(type);
+ }
+ _done(_("Change node type"));
+}
+
+void MultiPathManipulator::setSegmentType(SegmentType type)
+{
+ if (_selection.empty()) return;
+ invokeForAll(&PathManipulator::setSegmentType, type);
+ if (type == SEGMENT_STRAIGHT) {
+ _done(_("Straighten segments"));
+ } else {
+ _done(_("Make segments curves"));
+ }
+}
+
+void MultiPathManipulator::insertNodes()
+{
+ invokeForAll(&PathManipulator::insertNodes);
+ _done(_("Add nodes"));
+}
+
+void MultiPathManipulator::joinNodes()
+{
+ invokeForAll(&PathManipulator::hideDragPoint);
+ // Node join has two parts. In the first one we join two subpaths by fusing endpoints
+ // into one. In the second we fuse nodes in each subpath.
+ IterPairList joins;
+ NodeList::iterator preserve_pos;
+ Node *mouseover_node = dynamic_cast<Node*>(ControlPoint::mouseovered_point);
+ if (mouseover_node) {
+ preserve_pos = NodeList::get_iterator(mouseover_node);
+ }
+ find_join_iterators(_selection, joins);
+
+ for (IterPairList::iterator i = joins.begin(); i != joins.end(); ++i) {
+ bool same_path = prepare_join(*i);
+ NodeList &sp_first = NodeList::get(i->first);
+ NodeList &sp_second = NodeList::get(i->second);
+ i->first->setType(NODE_CUSP, false);
+
+ Geom::Point joined_pos, pos_handle_front, pos_handle_back;
+ pos_handle_front = *i->second->front();
+ pos_handle_back = *i->first->back();
+
+ // When we encounter the mouseover node, we unset the iterator - it will be invalidated
+ if (i->first == preserve_pos) {
+ joined_pos = *i->first;
+ preserve_pos = NodeList::iterator();
+ } else if (i->second == preserve_pos) {
+ joined_pos = *i->second;
+ preserve_pos = NodeList::iterator();
+ } else {
+ joined_pos = Geom::middle_point(*i->first, *i->second);
+ }
+
+ // if the handles aren't degenerate, don't move them
+ i->first->move(joined_pos);
+ Node *joined_node = i->first.ptr();
+ if (!i->second->front()->isDegenerate()) {
+ joined_node->front()->setPosition(pos_handle_front);
+ }
+ if (!i->first->back()->isDegenerate()) {
+ joined_node->back()->setPosition(pos_handle_back);
+ }
+ sp_second.erase(i->second);
+
+ if (same_path) {
+ sp_first.setClosed(true);
+ } else {
+ sp_first.splice(sp_first.end(), sp_second);
+ sp_second.kill();
+ }
+ _selection.insert(i->first.ptr());
+ }
+
+ if (joins.empty()) {
+ // Second part replaces contiguous selections of nodes with single nodes
+ invokeForAll(&PathManipulator::weldNodes, preserve_pos);
+ }
+
+ _doneWithCleanup(_("Join nodes"));
+}
+
+void MultiPathManipulator::breakNodes()
+{
+ if (_selection.empty()) return;
+ invokeForAll(&PathManipulator::breakNodes);
+ _done(_("Break nodes"));
+}
+
+void MultiPathManipulator::deleteNodes(bool keep_shape)
+{
+ if (_selection.empty()) return;
+ invokeForAll(&PathManipulator::deleteNodes, keep_shape);
+ _doneWithCleanup(_("Delete nodes"));
+}
+
+/** Join selected endpoints to create segments. */
+void MultiPathManipulator::joinSegments()
+{
+ IterPairList joins;
+ find_join_iterators(_selection, joins);
+
+ for (IterPairList::iterator i = joins.begin(); i != joins.end(); ++i) {
+ bool same_path = prepare_join(*i);
+ NodeList &sp_first = NodeList::get(i->first);
+ NodeList &sp_second = NodeList::get(i->second);
+ i->first->setType(NODE_CUSP, false);
+ i->second->setType(NODE_CUSP, false);
+ if (same_path) {
+ sp_first.setClosed(true);
+ } else {
+ sp_first.splice(sp_first.end(), sp_second);
+ sp_second.kill();
+ }
+ }
+
+ if (joins.empty()) {
+ invokeForAll(&PathManipulator::weldSegments);
+ }
+ _doneWithCleanup("Join segments");
+}
+
+void MultiPathManipulator::deleteSegments()
+{
+ if (_selection.empty()) return;
+ invokeForAll(&PathManipulator::deleteSegments);
+ _doneWithCleanup("Delete segments");
+}
+
+void MultiPathManipulator::alignNodes(Geom::Dim2 d)
+{
+ _selection.align(d);
+ if (d == Geom::X) {
+ _done("Align nodes to a horizontal line");
+ } else {
+ _done("Align nodes to a vertical line");
+ }
+}
+
+void MultiPathManipulator::distributeNodes(Geom::Dim2 d)
+{
+ _selection.distribute(d);
+ if (d == Geom::X) {
+ _done("Distrubute nodes horizontally");
+ } else {
+ _done("Distribute nodes vertically");
+ }
+}
+
+void MultiPathManipulator::reverseSubpaths()
+{
+ if (_selection.empty()) {
+ invokeForAll(&PathManipulator::reverseSubpaths, false);
+ _done("Reverse subpaths");
+ } else {
+ invokeForAll(&PathManipulator::reverseSubpaths, true);
+ _done("Reverse selected subpaths");
+ }
+}
+
+void MultiPathManipulator::move(Geom::Point const &delta)
+{
+ _selection.transform(Geom::Translate(delta));
+ _done("Move nodes");
+}
+
+void MultiPathManipulator::showOutline(bool show)
+{
+ for (MapType::iterator i = _mmap.begin(); i != _mmap.end(); ++i) {
+ // always show outlines for clipping paths and masks
+ i->second->showOutline(show || i->first.role != SHAPE_ROLE_NORMAL);
+ }
+ _show_outline = show;
+}
+
+void MultiPathManipulator::showHandles(bool show)
+{
+ invokeForAll(&PathManipulator::showHandles, show);
+ _show_handles = show;
+}
+
+void MultiPathManipulator::showPathDirection(bool show)
+{
+ invokeForAll(&PathManipulator::showPathDirection, show);
+ _show_path_direction = show;
+}
+
+/** @brief Set live outline update status
+ * When set to true, outline will be updated continuously when dragging
+ * or transforming nodes. Otherwise it will only update when changes are committed
+ * to XML. */
+void MultiPathManipulator::setLiveOutline(bool set)
+{
+ invokeForAll(&PathManipulator::setLiveOutline, set);
+ _live_outline = set;
+}
+
+/** @brief Set live object update status
+ * When set to true, objects will be updated continuously when dragging
+ * or transforming nodes. Otherwise they will only update when changes are committed
+ * to XML. */
+void MultiPathManipulator::setLiveObjects(bool set)
+{
+ invokeForAll(&PathManipulator::setLiveObjects, set);
+ _live_objects = set;
+}
+
+void MultiPathManipulator::updateOutlineColors()
+{
+ //for (MapType::iterator i = _mmap.begin(); i != _mmap.end(); ++i) {
+ // i->second->setOutlineColor(_getOutlineColor(i->first.role));
+ //}
+}
+
+bool MultiPathManipulator::event(GdkEvent *event)
+{
+ _tracker.event(event);
+ guint key = 0;
+ if (event->type == GDK_KEY_PRESS) {
+ key = shortcut_key(event->key);
+ }
+
+ // Single handle adjustments go here.
+ if (_selection.size() == 1 && event->type == GDK_KEY_PRESS) {
+ do {
+ Node *n = dynamic_cast<Node *>(*_selection.begin());
+ if (!n) break;
+
+ PathManipulator &pm = n->nodeList().subpathList().pm();
+
+ int which = 0;
+ if (_tracker.rightAlt() || _tracker.rightControl()) {
+ which = 1;
+ }
+ if (_tracker.leftAlt() || _tracker.leftControl()) {
+ if (which != 0) break; // ambiguous
+ which = -1;
+ }
+ if (which == 0) break; // no handle chosen
+ bool one_pixel = _tracker.leftAlt() || _tracker.rightAlt();
+ bool handled = true;
+
+ switch (key) {
+ // single handle functions
+ // rotation
+ case GDK_bracketleft:
+ case GDK_braceleft:
+ pm.rotateHandle(n, which, 1, one_pixel);
+ break;
+ case GDK_bracketright:
+ case GDK_braceright:
+ pm.rotateHandle(n, which, -1, one_pixel);
+ break;
+ // adjust length
+ case GDK_period:
+ case GDK_greater:
+ pm.scaleHandle(n, which, 1, one_pixel);
+ break;
+ case GDK_comma:
+ case GDK_less:
+ pm.scaleHandle(n, which, -1, one_pixel);
+ break;
+ default:
+ handled = false;
+ break;
+ }
+
+ if (handled) return true;
+ } while(0);
+ }
+
+
+ switch (event->type) {
+ case GDK_KEY_PRESS:
+ switch (key) {
+ case GDK_Insert:
+ case GDK_KP_Insert:
+ // Insert - insert nodes in the middle of selected segments
+ insertNodes();
+ return true;
+ case GDK_i:
+ case GDK_I:
+ if (held_only_shift(event->key)) {
+ // Shift+I - insert nodes (alternate keybinding for Mac keyboards
+ // that don't have the Insert key)
+ insertNodes();
+ return true;
+ }
+ break;
+ case GDK_j:
+ case GDK_J:
+ if (held_only_shift(event->key)) {
+ // Shift+J - join nodes
+ joinNodes();
+ return true;
+ }
+ if (held_only_alt(event->key)) {
+ // Alt+J - join segments
+ joinSegments();
+ return true;
+ }
+ break;
+ case GDK_b:
+ case GDK_B:
+ if (held_only_shift(event->key)) {
+ // Shift+B - break nodes
+ breakNodes();
+ return true;
+ }
+ break;
+ case GDK_Delete:
+ case GDK_KP_Delete:
+ case GDK_BackSpace:
+ if (held_shift(event->key)) break;
+ if (held_alt(event->key)) {
+ // Alt+Delete - delete segments
+ deleteSegments();
+ } else {
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ bool del_preserves_shape = prefs->getBool("/tools/nodes/delete_preserves_shape", true);
+ // pass keep_shape = true when:
+ // a) del preserves shape, and control is not pressed
+ // b) ctrl+del preserves shape (del_preserves_shape is false), and control is pressed
+ // Hence xor
+ deleteNodes(del_preserves_shape ^ held_control(event->key));
+ }
+ return true;
+ case GDK_c:
+ case GDK_C:
+ if (held_only_shift(event->key)) {
+ // Shift+C - make nodes cusp
+ setNodeType(NODE_CUSP);
+ return true;
+ }
+ break;
+ case GDK_s:
+ case GDK_S:
+ if (held_only_shift(event->key)) {
+ // Shift+S - make nodes smooth
+ setNodeType(NODE_SMOOTH);
+ return true;
+ }
+ break;
+ case GDK_a:
+ case GDK_A:
+ if (held_only_shift(event->key)) {
+ // Shift+A - make nodes auto-smooth
+ setNodeType(NODE_AUTO);
+ return true;
+ }
+ break;
+ case GDK_y:
+ case GDK_Y:
+ if (held_only_shift(event->key)) {
+ // Shift+Y - make nodes symmetric
+ setNodeType(NODE_SYMMETRIC);
+ return true;
+ }
+ break;
+ case GDK_r:
+ case GDK_R:
+ if (held_only_shift(event->key)) {
+ // Shift+R - reverse subpaths
+ reverseSubpaths();
+ return true;
+ }
+ break;
+ default:
+ break;
+ }
+ break;
+ case GDK_MOTION_NOTIFY:
+ combine_motion_events(_desktop->canvas, event->motion, 0);
+ for (MapType::iterator i = _mmap.begin(); i != _mmap.end(); ++i) {
+ if (i->second->event(event)) return true;
+ }
+ break;
+ default: break;
+ }
+
+ return false;
+}
+
+/** Commit changes to XML and add undo stack entry based on the action that was done. Invoked
+ * by sub-manipulators, for example TransformHandleSet and ControlPointSelection. */
+void MultiPathManipulator::_commit(CommitEvent cps)
+{
+ gchar const *reason = NULL;
+ gchar const *key = NULL;
+ switch(cps) {
+ case COMMIT_MOUSE_MOVE:
+ reason = _("Move nodes");
+ break;
+ case COMMIT_KEYBOARD_MOVE_X:
+ reason = _("Move nodes horizontally");
+ key = "node:move:x";
+ break;
+ case COMMIT_KEYBOARD_MOVE_Y:
+ reason = _("Move nodes vertically");
+ key = "node:move:y";
+ break;
+ case COMMIT_MOUSE_ROTATE:
+ reason = _("Rotate nodes");
+ break;
+ case COMMIT_KEYBOARD_ROTATE:
+ reason = _("Rotate nodes");
+ key = "node:rotate";
+ break;
+ case COMMIT_MOUSE_SCALE_UNIFORM:
+ reason = _("Scale nodes uniformly");
+ break;
+ case COMMIT_MOUSE_SCALE:
+ reason = _("Scale nodes");
+ break;
+ case COMMIT_KEYBOARD_SCALE_UNIFORM:
+ reason = _("Scale nodes uniformly");
+ key = "node:scale:uniform";
+ break;
+ case COMMIT_KEYBOARD_SCALE_X:
+ reason = _("Scale nodes horizontally");
+ key = "node:scale:x";
+ break;
+ case COMMIT_KEYBOARD_SCALE_Y:
+ reason = _("Scale nodes vertically");
+ key = "node:scale:y";
+ break;
+ case COMMIT_FLIP_X:
+ reason = _("Flip nodes horizontally");
+ break;
+ case COMMIT_FLIP_Y:
+ reason = _("Flip nodes vertically");
+ break;
+ default: return;
+ }
+
+ _selection.signal_update.emit();
+ invokeForAll(&PathManipulator::writeXML);
+ if (key) {
+ sp_document_maybe_done(sp_desktop_document(_desktop), key, SP_VERB_CONTEXT_NODE, reason);
+ } else {
+ sp_document_done(sp_desktop_document(_desktop), SP_VERB_CONTEXT_NODE, reason);
+ }
+ signal_coords_changed.emit();
+}
+
+/** Commits changes to XML and adds undo stack entry. */
+void MultiPathManipulator::_done(gchar const *reason) {
+ invokeForAll(&PathManipulator::update);
+ invokeForAll(&PathManipulator::writeXML);
+ sp_document_done(sp_desktop_document(_desktop), SP_VERB_CONTEXT_NODE, reason);
+ signal_coords_changed.emit();
+}
+
+/** Commits changes to XML, adds undo stack entry and removes empty manipulators. */
+void MultiPathManipulator::_doneWithCleanup(gchar const *reason) {
+ _changed.block();
+ _done(reason);
+ cleanup();
+ _changed.unblock();
+}
+
+/** Get an outline color based on the shape's role (normal, mask, LPE parameter, etc.). */
+guint32 MultiPathManipulator::_getOutlineColor(ShapeRole role)
+{
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ switch(role) {
+ case SHAPE_ROLE_CLIPPING_PATH:
+ return prefs->getColor("/tools/nodes/clipping_path_color", 0x00ff00ff);
+ case SHAPE_ROLE_MASK:
+ return prefs->getColor("/tools/nodes/mask_color", 0x0000ffff);
+ case SHAPE_ROLE_LPE_PARAM:
+ return prefs->getColor("/tools/nodes/lpe_param_color", 0x009000ff);
+ case SHAPE_ROLE_NORMAL:
+ default:
+ return prefs->getColor("/tools/nodes/outline_color", 0xff0000ff);
+ }
+}
+
+} // 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:encoding=utf-8:textwidth=99 :
diff --git a/src/ui/tool/multi-path-manipulator.h b/src/ui/tool/multi-path-manipulator.h
new file mode 100644
index 000000000..181ae6d1d
--- /dev/null
+++ b/src/ui/tool/multi-path-manipulator.h
@@ -0,0 +1,138 @@
+/** @file
+ * Multi path manipulator - a tool component that edits multiple paths at once
+ */
+/* Authors:
+ * Krzysztof Kosiński <tweenk.pl@gmail.com>
+ *
+ * Copyright (C) 2009 Authors
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ */
+
+#ifndef SEEN_UI_TOOL_MULTI_PATH_MANIPULATOR_H
+#define SEEN_UI_TOOL_MULTI_PATH_MANIPULATOR_H
+
+#include <sigc++/connection.h>
+#include "display/display-forward.h"
+#include "forward.h"
+#include "ui/tool/commit-events.h"
+#include "ui/tool/manipulator.h"
+#include "ui/tool/modifier-tracker.h"
+#include "ui/tool/node.h"
+#include "ui/tool/node-types.h"
+#include "ui/tool/shape-record.h"
+
+struct SPCanvasGroup;
+
+namespace Inkscape {
+namespace UI {
+
+class PathManipulator;
+class MultiPathManipulator;
+struct PathSharedData;
+
+/**
+ * Manipulator that manages multiple path manipulators active at the same time.
+ */
+class MultiPathManipulator : public PointManipulator {
+public:
+ MultiPathManipulator(PathSharedData &data, sigc::connection &chg);
+ virtual ~MultiPathManipulator();
+ virtual bool event(GdkEvent *event);
+
+ bool empty() { return _mmap.empty(); }
+ unsigned size() { return _mmap.empty(); }
+ void setItems(std::set<ShapeRecord> const &);
+ void clear() { _mmap.clear(); }
+ void cleanup();
+
+ void selectSubpaths();
+ void shiftSelection(int dir);
+ void invertSelectionInSubpaths();
+
+ void setNodeType(NodeType t);
+ void setSegmentType(SegmentType t);
+
+ void insertNodes();
+ void joinNodes();
+ void breakNodes();
+ void deleteNodes(bool keep_shape = true);
+ void joinSegments();
+ void deleteSegments();
+ void alignNodes(Geom::Dim2 d);
+ void distributeNodes(Geom::Dim2 d);
+ void reverseSubpaths();
+ void move(Geom::Point const &delta);
+
+ void showOutline(bool show);
+ void showHandles(bool show);
+ void showPathDirection(bool show);
+ void setLiveOutline(bool set);
+ void setLiveObjects(bool set);
+ void updateOutlineColors();
+
+ sigc::signal<void> signal_coords_changed; /// Emitted whenever the coordinates
+ /// shown in the status bar need updating
+private:
+ typedef std::pair<ShapeRecord, boost::shared_ptr<PathManipulator> > MapPair;
+ typedef std::map<ShapeRecord, boost::shared_ptr<PathManipulator> > MapType;
+
+ template <typename R>
+ void invokeForAll(R (PathManipulator::*method)()) {
+ for (MapType::iterator i = _mmap.begin(); i != _mmap.end(); ++i) {
+ ((i->second.get())->*method)();
+ }
+ }
+ template <typename R, typename A>
+ void invokeForAll(R (PathManipulator::*method)(A), A a) {
+ for (MapType::iterator i = _mmap.begin(); i != _mmap.end(); ++i) {
+ ((i->second.get())->*method)(a);
+ }
+ }
+ template <typename R, typename A>
+ void invokeForAll(R (PathManipulator::*method)(A const &), A const &a) {
+ for (MapType::iterator i = _mmap.begin(); i != _mmap.end(); ++i) {
+ ((i->second.get())->*method)(a);
+ }
+ }
+ template <typename R, typename A, typename B>
+ void invokeForAll(R (PathManipulator::*method)(A,B), A a, B b) {
+ for (MapType::iterator i = _mmap.begin(); i != _mmap.end(); ++i) {
+ ((i->second.get())->*method)(a, b);
+ }
+ }
+
+ void _commit(CommitEvent cps);
+ void _done(gchar const *);
+ void _doneWithCleanup(gchar const *);
+ guint32 _getOutlineColor(ShapeRole role);
+
+ MapType _mmap;
+public:
+ PathSharedData const &_path_data;
+private:
+ sigc::connection &_changed;
+ ModifierTracker _tracker;
+ bool _show_handles;
+ bool _show_outline;
+ bool _show_path_direction;
+ bool _live_outline;
+ bool _live_objects;
+
+ friend class PathManipulator;
+};
+
+} // namespace UI
+} // namespace Inkscape
+
+#endif
+
+/*
+ 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:encoding=utf-8:textwidth=99 :
diff --git a/src/ui/tool/node-tool.cpp b/src/ui/tool/node-tool.cpp
new file mode 100644
index 000000000..443e7f258
--- /dev/null
+++ b/src/ui/tool/node-tool.cpp
@@ -0,0 +1,651 @@
+/** @file
+ * @brief New node tool - implementation
+ */
+/* Authors:
+ * Krzysztof Kosiński <tweenk@gmail.com>
+ *
+ * Copyright (C) 2009 Authors
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ */
+
+#include <glib.h>
+#include <glib/gi18n.h>
+#include "desktop.h"
+#include "desktop-handles.h"
+#include "display/canvas-bpath.h"
+#include "display/curve.h"
+#include "display/sp-canvas.h"
+#include "document.h"
+#include "live_effects/lpeobject.h"
+#include "message-context.h"
+#include "selection.h"
+#include "shape-editor.h" // temporary!
+#include "sp-clippath.h"
+#include "sp-item-group.h"
+#include "sp-mask.h"
+#include "sp-object-group.h"
+#include "sp-path.h"
+#include "ui/tool/node-tool.h"
+#include "ui/tool/control-point-selection.h"
+#include "ui/tool/curve-drag-point.h"
+#include "ui/tool/event-utils.h"
+#include "ui/tool/manipulator.h"
+#include "ui/tool/multi-path-manipulator.h"
+#include "ui/tool/path-manipulator.h"
+#include "ui/tool/selector.h"
+#include "ui/tool/shape-record.h"
+
+#include "pixmaps/cursor-node.xpm"
+#include "pixmaps/cursor-node-d.xpm"
+
+/** @struct InkNodeTool
+ *
+ * Node tool event context.
+ *
+ * @par Architectural overview of the tool
+ * @par
+ * Here's a breakdown of what each object does.
+ * - Handle: shows a handle and keeps the node type constraint (smooth / symmetric) by updating
+ * the other handle's position when dragged. Its move() method cannot violate the constraints.
+ * - Node: keeps node type constraints for auto nodes and smooth nodes at ends of linear segments.
+ * Its move() method cannot violate constraints. Handles linear grow and dispatches spatial grow
+ * to MultiPathManipulator. Keeps a reference to its NodeList.
+ * - NodeList: exposes an iterator-based interface to nodes. It is possible to obtain an iterator
+ * to a node from the node. Keeps a reference to its SubpathList.
+ * - SubpathList: list of NodeLists that represents an editable pathvector. Keeps a reference
+ * to its PathManipulator.
+ * - PathManipulator: performs most of the single-path actions like reverse subpaths,
+ * delete segment, shift selection, etc. Keeps a reference to MultiPathManipulator.
+ * - MultiPathManipulator: performs additional operations for actions that are not per-path,
+ * for example node joins and segment joins. Tracks the control transforms for PMs that edit
+ * clipping paths and masks. It is more or less equivalent to ShapeEditor and in the future
+ * it might handle all shapes. Handles XML commit of actions that affect all paths or
+ * the node selection and removes PathManipulators that have no nodes left after e.g. node
+ * deletes.
+ * - ControlPointSelection: keeps track of node selection and a set of nodes that can potentially
+ * be selected. There can be more than one selection. Performs actions that require no
+ * knowledge about the path, only about the nodes, like dragging and transforms. It is not
+ * specific to nodes and can accomodate any control point derived from SelectableControlPoint.
+ * Transforms nodes in response to transform handle events.
+ * - TransformHandleSet: displays nodeset transform handles and emits transform events. The aim
+ * is to eventually use a common class for object and control point transforms.
+ * - SelectableControlPoint: base for any type of selectable point. It can belong to only one
+ * selection.
+ *
+ * @par Plans for the future
+ * @par
+ * - MultiPathManipulator should become a generic shape editor that manages all active manipulator,
+ * more or less like the old ShapeEditor.
+ * - Knotholder should be rewritten into one manipulator class per shape, using the control point
+ * classes. Interesting features like dragging rectangle sides could be added along the way.
+ * - Better handling of clip and mask editing, particularly in response to undo.
+ * - High level refactoring of the event context hierarchy. All aspects of tools, like toolbox
+ * controls, icons, event handling should be collected in one class, though each aspect
+ * of a tool might be in an separate class for better modularity. The long term goal is to allow
+ * tools to be defined in extensions or shared library plugins.
+ */
+
+namespace {
+SPCanvasGroup *create_control_group(SPDesktop *d);
+void ink_node_tool_class_init(InkNodeToolClass *klass);
+void ink_node_tool_init(InkNodeTool *node_context);
+void ink_node_tool_dispose(GObject *object);
+
+void ink_node_tool_setup(SPEventContext *ec);
+gint ink_node_tool_root_handler(SPEventContext *event_context, GdkEvent *event);
+gint ink_node_tool_item_handler(SPEventContext *event_context, SPItem *item, GdkEvent *event);
+void ink_node_tool_set(SPEventContext *ec, Inkscape::Preferences::Entry *value);
+
+void ink_node_tool_update_tip(InkNodeTool *nt, GdkEvent *event);
+void ink_node_tool_selection_changed(InkNodeTool *nt, Inkscape::Selection *sel);
+void ink_node_tool_select_area(InkNodeTool *nt, Geom::Rect const &, GdkEventButton *);
+void ink_node_tool_select_point(InkNodeTool *nt, Geom::Point const &, GdkEventButton *);
+void ink_node_tool_mouseover_changed(InkNodeTool *nt, Inkscape::UI::ControlPoint *p);
+} // anonymous namespace
+
+GType ink_node_tool_get_type()
+{
+ static GType type = 0;
+ if (!type) {
+ GTypeInfo info = {
+ sizeof(InkNodeToolClass),
+ NULL, NULL,
+ (GClassInitFunc) ink_node_tool_class_init,
+ NULL, NULL,
+ sizeof(InkNodeTool),
+ 4,
+ (GInstanceInitFunc) ink_node_tool_init,
+ NULL, /* value_table */
+ };
+ type = g_type_register_static(SP_TYPE_EVENT_CONTEXT, "InkNodeTool", &info, (GTypeFlags)0);
+ }
+ return type;
+}
+
+namespace {
+
+SPCanvasGroup *create_control_group(SPDesktop *d)
+{
+ return reinterpret_cast<SPCanvasGroup*>(sp_canvas_item_new(
+ sp_desktop_controls(d), SP_TYPE_CANVAS_GROUP, NULL));
+}
+
+void destroy_group(SPCanvasGroup *g)
+{
+ gtk_object_destroy(GTK_OBJECT(g));
+}
+
+void ink_node_tool_class_init(InkNodeToolClass *klass)
+{
+ GObjectClass *object_class = (GObjectClass *) klass;
+ SPEventContextClass *event_context_class = (SPEventContextClass *) klass;
+
+ object_class->dispose = ink_node_tool_dispose;
+
+ event_context_class->setup = ink_node_tool_setup;
+ event_context_class->set = ink_node_tool_set;
+ event_context_class->root_handler = ink_node_tool_root_handler;
+ event_context_class->item_handler = ink_node_tool_item_handler;
+}
+
+void ink_node_tool_init(InkNodeTool *nt)
+{
+ SPEventContext *event_context = SP_EVENT_CONTEXT(nt);
+
+ event_context->cursor_shape = cursor_node_xpm;
+ event_context->hot_x = 1;
+ event_context->hot_y = 1;
+
+ new (&nt->_selection_changed_connection) sigc::connection();
+ new (&nt->_selection_modified_connection) sigc::connection();
+ new (&nt->_mouseover_changed_connection) sigc::connection();
+ //new (&nt->_mgroup) Inkscape::UI::ManipulatorGroup(nt->desktop);
+ new (&nt->_selected_nodes) CSelPtr();
+ new (&nt->_multipath) MultiPathPtr();
+ new (&nt->_selector) SelectorPtr();
+ new (&nt->_path_data) PathSharedDataPtr();
+}
+
+void ink_node_tool_dispose(GObject *object)
+{
+ InkNodeTool *nt = INK_NODE_TOOL(object);
+
+ nt->enableGrDrag(false);
+
+ nt->_selection_changed_connection.disconnect();
+ nt->_selection_modified_connection.disconnect();
+ nt->_mouseover_changed_connection.disconnect();
+ nt->_multipath.~MultiPathPtr();
+ nt->_selected_nodes.~CSelPtr();
+ nt->_selector.~SelectorPtr();
+
+ Inkscape::UI::PathSharedData &data = *nt->_path_data;
+ destroy_group(data.node_data.node_group);
+ destroy_group(data.node_data.handle_group);
+ destroy_group(data.node_data.handle_line_group);
+ destroy_group(data.outline_group);
+ destroy_group(data.dragpoint_group);
+ destroy_group(nt->_transform_handle_group);
+
+ nt->_path_data.~PathSharedDataPtr();
+ nt->_selection_changed_connection.~connection();
+ nt->_selection_modified_connection.~connection();
+ nt->_mouseover_changed_connection.~connection();
+
+ if (nt->_node_message_context) {
+ delete nt->_node_message_context;
+ }
+ if (nt->shape_editor) {
+ nt->shape_editor->unset_item(SH_KNOTHOLDER);
+ delete nt->shape_editor;
+ }
+
+ G_OBJECT_CLASS(g_type_class_peek(g_type_parent(INK_TYPE_NODE_TOOL)))->dispose(object);
+}
+
+void ink_node_tool_setup(SPEventContext *ec)
+{
+ InkNodeTool *nt = INK_NODE_TOOL(ec);
+
+ SPEventContextClass *parent = (SPEventContextClass *) g_type_class_peek(g_type_parent(INK_TYPE_NODE_TOOL));
+ if (parent->setup) parent->setup(ec);
+
+ nt->_node_message_context = new Inkscape::MessageContext((ec->desktop)->messageStack());
+
+ nt->_path_data.reset(new Inkscape::UI::PathSharedData());
+ Inkscape::UI::PathSharedData &data = *nt->_path_data;
+ data.node_data.desktop = nt->desktop;
+
+ // selector has to be created here, so that its hidden control point is on the bottom
+ nt->_selector.reset(new Inkscape::UI::Selector(nt->desktop));
+
+ // Prepare canvas groups for controls. This guarantees correct z-order, so that
+ // for example a dragpoint won't obscure a node
+ data.outline_group = create_control_group(nt->desktop);
+ data.node_data.handle_line_group = create_control_group(nt->desktop);
+ data.dragpoint_group = create_control_group(nt->desktop);
+ nt->_transform_handle_group = create_control_group(nt->desktop);
+ data.node_data.node_group = create_control_group(nt->desktop);
+ data.node_data.handle_group = create_control_group(nt->desktop);
+
+ Inkscape::Selection *selection = sp_desktop_selection (ec->desktop);
+ nt->_selection_changed_connection.disconnect();
+ nt->_selection_changed_connection =
+ selection->connectChanged(
+ sigc::bind<0>(
+ sigc::ptr_fun(&ink_node_tool_selection_changed),
+ nt));
+ /*nt->_selection_modified_connection.disconnect();
+ nt->_selection_modified_connection =
+ selection->connectModified(
+ sigc::hide(sigc::bind<0>(
+ sigc::ptr_fun(&ink_node_tool_selection_modified),
+ nt)));*/
+ nt->_mouseover_changed_connection.disconnect();
+ nt->_mouseover_changed_connection =
+ Inkscape::UI::ControlPoint::signal_mouseover_change.connect(
+ sigc::bind<0>(
+ sigc::ptr_fun(&ink_node_tool_mouseover_changed),
+ nt));
+
+ nt->_selected_nodes.reset(
+ new Inkscape::UI::ControlPointSelection(nt->desktop, nt->_transform_handle_group));
+ data.node_data.selection = nt->_selected_nodes.get();
+ nt->_multipath.reset(new Inkscape::UI::MultiPathManipulator(data,
+ nt->_selection_changed_connection));
+
+ nt->_selector->signal_point.connect(
+ sigc::bind<0>(
+ sigc::ptr_fun(&ink_node_tool_select_point),
+ nt));
+ nt->_selector->signal_area.connect(
+ sigc::bind<0>(
+ sigc::ptr_fun(&ink_node_tool_select_area),
+ nt));
+
+ nt->_multipath->signal_coords_changed.connect(
+ sigc::bind(
+ sigc::mem_fun(*nt->desktop, &SPDesktop::emitToolSubselectionChanged),
+ (void*) 0));
+ nt->_selected_nodes->signal_point_changed.connect(
+ sigc::hide( sigc::hide(
+ sigc::bind(
+ sigc::bind(
+ sigc::ptr_fun(ink_node_tool_update_tip),
+ (GdkEvent*)0),
+ nt))));
+
+ nt->cursor_drag = false;
+ nt->show_transform_handles = true;
+ nt->single_node_transform_handles = false;
+ nt->flash_tempitem = NULL;
+ nt->flashed_item = NULL;
+ nt->_last_over = NULL;
+ // TODO long term, fold ShapeEditor into MultiPathManipulator and rename MPM
+ // to something better
+ nt->shape_editor = new ShapeEditor(nt->desktop);
+
+ // read prefs before adding items to selection to prevent momentarily showing the outline
+ sp_event_context_read(nt, "show_handles");
+ sp_event_context_read(nt, "show_outline");
+ sp_event_context_read(nt, "live_outline");
+ sp_event_context_read(nt, "live_objects");
+ sp_event_context_read(nt, "show_path_direction");
+ sp_event_context_read(nt, "show_transform_handles");
+ sp_event_context_read(nt, "single_node_transform_handles");
+ sp_event_context_read(nt, "edit_clipping_paths");
+ sp_event_context_read(nt, "edit_masks");
+
+ ink_node_tool_selection_changed(nt, selection);
+ ink_node_tool_update_tip(nt, NULL);
+
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ if (prefs->getBool("/tools/nodes/selcue")) {
+ ec->enableSelectionCue();
+ }
+ if (prefs->getBool("/tools/nodes/gradientdrag")) {
+ ec->enableGrDrag();
+ }
+
+ nt->desktop->emitToolSubselectionChanged(NULL); // sets the coord entry fields to inactive
+}
+
+void ink_node_tool_set(SPEventContext *ec, Inkscape::Preferences::Entry *value)
+{
+ InkNodeTool *nt = INK_NODE_TOOL(ec);
+ Glib::ustring entry_name = value->getEntryName();
+
+ if (entry_name == "show_handles") {
+ nt->_multipath->showHandles(value->getBool(true));
+ } else if (entry_name == "show_outline") {
+ nt->show_outline = value->getBool();
+ nt->_multipath->showOutline(nt->show_outline);
+ } else if (entry_name == "live_outline") {
+ nt->live_outline = value->getBool();
+ nt->_multipath->setLiveOutline(nt->live_outline);
+ } else if (entry_name == "live_objects") {
+ nt->live_objects = value->getBool();
+ nt->_multipath->setLiveObjects(nt->live_objects);
+ } else if (entry_name == "show_path_direction") {
+ nt->show_path_direction = value->getBool();
+ nt->_multipath->showPathDirection(nt->show_path_direction);
+ } else if (entry_name == "show_transform_handles") {
+ nt->show_transform_handles = value->getBool(true);
+ nt->_selected_nodes->showTransformHandles(
+ nt->show_transform_handles, nt->single_node_transform_handles);
+ } else if (entry_name == "single_node_transform_handles") {
+ nt->single_node_transform_handles = value->getBool();
+ nt->_selected_nodes->showTransformHandles(
+ nt->show_transform_handles, nt->single_node_transform_handles);
+ } else if (entry_name == "edit_clipping_paths") {
+ nt->edit_clipping_paths = value->getBool();
+ ink_node_tool_selection_changed(nt, nt->desktop->selection);
+ } else if (entry_name == "edit_masks") {
+ nt->edit_masks = value->getBool();
+ ink_node_tool_selection_changed(nt, nt->desktop->selection);
+ } else {
+ SPEventContextClass *parent_class =
+ (SPEventContextClass *) g_type_class_peek(g_type_parent(INK_TYPE_NODE_TOOL));
+ if (parent_class->set)
+ parent_class->set(ec, value);
+ }
+}
+
+/** Recursively collect ShapeRecords */
+void gather_items(InkNodeTool *nt, SPItem *base, SPObject *obj, Inkscape::UI::ShapeRole role,
+ std::set<Inkscape::UI::ShapeRecord> &s)
+{
+ using namespace Inkscape::UI;
+ if (!obj) return;
+
+ if (SP_IS_PATH(obj) && obj->repr->attribute("inkscape:original-d") != NULL) {
+ ShapeRecord r;
+ r.item = static_cast<SPItem*>(obj);
+ r.edit_transform = Geom::identity(); // TODO wrong?
+ r.role = role;
+ s.insert(r);
+ } else if (role != SHAPE_ROLE_NORMAL && (SP_IS_GROUP(obj) || SP_IS_OBJECTGROUP(obj))) {
+ for (SPObject *c = obj->children; c; c = c->next) {
+ gather_items(nt, base, c, role, s);
+ }
+ } else if (SP_IS_ITEM(obj)) {
+ SPItem *item = static_cast<SPItem*>(obj);
+ ShapeRecord r;
+ r.item = item;
+ // TODO add support for objectBoundingBox
+ r.edit_transform = base ? sp_item_i2doc_affine(base) : Geom::identity();
+ r.role = role;
+ if (s.insert(r).second) {
+ // this item was encountered the first time
+ if (nt->edit_clipping_paths && item->clip_ref) {
+ gather_items(nt, item, item->clip_ref->getObject(), SHAPE_ROLE_CLIPPING_PATH, s);
+ }
+ if (nt->edit_masks && item->mask_ref) {
+ gather_items(nt, item, item->mask_ref->getObject(), SHAPE_ROLE_MASK, s);
+ }
+ }
+ }
+}
+
+void ink_node_tool_selection_changed(InkNodeTool *nt, Inkscape::Selection *sel)
+{
+ using namespace Inkscape::UI;
+
+ std::set<ShapeRecord> shapes;
+
+ GSList const *ilist = sel->itemList();
+
+ for (GSList *i = const_cast<GSList*>(ilist); i; i = i->next) {
+ SPObject *obj = static_cast<SPObject*>(i->data);
+ if (SP_IS_ITEM(obj)) {
+ gather_items(nt, NULL, static_cast<SPItem*>(obj), SHAPE_ROLE_NORMAL, shapes);
+ }
+ }
+
+ // ugly hack: set the first editable non-path item for knotholder
+ // maybe use multiple ShapeEditors for now, to allow editing many shapes at once?
+ bool something_set = false;
+ for (std::set<ShapeRecord>::iterator i = shapes.begin(); i != shapes.end(); ++i) {
+ ShapeRecord const &r = *i;
+ if (SP_IS_SHAPE(r.item) ||
+ (SP_IS_PATH(r.item) && r.item->repr->attribute("inkscape:original-d") != NULL))
+ {
+ nt->shape_editor->set_item(r.item, SH_KNOTHOLDER);
+ something_set = true;
+ break;
+ }
+ }
+ if (!something_set) {
+ nt->shape_editor->unset_item(SH_KNOTHOLDER);
+ }
+
+ nt->_multipath->setItems(shapes);
+ ink_node_tool_update_tip(nt, NULL);
+ nt->desktop->updateNow();
+}
+
+gint ink_node_tool_root_handler(SPEventContext *event_context, GdkEvent *event)
+{
+ /* things to handle here:
+ * 1. selection of items
+ * 2. passing events to manipulators
+ * 3. some keybindings
+ */
+ using namespace Inkscape::UI; // pull in event helpers
+
+ SPDesktop *desktop = event_context->desktop;
+ Inkscape::Selection *selection = desktop->selection;
+ InkNodeTool *nt = static_cast<InkNodeTool*>(event_context);
+ static Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+
+ if (nt->_multipath->event(event)) return true;
+ if (nt->_selector->event(event)) return true;
+ if (nt->_selected_nodes->event(event)) return true;
+
+ switch (event->type)
+ {
+ case GDK_MOTION_NOTIFY: {
+ combine_motion_events(desktop->canvas, event->motion, 0);
+ SPItem *over_item = sp_event_context_find_item (desktop, event_point(event->button),
+ FALSE, TRUE);
+ if (over_item != nt->_last_over) {
+ nt->_last_over = over_item;
+ ink_node_tool_update_tip(nt, event);
+ }
+
+ // create pathflash outline
+ if (prefs->getBool("/tools/nodes/pathflash_enabled")) {
+ if (over_item == nt->flashed_item) break;
+ if (!prefs->getBool("/tools/nodes/pathflash_selected") && selection->includes(over_item)) break;
+ if (nt->flash_tempitem) {
+ desktop->remove_temporary_canvasitem(nt->flash_tempitem);
+ nt->flash_tempitem = NULL;
+ nt->flashed_item = NULL;
+ }
+ if (!SP_IS_PATH(over_item)) break; // for now, handle only paths
+
+ nt->flashed_item = over_item;
+ SPCurve *c = sp_path_get_curve_for_edit(SP_PATH(over_item));
+ c->transform(sp_item_i2d_affine(over_item));
+ SPCanvasItem *flash = sp_canvas_bpath_new(sp_desktop_tempgroup(desktop), c);
+ sp_canvas_bpath_set_stroke(SP_CANVAS_BPATH(flash),
+ prefs->getInt("/tools/nodes/highlight_color", 0xff0000ff), 1.0,
+ SP_STROKE_LINEJOIN_MITER, SP_STROKE_LINECAP_BUTT);
+ sp_canvas_bpath_set_fill(SP_CANVAS_BPATH(flash), 0, SP_WIND_RULE_NONZERO);
+ nt->flash_tempitem = desktop->add_temporary_canvasitem(flash,
+ prefs->getInt("/tools/nodes/pathflash_timeout", 500));
+ c->unref();
+ }
+ } break; // do not return true, because we need to pass this event to the parent context
+ // otherwise some features cease to work
+
+ case GDK_KEY_PRESS:
+ switch (get_group0_keyval(&event->key))
+ {
+ case GDK_Escape: // deselect everything
+ if (nt->_selected_nodes->empty()) {
+ selection->clear();
+ } else {
+ nt->_selected_nodes->clear();
+ }
+ ink_node_tool_update_tip(nt, event);
+ return TRUE;
+ case GDK_a:
+ if (held_control(event->key) && held_alt(event->key)) {
+ nt->_selected_nodes->selectAll();
+ // Ctrl+A is handled in selection-chemistry.cpp via verb
+ ink_node_tool_update_tip(nt, event);
+ return TRUE;
+ }
+ break;
+ default:
+ break;
+ }
+ ink_node_tool_update_tip(nt, event);
+ break;
+ case GDK_KEY_RELEASE:
+ ink_node_tool_update_tip(nt, event);
+ break;
+ default: break;
+ }
+
+ SPEventContextClass *parent_class = (SPEventContextClass *) g_type_class_peek(g_type_parent(INK_TYPE_NODE_TOOL));
+ if (parent_class->root_handler)
+ return parent_class->root_handler(event_context, event);
+ return FALSE;
+}
+
+void ink_node_tool_update_tip(InkNodeTool *nt, GdkEvent *event)
+{
+ using namespace Inkscape::UI;
+ if (event && (event->type == GDK_KEY_PRESS || event->type == GDK_KEY_RELEASE)) {
+ unsigned new_state = state_after_event(event);
+ if (new_state == event->key.state) return;
+ if (state_held_shift(new_state)) {
+ if (nt->_last_over) {
+ nt->_node_message_context->set(Inkscape::NORMAL_MESSAGE,
+ C_("Node tool tip", "<b>Shift:</b> drag to add nodes to the selection, "
+ "click to toggle object selection"));
+ } else {
+ nt->_node_message_context->set(Inkscape::NORMAL_MESSAGE,
+ C_("Node tool tip", "<b>Shift:</b> drag to add nodes to the selection"));
+ }
+ return;
+ }
+ }
+ unsigned sz = nt->_selected_nodes->size();
+ unsigned total = nt->_selected_nodes->allPoints().size();
+ if (sz != 0) {
+ if (nt->_last_over) {
+ char *dyntip = g_strdup_printf(C_("Node tool tip",
+ "<b>%u of %u nodes</b> selected. "
+ "Drag to select nodes, click to edit only this object (more: Shift)"), sz, total);
+ nt->_node_message_context->set(Inkscape::NORMAL_MESSAGE, dyntip);
+ g_free(dyntip);
+ } else {
+ char *dyntip = g_strdup_printf(C_("Node tool tip",
+ "<b>%u of %u nodes</b> selected. "
+ "Drag to select nodes, click clear the selection"), sz, total);
+ nt->_node_message_context->set(Inkscape::NORMAL_MESSAGE, dyntip);
+ g_free(dyntip);
+ }
+ } else if (!nt->_multipath->empty()) {
+ if (nt->_last_over) {
+ nt->_node_message_context->set(Inkscape::NORMAL_MESSAGE, C_("Node tool tip",
+ "Drag to select nodes, click to edit only this object"));
+ } else {
+ nt->_node_message_context->set(Inkscape::NORMAL_MESSAGE, C_("Node tool tip",
+ "Drag to select nodes, click to clear the selection"));
+ }
+ } else {
+ if (nt->_last_over) {
+ nt->_node_message_context->set(Inkscape::NORMAL_MESSAGE, C_("Node tool tip",
+ "Drag to select objects to edit, click to edit this object (more: Shift)"));
+ } else {
+ nt->_node_message_context->set(Inkscape::NORMAL_MESSAGE, C_("Node tool tip",
+ "Drag to select objects to edit"));
+ }
+ }
+}
+
+gint ink_node_tool_item_handler(SPEventContext *event_context, SPItem *item, GdkEvent *event)
+{
+ SPEventContextClass *parent_class =
+ (SPEventContextClass *) g_type_class_peek(g_type_parent(INK_TYPE_NODE_TOOL));
+ if (parent_class->item_handler)
+ return parent_class->item_handler(event_context, item, event);
+ return FALSE;
+}
+
+void ink_node_tool_select_area(InkNodeTool *nt, Geom::Rect const &sel, GdkEventButton *event)
+{
+ using namespace Inkscape::UI;
+ if (nt->_multipath->empty()) {
+ // if multipath is empty, select rubberbanded items rather than nodes
+ Inkscape::Selection *selection = nt->desktop->selection;
+ GSList *items = sp_document_items_in_box(
+ sp_desktop_document(nt->desktop), nt->desktop->dkey, sel);
+ selection->setList(items);
+ g_slist_free(items);
+ } else {
+ if (!held_shift(*event)) nt->_selected_nodes->clear();
+ nt->_selected_nodes->selectArea(sel);
+ }
+}
+void ink_node_tool_select_point(InkNodeTool *nt, Geom::Point const &/*sel*/, GdkEventButton *event)
+{
+ using namespace Inkscape::UI; // pull in event helpers
+ if (!event) return;
+ if (event->button != 1) return;
+
+ Inkscape::Selection *selection = nt->desktop->selection;
+
+ SPItem *item_clicked = sp_event_context_find_item (nt->desktop, event_point(*event),
+ (event->state & GDK_MOD1_MASK) && !(event->state & GDK_CONTROL_MASK), TRUE);
+
+ if (item_clicked == NULL) { // nothing under cursor
+ // if no Shift, deselect
+ if (!(event->state & GDK_SHIFT_MASK)) {
+ selection->clear();
+ }
+ } else {
+ if (held_shift(*event)) {
+ selection->toggle(item_clicked);
+ } else {
+ selection->set(item_clicked);
+ }
+ nt->desktop->updateNow();
+ }
+}
+
+void ink_node_tool_mouseover_changed(InkNodeTool *nt, Inkscape::UI::ControlPoint *p)
+{
+ using Inkscape::UI::CurveDragPoint;
+ CurveDragPoint *cdp = dynamic_cast<CurveDragPoint*>(p);
+ if (cdp && !nt->cursor_drag) {
+ nt->cursor_shape = cursor_node_d_xpm;
+ nt->hot_x = 1;
+ nt->hot_y = 1;
+ sp_event_context_update_cursor(nt);
+ nt->cursor_drag = true;
+ } else if (!cdp && nt->cursor_drag) {
+ nt->cursor_shape = cursor_node_xpm;
+ nt->hot_x = 1;
+ nt->hot_y = 1;
+ sp_event_context_update_cursor(nt);
+ nt->cursor_drag = false;
+ }
+}
+
+} // anonymous namespace
+
+/*
+ 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:encoding=utf-8:textwidth=99 :
diff --git a/src/ui/tool/node-tool.h b/src/ui/tool/node-tool.h
new file mode 100644
index 000000000..baac642ac
--- /dev/null
+++ b/src/ui/tool/node-tool.h
@@ -0,0 +1,88 @@
+/** @file
+ * @brief New node tool with support for multiple path editing
+ */
+/* Authors:
+ * Krzysztof Kosiński <tweenk@gmail.com>
+ *
+ * Copyright (C) 2009 Authors
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ */
+
+#ifndef SEEN_UI_TOOL_NODE_TOOL_H
+#define SEEN_UI_TOOL_NODE_TOOL_H
+
+#include <memory>
+#include <glib.h>
+#include <sigc++/sigc++.h>
+#include "event-context.h"
+#include "forward.h"
+#include "display/display-forward.h"
+#include "ui/tool/node-types.h"
+
+#define INK_TYPE_NODE_TOOL (ink_node_tool_get_type ())
+#define INK_NODE_TOOL(obj) (GTK_CHECK_CAST ((obj), INK_TYPE_NODE_TOOL, InkNodeTool))
+#define INK_NODE_TOOL_CLASS(klass) (GTK_CHECK_CLASS_CAST ((klass), INK_TYPE_NODE_TOOL, InkNodeToolClass))
+#define INK_IS_NODE_TOOL(obj) (GTK_CHECK_TYPE ((obj), INK_TYPE_NODE_TOOL))
+#define INK_IS_NODE_TOOL_CLASS(klass) (GTK_CHECK_CLASS_TYPE ((klass), INK_TYPE_NODE_TOOL))
+
+class InkNodeTool;
+class InkNodeToolClass;
+
+namespace Inkscape {
+namespace UI {
+class MultiPathManipulator;
+class ControlPointSelection;
+class Selector;
+struct PathSharedData;
+}
+}
+
+typedef std::auto_ptr<Inkscape::UI::MultiPathManipulator> MultiPathPtr;
+typedef std::auto_ptr<Inkscape::UI::ControlPointSelection> CSelPtr;
+typedef std::auto_ptr<Inkscape::UI::Selector> SelectorPtr;
+typedef std::auto_ptr<Inkscape::UI::PathSharedData> PathSharedDataPtr;
+
+struct InkNodeTool : public SPEventContext
+{
+ sigc::connection _selection_changed_connection;
+ sigc::connection _mouseover_changed_connection;
+ sigc::connection _selection_modified_connection;
+ Inkscape::MessageContext *_node_message_context;
+ SPItem *flashed_item;
+ Inkscape::Display::TemporaryItem *flash_tempitem;
+ CSelPtr _selected_nodes;
+ MultiPathPtr _multipath;
+ SelectorPtr _selector;
+ PathSharedDataPtr _path_data;
+ SPCanvasGroup *_transform_handle_group;
+ SPItem *_last_over;
+
+ unsigned cursor_drag : 1;
+ unsigned show_outline : 1;
+ unsigned live_outline : 1;
+ unsigned live_objects : 1;
+ unsigned show_path_direction : 1;
+ unsigned show_transform_handles : 1;
+ unsigned single_node_transform_handles : 1;
+ unsigned edit_clipping_paths : 1;
+ unsigned edit_masks : 1;
+};
+
+struct InkNodeToolClass {
+ SPEventContextClass parent_class;
+};
+
+GType ink_node_tool_get_type (void);
+
+#endif
+
+/*
+ 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:encoding=utf-8:textwidth=99 :
diff --git a/src/ui/tool/node-types.h b/src/ui/tool/node-types.h
new file mode 100644
index 000000000..80eaf4fa7
--- /dev/null
+++ b/src/ui/tool/node-types.h
@@ -0,0 +1,48 @@
+/** @file
+ * Node types and other small enums.
+ * This file exists to reduce the number of includes pulled in by toolbox.cpp.
+ */
+/* Authors:
+ * Krzysztof Kosiński <tweenk.pl@gmail.com>
+ *
+ * Copyright (C) 2009 Authors
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ */
+
+#ifndef SEEN_UI_TOOL_NODE_TYPES_H
+#define SEEN_UI_TOOL_NODE_TYPES_H
+
+namespace Inkscape {
+namespace UI {
+
+/** Types of nodes supported in the node tool. */
+enum NodeType {
+ NODE_CUSP, ///< Cusp node - no handle constraints
+ NODE_SMOOTH, ///< Smooth node - handles must be colinear
+ NODE_AUTO, ///< Auto node - handles adjusted automatically based on neighboring nodes
+ NODE_SYMMETRIC, ///< Symmetric node - handles must be colinear and of equal length
+ NODE_LAST_REAL_TYPE, ///< Last real type of node - used for ctrl+click on a node
+ NODE_PICK_BEST = 100 ///< Select type based on handle positions
+};
+
+/** Types of segments supported in the node tool. */
+enum SegmentType {
+ SEGMENT_STRAIGHT, ///< Straight linear segment
+ SEGMENT_CUBIC_BEZIER ///< Bezier curve with two control points
+};
+
+} // namespace UI
+} // namespace Inkscape
+
+#endif
+
+/*
+ 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:encoding=utf-8:textwidth=99 :
diff --git a/src/ui/tool/node.cpp b/src/ui/tool/node.cpp
new file mode 100644
index 000000000..e9fa79fb3
--- /dev/null
+++ b/src/ui/tool/node.cpp
@@ -0,0 +1,1407 @@
+/** @file
+ * Editable node - implementation
+ */
+/* Authors:
+ * Krzysztof Kosiński <tweenk.pl@gmail.com>
+ *
+ * Copyright (C) 2009 Authors
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ */
+
+#include <iostream>
+#include <stdexcept>
+#include <boost/utility.hpp>
+#include <glib.h>
+#include <glib/gi18n.h>
+#include <2geom/bezier-utils.h>
+#include <2geom/transforms.h>
+
+#include "display/sp-ctrlline.h"
+#include "display/sp-canvas.h"
+#include "display/sp-canvas-util.h"
+#include "desktop.h"
+#include "desktop-handles.h"
+#include "preferences.h"
+#include "snap.h"
+#include "snap-preferences.h"
+#include "sp-metrics.h"
+#include "sp-namedview.h"
+#include "ui/tool/control-point-selection.h"
+#include "ui/tool/event-utils.h"
+#include "ui/tool/multi-path-manipulator.h"
+#include "ui/tool/node.h"
+#include "ui/tool/path-manipulator.h"
+
+namespace Inkscape {
+namespace UI {
+
+static SelectableControlPoint::ColorSet node_colors = {
+ {
+ {0xbfbfbf00, 0x000000ff}, // normal fill, stroke
+ {0xff000000, 0x000000ff}, // mouseover fill, stroke
+ {0xff000000, 0x000000ff} // clicked fill, stroke
+ },
+ {0x0000ffff, 0x000000ff}, // normal fill, stroke when selected
+ {0xff000000, 0x000000ff}, // mouseover fill, stroke when selected
+ {0xff000000, 0x000000ff} // clicked fill, stroke when selected
+};
+
+static ControlPoint::ColorSet handle_colors = {
+ {0xffffffff, 0x000000ff}, // normal fill, stroke
+ {0xff000000, 0x000000ff}, // mouseover fill, stroke
+ {0xff000000, 0x000000ff} // clicked fill, stroke
+};
+
+std::ostream &operator<<(std::ostream &out, NodeType type)
+{
+ switch(type) {
+ case NODE_CUSP: out << 'c'; break;
+ case NODE_SMOOTH: out << 's'; break;
+ case NODE_AUTO: out << 'a'; break;
+ case NODE_SYMMETRIC: out << 'z'; break;
+ default: out << 'b'; break;
+ }
+ return out;
+}
+
+/** Computes an unit vector of the direction from first to second control point */
+static Geom::Point direction(Geom::Point const &first, Geom::Point const &second) {
+ return Geom::unit_vector(second - first);
+}
+
+/**
+ * @class Handle
+ * @brief Control point of a cubic Bezier curve in a path.
+ *
+ * Handle keeps the node type invariant only for the opposite handle of the same node.
+ * Keeping the invariant on node moves is left to the %Node class.
+ */
+
+Geom::Point Handle::_saved_other_pos(0, 0);
+double Handle::_saved_length = 0.0;
+bool Handle::_drag_out = false;
+
+Handle::Handle(NodeSharedData const &data, Geom::Point const &initial_pos, Node *parent)
+ : ControlPoint(data.desktop, initial_pos, Gtk::ANCHOR_CENTER, SP_CTRL_SHAPE_CIRCLE, 7.0,
+ &handle_colors, data.handle_group)
+ , _parent(parent)
+ , _degenerate(true)
+{
+ _cset = &handle_colors;
+ _handle_line = sp_canvas_item_new(data.handle_line_group, SP_TYPE_CTRLLINE, NULL);
+ setVisible(false);
+}
+Handle::~Handle()
+{
+ //sp_canvas_item_hide(_handle_line);
+ gtk_object_destroy(GTK_OBJECT(_handle_line));
+}
+
+void Handle::setVisible(bool v)
+{
+ ControlPoint::setVisible(v);
+ if (v) sp_canvas_item_show(_handle_line);
+ else sp_canvas_item_hide(_handle_line);
+}
+
+void Handle::move(Geom::Point const &new_pos)
+{
+ Handle *other = this->other();
+ Node *node_towards = _parent->nodeToward(this); // node in direction of this handle
+ Node *node_away = _parent->nodeAwayFrom(this); // node in the opposite direction
+ Handle *towards = node_towards ? node_towards->handleAwayFrom(_parent) : NULL;
+ Handle *towards_second = node_towards ? node_towards->handleToward(_parent) : NULL;
+
+ if (Geom::are_near(new_pos, _parent->position())) {
+ // The handle becomes degenerate. If the segment between it and the node
+ // in its direction becomes linear and there are smooth nodes
+ // at its ends, make their handles colinear with the segment
+ if (towards && towards->isDegenerate()) {
+ if (node_towards->type() == NODE_SMOOTH) {
+ towards_second->setDirection(*_parent, *node_towards);
+ }
+ if (_parent->type() == NODE_SMOOTH) {
+ other->setDirection(*node_towards, *_parent);
+ }
+ }
+ setPosition(new_pos);
+ return;
+ }
+
+ if (_parent->type() == NODE_SMOOTH && Node::_is_line_segment(_parent, node_away)) {
+ // restrict movement to the line joining the nodes
+ Geom::Point direction = _parent->position() - node_away->position();
+ Geom::Point delta = new_pos - _parent->position();
+ // project the relative position on the direction line
+ Geom::Point new_delta = (Geom::dot(delta, direction)
+ / Geom::L2sq(direction)) * direction;
+ setRelativePos(new_delta);
+ return;
+ }
+
+ switch (_parent->type()) {
+ case NODE_AUTO:
+ _parent->setType(NODE_SMOOTH, false);
+ // fall through - auto nodes degrade into smooth nodes
+ case NODE_SMOOTH: {
+ /* for smooth nodes, we need to rotate the other handle so that it's colinear
+ * with the dragged one while conserving length. */
+ other->setDirection(new_pos, *_parent);
+ } break;
+ case NODE_SYMMETRIC:
+ // for symmetric nodes, place the other handle on the opposite side
+ other->setRelativePos(-(new_pos - _parent->position()));
+ break;
+ default: break;
+ }
+
+ setPosition(new_pos);
+}
+
+void Handle::setPosition(Geom::Point const &p)
+{
+ ControlPoint::setPosition(p);
+ sp_ctrlline_set_coords(SP_CTRLLINE(_handle_line), _parent->position(), position());
+
+ // update degeneration info and visibility
+ if (Geom::are_near(position(), _parent->position()))
+ _degenerate = true;
+ else _degenerate = false;
+ if (_parent->_handles_shown && _parent->visible() && !_degenerate) {
+ setVisible(true);
+ } else {
+ setVisible(false);
+ }
+ // If both handles become degenerate, convert to parent cusp node
+ if (_parent->isDegenerate()) {
+ _parent->setType(NODE_CUSP, false);
+ }
+}
+
+void Handle::setLength(double len)
+{
+ if (isDegenerate()) return;
+ Geom::Point dir = Geom::unit_vector(relativePos());
+ setRelativePos(dir * len);
+}
+
+void Handle::retract()
+{
+ setPosition(_parent->position());
+}
+
+void Handle::setDirection(Geom::Point const &from, Geom::Point const &to)
+{
+ setDirection(to - from);
+}
+
+void Handle::setDirection(Geom::Point const &dir)
+{
+ Geom::Point unitdir = Geom::unit_vector(dir);
+ setRelativePos(unitdir * length());
+}
+
+char const *Handle::handle_type_to_localized_string(NodeType type)
+{
+ switch(type) {
+ case NODE_CUSP: return _("Cusp node handle");
+ case NODE_SMOOTH: return _("Smooth node handle");
+ case NODE_SYMMETRIC: return _("Symmetric node handle");
+ case NODE_AUTO: return _("Auto-smooth node handle");
+ default: return "";
+ }
+}
+
+bool Handle::grabbed(GdkEventMotion *)
+{
+ _saved_other_pos = other()->position();
+ _saved_length = _drag_out ? 0 : length();
+ _pm()._handleGrabbed();
+ return false;
+}
+
+void Handle::dragged(Geom::Point &new_pos, GdkEventMotion *event)
+{
+ Geom::Point parent_pos = _parent->position();
+ Geom::Point origin = _last_drag_origin();
+ SnapManager &sm = _desktop->namedview->snap_manager;
+ bool snap = sm.someSnapperMightSnap();
+
+ // with Alt, preserve length
+ if (held_alt(*event)) {
+ new_pos = parent_pos + Geom::unit_vector(new_pos - parent_pos) * _saved_length;
+ snap = false;
+ }
+ // with Ctrl, constrain to M_PI/rotationsnapsperpi increments from vertical
+ // and the original position.
+ if (held_control(*event)) {
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ int snaps = 2 * prefs->getIntLimited("/options/rotationsnapsperpi/value", 12, 1, 1000);
+
+ // note: if snapping to the original position is only desired in the original
+ // direction of the handle, change to Ray instead of Line
+ Geom::Line original_line(parent_pos, origin);
+ Geom::Point snap_pos = parent_pos + Geom::constrain_angle(
+ Geom::Point(0,0), new_pos - parent_pos, snaps, Geom::Point(1,0));
+ Geom::Point orig_pos = original_line.pointAt(original_line.nearestPoint(new_pos));
+
+ if (Geom::distance(snap_pos, new_pos) < Geom::distance(orig_pos, new_pos)) {
+ new_pos = snap_pos;
+ } else {
+ new_pos = orig_pos;
+ }
+ snap = false;
+ }
+
+ std::vector<Inkscape::SnapCandidatePoint> unselected;
+ if (snap) {
+ typedef ControlPointSelection::Set Set;
+ Set &nodes = _parent->_selection.allPoints();
+ for (Set::iterator i = nodes.begin(); i != nodes.end(); ++i) {
+ Node *n = static_cast<Node*>(*i);
+ Inkscape::SnapCandidatePoint p(n->position(), n->_snapSourceType(), n->_snapTargetType());
+ unselected.push_back(p);
+ }
+ sm.setupIgnoreSelection(_desktop, true, &unselected);
+
+ Node *node_away = (this == &_parent->_front ? _parent->_prev() : _parent->_next());
+ if (_parent->type() == NODE_SMOOTH && Node::_is_line_segment(_parent, node_away)) {
+ Inkscape::Snapper::ConstraintLine cl(_parent->position(),
+ _parent->position() - node_away->position());
+ Inkscape::SnappedPoint p;
+ p = sm.constrainedSnap(Inkscape::SnapCandidatePoint(new_pos, SNAPSOURCE_NODE_HANDLE), cl);
+ if (p.getSnapped()) {
+ p.getPoint(new_pos);
+ }
+ } else {
+ sm.freeSnapReturnByRef(new_pos, SNAPSOURCE_NODE_HANDLE);
+ }
+ }
+
+ // with Shift, if the node is cusp, rotate the other handle as well
+ if (_parent->type() == NODE_CUSP && !_drag_out) {
+ if (held_shift(*event)) {
+ Geom::Point other_relpos = _saved_other_pos - parent_pos;
+ other_relpos *= Geom::Rotate(Geom::angle_between(origin - parent_pos, new_pos - parent_pos));
+ other()->setRelativePos(other_relpos);
+ } else {
+ // restore the position
+ other()->setPosition(_saved_other_pos);
+ }
+ }
+ move(new_pos); // needed for correct update, even though it's redundant
+ _pm().update();
+}
+
+void Handle::ungrabbed(GdkEventButton *event)
+{
+ // hide the handle if it's less than dragtolerance away from the node
+ // TODO is this actually desired?
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ int drag_tolerance = prefs->getIntLimited("/options/dragtolerance/value", 0, 0, 100);
+
+ Geom::Point dist = _desktop->d2w(_parent->position()) - _desktop->d2w(position());
+ if (dist.length() <= drag_tolerance) {
+ move(_parent->position());
+ }
+
+ // HACK: If the handle was dragged out, call parent's ungrabbed handler,
+ // so that transform handles reappear
+ if (_drag_out) {
+ _parent->ungrabbed(event);
+ }
+ _drag_out = false;
+
+ _pm()._handleUngrabbed();
+}
+
+bool Handle::clicked(GdkEventButton *event)
+{
+ _pm()._handleClicked(this, event);
+ return true;
+}
+
+Handle *Handle::other()
+{
+ if (this == &_parent->_front) return &_parent->_back;
+ return &_parent->_front;
+}
+
+static double snap_increment_degrees() {
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ int snaps = prefs->getIntLimited("/options/rotationsnapsperpi/value", 12, 1, 1000);
+ return 180.0 / snaps;
+}
+
+Glib::ustring Handle::_getTip(unsigned state)
+{
+ char const *more;
+ bool can_shift_rotate = _parent->type() == NODE_CUSP && !other()->isDegenerate();
+ if (can_shift_rotate) {
+ more = C_("Path handle tip", "more: Shift, Ctrl, Alt");
+ } else {
+ more = C_("Path handle tip", "more: Ctrl, Alt");
+ }
+ if (state_held_alt(state)) {
+ if (state_held_control(state)) {
+ if (state_held_shift(state) && can_shift_rotate) {
+ return format_tip(C_("Path handle tip",
+ "<b>Shift+Ctrl+Alt</b>: preserve length and snap rotation angle to %g° "
+ "increments while rotating both handles"),
+ snap_increment_degrees());
+ } else {
+ return format_tip(C_("Path handle tip",
+ "<b>Ctrl+Alt</b>: preserve length and snap rotation angle to %g° increments"),
+ snap_increment_degrees());
+ }
+ } else {
+ if (state_held_shift(state) && can_shift_rotate) {
+ return C_("Path handle tip",
+ "<b>Shift+Alt:</b> preserve handle length and rotate both handles");
+ } else {
+ return C_("Path handle tip",
+ "<b>Alt:</b> preserve handle length while dragging");
+ }
+ }
+ } else {
+ if (state_held_control(state)) {
+ if (state_held_shift(state) && can_shift_rotate) {
+ return format_tip(C_("Path handle tip",
+ "<b>Shift+Ctrl:</b> snap rotation angle to %g° increments and rotate both handles"),
+ snap_increment_degrees());
+ } else {
+ return format_tip(C_("Path handle tip",
+ "<b>Ctrl:</b> snap rotation angle to %g° increments, click to retract"),
+ snap_increment_degrees());
+ }
+ } else if (state_held_shift(state) && can_shift_rotate) {
+ return C_("Path hande tip",
+ "<b>Shift</b>: rotate both handles by the same angle");
+ }
+ }
+
+ switch (_parent->type()) {
+ case NODE_AUTO:
+ return format_tip(C_("Path handle tip",
+ "<b>Auto node handle:</b> drag to convert to smooth node (%s)"), more);
+ default:
+ return format_tip(C_("Path handle tip",
+ "<b>%s:</b> drag to shape the segment (%s)"),
+ handle_type_to_localized_string(_parent->type()), more);
+ }
+}
+
+Glib::ustring Handle::_getDragTip(GdkEventMotion */*event*/)
+{
+ Geom::Point dist = position() - _last_drag_origin();
+ // report angle in mathematical convention
+ double angle = Geom::angle_between(Geom::Point(-1,0), position() - _parent->position());
+ angle += M_PI; // angle is (-M_PI...M_PI] - offset by +pi and scale to 0...360
+ angle *= 360.0 / (2 * M_PI);
+ GString *x = SP_PX_TO_METRIC_STRING(dist[Geom::X], _desktop->namedview->getDefaultMetric());
+ GString *y = SP_PX_TO_METRIC_STRING(dist[Geom::Y], _desktop->namedview->getDefaultMetric());
+ GString *len = SP_PX_TO_METRIC_STRING(length(), _desktop->namedview->getDefaultMetric());
+ Glib::ustring ret = format_tip(C_("Path handle tip",
+ "Move handle by %s, %s; angle %.2f°, length %s"), x->str, y->str, angle, len->str);
+ g_string_free(x, TRUE);
+ g_string_free(y, TRUE);
+ g_string_free(len, TRUE);
+ return ret;
+}
+
+/**
+ * @class Node
+ * @brief Curve endpoint in an editable path.
+ *
+ * The method move() keeps node type invariants during translations.
+ */
+
+Node::Node(NodeSharedData const &data, Geom::Point const &initial_pos)
+ : SelectableControlPoint(data.desktop, initial_pos, Gtk::ANCHOR_CENTER,
+ SP_CTRL_SHAPE_DIAMOND, 9.0, *data.selection, &node_colors, data.node_group)
+ , _front(data, initial_pos, this)
+ , _back(data, initial_pos, this)
+ , _type(NODE_CUSP)
+ , _handles_shown(false)
+{
+ // NOTE we do not set type here, because the handles are still degenerate
+}
+
+// NOTE: not using iterators won't make this much quicker because iterators can be 100% inlined.
+Node *Node::_next()
+{
+ NodeList::iterator n = NodeList::get_iterator(this).next();
+ if (n) return n.ptr();
+ return NULL;
+}
+Node *Node::_prev()
+{
+ NodeList::iterator p = NodeList::get_iterator(this).prev();
+ if (p) return p.ptr();
+ return NULL;
+}
+
+void Node::move(Geom::Point const &new_pos)
+{
+ // move handles when the node moves.
+ Geom::Point old_pos = position();
+ Geom::Point delta = new_pos - position();
+ setPosition(new_pos);
+ _front.setPosition(_front.position() + delta);
+ _back.setPosition(_back.position() + delta);
+
+ // if the node has a smooth handle after a line segment, it should be kept colinear
+ // with the segment
+ _fixNeighbors(old_pos, new_pos);
+}
+
+void Node::transform(Geom::Matrix const &m)
+{
+ Geom::Point old_pos = position();
+ setPosition(position() * m);
+ _front.setPosition(_front.position() * m);
+ _back.setPosition(_back.position() * m);
+
+ /* Affine transforms keep handle invariants for smooth and symmetric nodes,
+ * but smooth nodes at ends of linear segments and auto nodes need special treatment */
+ _fixNeighbors(old_pos, position());
+}
+
+Geom::Rect Node::bounds()
+{
+ Geom::Rect b(position(), position());
+ b.expandTo(_front.position());
+ b.expandTo(_back.position());
+ return b;
+}
+
+void Node::_fixNeighbors(Geom::Point const &old_pos, Geom::Point const &new_pos)
+{
+ /* This method restores handle invariants for neighboring nodes,
+ * and invariants that are based on positions of those nodes for this one. */
+
+ /* Fix auto handles */
+ if (_type == NODE_AUTO) _updateAutoHandles();
+ if (old_pos != new_pos) {
+ if (_next() && _next()->_type == NODE_AUTO) _next()->_updateAutoHandles();
+ if (_prev() && _prev()->_type == NODE_AUTO) _prev()->_updateAutoHandles();
+ }
+
+ /* Fix smooth handles at the ends of linear segments.
+ * Rotate the appropriate handle to be colinear with the segment.
+ * If there is a smooth node at the other end of the segment, rotate it too. */
+ Handle *handle, *other_handle;
+ Node *other;
+ if (_is_line_segment(this, _next())) {
+ handle = &_back;
+ other = _next();
+ other_handle = &_next()->_front;
+ } else if (_is_line_segment(_prev(), this)) {
+ handle = &_front;
+ other = _prev();
+ other_handle = &_prev()->_back;
+ } else return;
+
+ if (_type == NODE_SMOOTH && !handle->isDegenerate()) {
+ handle->setDirection(other->position(), new_pos);
+ }
+ // also update the handle on the other end of the segment
+ if (other->_type == NODE_SMOOTH && !other_handle->isDegenerate()) {
+ other_handle->setDirection(new_pos, other->position());
+ }
+}
+
+void Node::_updateAutoHandles()
+{
+ // Recompute the position of automatic handles.
+ // For endnodes, retract both handles. (It's only possible to create an end auto node
+ // through the XML editor.)
+ if (isEndNode()) {
+ _front.retract();
+ _back.retract();
+ return;
+ }
+
+ // Auto nodes automaticaly adjust their handles to give an appearance of smoothness,
+ // no matter what their surroundings are.
+ Geom::Point vec_next = _next()->position() - position();
+ Geom::Point vec_prev = _prev()->position() - position();
+ double len_next = vec_next.length(), len_prev = vec_prev.length();
+ if (len_next > 0 && len_prev > 0) {
+ // "dir" is an unit vector perpendicular to the bisector of the angle created
+ // by the previous node, this auto node and the next node.
+ Geom::Point dir = Geom::unit_vector((len_prev / len_next) * vec_next - vec_prev);
+ // Handle lengths are equal to 1/3 of the distance from the adjacent node.
+ _back.setRelativePos(-dir * (len_prev / 3));
+ _front.setRelativePos(dir * (len_next / 3));
+ } else {
+ // If any of the adjacent nodes coincides, retract both handles.
+ _front.retract();
+ _back.retract();
+ }
+}
+
+void Node::showHandles(bool v)
+{
+ _handles_shown = v;
+ if (!_front.isDegenerate()) _front.setVisible(v);
+ if (!_back.isDegenerate()) _back.setVisible(v);
+}
+
+/** Sets the node type and optionally restores the invariants associated with the given type.
+ * @param type The type to set
+ * @param update_handles Whether to restore invariants associated with the given type.
+ * Passing false is useful e.g. wen initially creating the path,
+ * and when making cusp nodes during some node algorithms.
+ * Pass true when used in response to an UI node type button.
+ */
+void Node::setType(NodeType type, bool update_handles)
+{
+ if (type == NODE_PICK_BEST) {
+ pickBestType();
+ updateState(); // The size of the control might have changed
+ return;
+ }
+
+ // if update_handles is true, adjust handle positions to match the node type
+ // handle degenerate handles appropriately
+ if (update_handles) {
+ switch (type) {
+ case NODE_CUSP:
+ // if the existing type is also NODE_CUSP, retract handles
+ if (_type == NODE_CUSP) {
+ _front.retract();
+ _back.retract();
+ }
+ break;
+ case NODE_AUTO:
+ // auto handles make no sense for endnodes
+ if (isEndNode()) return;
+ _updateAutoHandles();
+ break;
+ case NODE_SMOOTH: {
+ // rotate handles to be colinear
+ // for degenerate nodes set positions like auto handles
+ bool prev_line = _is_line_segment(_prev(), this);
+ bool next_line = _is_line_segment(this, _next());
+ if (_type == NODE_SMOOTH) {
+ // for a node that is already smooth and has a degenerate handle,
+ // drag out the second handle to 1/3 the length of the linear segment
+ if (_front.isDegenerate()) {
+ double dist = Geom::distance(_next()->position(), position());
+ _front.setRelativePos(Geom::unit_vector(-_back.relativePos()) * dist / 3);
+ }
+ if (_back.isDegenerate()) {
+ double dist = Geom::distance(_prev()->position(), position());
+ _back.setRelativePos(Geom::unit_vector(-_front.relativePos()) * dist / 3);
+ }
+ } else if (isDegenerate()) {
+ _updateAutoHandles();
+ } else if (_front.isDegenerate()) {
+ // if the front handle is degenerate and this...next is a line segment,
+ // make back colinear; otherwise pull out the other handle
+ // to 1/3 of distance to prev
+ if (next_line) {
+ _back.setDirection(*_next(), *this);
+ } else if (_prev()) {
+ Geom::Point dir = direction(_back, *this);
+ _front.setRelativePos(Geom::distance(_prev()->position(), position()) / 3 * dir);
+ }
+ } else if (_back.isDegenerate()) {
+ if (prev_line) {
+ _front.setDirection(*_prev(), *this);
+ } else if (_next()) {
+ Geom::Point dir = direction(_front, *this);
+ _back.setRelativePos(Geom::distance(_next()->position(), position()) / 3 * dir);
+ }
+ } else {
+ // both handles are extended. make colinear while keeping length
+ // first make back colinear with the vector front ---> back,
+ // then make front colinear with back ---> node
+ // (not back ---> front because back's position was changed in the first call)
+ _back.setDirection(_front, _back);
+ _front.setDirection(_back, *this);
+ }
+ } break;
+ case NODE_SYMMETRIC:
+ if (isEndNode()) return; // symmetric handles make no sense for endnodes
+ if (isDegenerate()) {
+ // similar to auto handles but set the same length for both
+ Geom::Point vec_next = _next()->position() - position();
+ Geom::Point vec_prev = _prev()->position() - position();
+ double len_next = vec_next.length(), len_prev = vec_prev.length();
+ double len = (len_next + len_prev) / 6; // take 1/3 of average
+ if (len == 0) return;
+
+ Geom::Point dir = Geom::unit_vector((len_prev / len_next) * vec_next - vec_prev);
+ _back.setRelativePos(-dir * len);
+ _front.setRelativePos(dir * len);
+ } else {
+ // Both handles are extended. Compute average length, use direction from
+ // back handle to front handle. This also works correctly for degenerates
+ double len = (_front.length() + _back.length()) / 2;
+ Geom::Point dir = direction(_back, _front);
+ _front.setRelativePos(dir * len);
+ _back.setRelativePos(-dir * len);
+ }
+ break;
+ default: break;
+ }
+ }
+ _type = type;
+ _setShape(_node_type_to_shape(type));
+ updateState();
+}
+
+/** Pick the best type for this node, based on the position of its handles.
+ * This is what assigns types to nodes created using the pen tool. */
+void Node::pickBestType()
+{
+ _type = NODE_CUSP;
+ bool front_degen = _front.isDegenerate();
+ bool back_degen = _back.isDegenerate();
+ bool both_degen = front_degen && back_degen;
+ bool neither_degen = !front_degen && !back_degen;
+ do {
+ // if both handles are degenerate, do nothing
+ if (both_degen) break;
+ // if neither are degenerate, check their respective positions
+ if (neither_degen) {
+ Geom::Point front_delta = _front.position() - position();
+ Geom::Point back_delta = _back.position() - position();
+ // for now do not automatically make nodes symmetric, it can be annoying
+ /*if (Geom::are_near(front_delta, -back_delta)) {
+ _type = NODE_SYMMETRIC;
+ break;
+ }*/
+ if (Geom::are_near(Geom::unit_vector(front_delta),
+ Geom::unit_vector(-back_delta)))
+ {
+ _type = NODE_SMOOTH;
+ break;
+ }
+ }
+ // check whether the handle aligns with the previous line segment.
+ // we know that if front is degenerate, back isn't, because
+ // both_degen was false
+ if (front_degen && _next() && _next()->_back.isDegenerate()) {
+ Geom::Point segment_delta = Geom::unit_vector(_next()->position() - position());
+ Geom::Point handle_delta = Geom::unit_vector(_back.position() - position());
+ if (Geom::are_near(segment_delta, -handle_delta)) {
+ _type = NODE_SMOOTH;
+ break;
+ }
+ } else if (back_degen && _prev() && _prev()->_front.isDegenerate()) {
+ Geom::Point segment_delta = Geom::unit_vector(_prev()->position() - position());
+ Geom::Point handle_delta = Geom::unit_vector(_front.position() - position());
+ if (Geom::are_near(segment_delta, -handle_delta)) {
+ _type = NODE_SMOOTH;
+ break;
+ }
+ }
+ } while (false);
+ _setShape(_node_type_to_shape(_type));
+ updateState();
+}
+
+bool Node::isEndNode()
+{
+ return !_prev() || !_next();
+}
+
+/** Move the node to the bottom of its canvas group. Useful for node break, to ensure that
+ * the selected nodes are above the unselected ones. */
+void Node::sink()
+{
+ sp_canvas_item_move_to_z(_canvas_item, 0);
+}
+
+NodeType Node::parse_nodetype(char x)
+{
+ switch (x) {
+ case 'a': return NODE_AUTO;
+ case 'c': return NODE_CUSP;
+ case 's': return NODE_SMOOTH;
+ case 'z': return NODE_SYMMETRIC;
+ default: return NODE_PICK_BEST;
+ }
+}
+
+/** Customized event handler to catch scroll events needed for selection grow/shrink. */
+bool Node::_eventHandler(GdkEvent *event)
+{
+ static NodeList::iterator origin;
+ static int dir;
+
+ switch (event->type)
+ {
+ case GDK_SCROLL:
+ if (event->scroll.direction == GDK_SCROLL_UP) {
+ dir = 1;
+ } else if (event->scroll.direction == GDK_SCROLL_DOWN) {
+ dir = -1;
+ } else break;
+ if (held_control(event->scroll)) {
+ _selection.spatialGrow(this, dir);
+ } else {
+ _linearGrow(dir);
+ }
+ return true;
+ default:
+ break;
+ }
+ return ControlPoint::_eventHandler(event);
+}
+
+// TODO Move this to 2Geom!
+static double bezier_length (Geom::Point a0, Geom::Point a1, Geom::Point a2, Geom::Point a3)
+{
+ double lower = Geom::distance(a0, a3);
+ double upper = Geom::distance(a0, a1) + Geom::distance(a1, a2) + Geom::distance(a2, a3);
+
+ if (upper - lower < Geom::EPSILON) return (lower + upper)/2;
+
+ Geom::Point // Casteljau subdivision
+ b0 = a0,
+ c0 = a3,
+ b1 = 0.5*(a0 + a1),
+ t0 = 0.5*(a1 + a2),
+ c1 = 0.5*(a2 + a3),
+ b2 = 0.5*(b1 + t0),
+ c2 = 0.5*(t0 + c1),
+ b3 = 0.5*(b2 + c2); // == c3
+ return bezier_length(b0, b1, b2, b3) + bezier_length(b3, c2, c1, c0);
+}
+
+/** Select or deselect a node in this node's subpath based on its path distance from this node.
+ * @param dir If negative, shrink selection by one node; if positive, grow by one node */
+void Node::_linearGrow(int dir)
+{
+ // Interestingly, we do not need any help from PathManipulator when doing linear grow.
+ // First handle the trivial case of growing over an unselected node.
+ if (!selected() && dir > 0) {
+ _selection.insert(this);
+ return;
+ }
+
+ NodeList::iterator this_iter = NodeList::get_iterator(this);
+ NodeList::iterator fwd = this_iter, rev = this_iter;
+ double distance_back = 0, distance_front = 0;
+
+ // Linear grow is simple. We find the first unselected nodes in each direction
+ // and compare the linear distances to them.
+ if (dir > 0) {
+ if (!selected()) {
+ _selection.insert(this);
+ return;
+ }
+
+ // find first unselected nodes on both sides
+ while (fwd && fwd->selected()) {
+ NodeList::iterator n = fwd.next();
+ distance_front += bezier_length(*fwd, fwd->_front, n->_back, *n);
+ fwd = n;
+ if (fwd == this_iter)
+ // there is no unselected node in this cyclic subpath
+ return;
+ }
+ // do the same for the second direction. Do not check for equality with
+ // this node, because there is at least one unselected node in the subpath,
+ // so we are guaranteed to stop.
+ while (rev && rev->selected()) {
+ NodeList::iterator p = rev.prev();
+ distance_back += bezier_length(*rev, rev->_back, p->_front, *p);
+ rev = p;
+ }
+
+ NodeList::iterator t; // node to select
+ if (fwd && rev) {
+ if (distance_front <= distance_back) t = fwd;
+ else t = rev;
+ } else {
+ if (fwd) t = fwd;
+ if (rev) t = rev;
+ }
+ if (t) _selection.insert(t.ptr());
+
+ // Linear shrink is more complicated. We need to find the farthest selected node.
+ // This means we have to check the entire subpath. We go in the direction in which
+ // the distance we traveled is lower. We do this until we run out of nodes (ends of path)
+ // or the two iterators meet. On the way, we store the last selected node and its distance
+ // in each direction (if any). At the end, we choose the one that is farther and deselect it.
+ } else {
+ // both iterators that store last selected nodes are initially empty
+ NodeList::iterator last_fwd, last_rev;
+ double last_distance_back = 0, last_distance_front = 0;
+
+ while (rev || fwd) {
+ if (fwd && (!rev || distance_front <= distance_back)) {
+ if (fwd->selected()) {
+ last_fwd = fwd;
+ last_distance_front = distance_front;
+ }
+ NodeList::iterator n = fwd.next();
+ if (n) distance_front += bezier_length(*fwd, fwd->_front, n->_back, *n);
+ fwd = n;
+ } else if (rev && (!fwd || distance_front > distance_back)) {
+ if (rev->selected()) {
+ last_rev = rev;
+ last_distance_back = distance_back;
+ }
+ NodeList::iterator p = rev.prev();
+ if (p) distance_back += bezier_length(*rev, rev->_back, p->_front, *p);
+ rev = p;
+ }
+ // Check whether we walked the entire cyclic subpath.
+ // This is initially true because both iterators start from this node,
+ // so this check cannot go in the while condition.
+ // When this happens, we need to check the last node, pointed to by the iterators.
+ if (fwd && fwd == rev) {
+ if (!fwd->selected()) break;
+ NodeList::iterator fwdp = fwd.prev(), revn = rev.next();
+ double df = distance_front + bezier_length(*fwdp, fwdp->_front, fwd->_back, *fwd);
+ double db = distance_back + bezier_length(*revn, revn->_back, rev->_front, *rev);
+ if (df > db) {
+ last_fwd = fwd;
+ last_distance_front = df;
+ } else {
+ last_rev = rev;
+ last_distance_back = db;
+ }
+ break;
+ }
+ }
+
+ NodeList::iterator t;
+ if (last_fwd && last_rev) {
+ if (last_distance_front >= last_distance_back) t = last_fwd;
+ else t = last_rev;
+ } else {
+ if (last_fwd) t = last_fwd;
+ if (last_rev) t = last_rev;
+ }
+ if (t) _selection.erase(t.ptr());
+ }
+}
+
+void Node::_setState(State state)
+{
+ // change node size to match type and selection state
+ switch (_type) {
+ case NODE_AUTO:
+ case NODE_CUSP:
+ if (selected()) _setSize(11);
+ else _setSize(9);
+ break;
+ default:
+ if(selected()) _setSize(9);
+ else _setSize(7);
+ break;
+ }
+ SelectableControlPoint::_setState(state);
+}
+
+bool Node::grabbed(GdkEventMotion *event)
+{
+ if (SelectableControlPoint::grabbed(event))
+ return true;
+
+ // Dragging out handles with Shift + drag on a node.
+ if (!held_shift(*event)) return false;
+
+ Handle *h;
+ Geom::Point evp = event_point(*event);
+ Geom::Point rel_evp = evp - _last_click_event_point();
+
+ // This should work even if dragtolerance is zero and evp coincides with node position.
+ double angle_next = HUGE_VAL;
+ double angle_prev = HUGE_VAL;
+ bool has_degenerate = false;
+ // determine which handle to drag out based on degeneration and the direction of drag
+ if (_front.isDegenerate() && _next()) {
+ Geom::Point next_relpos = _desktop->d2w(_next()->position())
+ - _desktop->d2w(position());
+ angle_next = fabs(Geom::angle_between(rel_evp, next_relpos));
+ has_degenerate = true;
+ }
+ if (_back.isDegenerate() && _prev()) {
+ Geom::Point prev_relpos = _desktop->d2w(_prev()->position())
+ - _desktop->d2w(position());
+ angle_prev = fabs(Geom::angle_between(rel_evp, prev_relpos));
+ has_degenerate = true;
+ }
+ if (!has_degenerate) return false;
+ h = angle_next < angle_prev ? &_front : &_back;
+
+ h->setPosition(_desktop->w2d(evp));
+ h->setVisible(true);
+ h->transferGrab(this, event);
+ Handle::_drag_out = true;
+ return true;
+}
+
+void Node::dragged(Geom::Point &new_pos, GdkEventMotion *event)
+{
+ // For a note on how snapping is implemented in Inkscape, see snap.h.
+ SnapManager &sm = _desktop->namedview->snap_manager;
+ bool snap = sm.someSnapperMightSnap();
+ std::vector<Inkscape::SnapCandidatePoint> unselected;
+ if (snap) {
+ /* setup
+ * TODO We are doing this every time a snap happens. It should once be done only once
+ * per drag - maybe in the grabbed handler?
+ * TODO Unselected nodes vector must be valid during the snap run, because it is not
+ * copied. Fix this in snap.h and snap.cpp, then the above.
+ * TODO Snapping to unselected segments of selected paths doesn't work yet. */
+
+ // Build the list of unselected nodes.
+ typedef ControlPointSelection::Set Set;
+ Set &nodes = _selection.allPoints();
+ for (Set::iterator i = nodes.begin(); i != nodes.end(); ++i) {
+ if (!(*i)->selected()) {
+ Node *n = static_cast<Node*>(*i);
+ Inkscape::SnapCandidatePoint p(n->position(), n->_snapSourceType(), n->_snapTargetType());
+ unselected.push_back(p);
+ }
+ }
+ sm.setupIgnoreSelection(_desktop, false, &unselected);
+ }
+
+ if (held_control(*event)) {
+ Geom::Point origin = _last_drag_origin();
+ Inkscape::SnappedPoint fp, bp;
+ if (held_alt(*event)) {
+ // with Ctrl+Alt, constrain to handle lines
+ // project the new position onto a handle line that is closer
+ boost::optional<Geom::Point> front_point, back_point;
+ boost::optional<Inkscape::Snapper::ConstraintLine> line_front, line_back;
+ if (_front.isDegenerate()) {
+ if (_is_line_segment(this, _next()))
+ front_point = _next()->position() - origin;
+ } else {
+ front_point = _front.relativePos();
+ }
+ if (_back.isDegenerate()) {
+ if (_is_line_segment(_prev(), this))
+ back_point = _prev()->position() - origin;
+ } else {
+ back_point = _back.relativePos();
+ }
+ if (front_point)
+ line_front = Inkscape::Snapper::ConstraintLine(origin, *front_point);
+ if (back_point)
+ line_back = Inkscape::Snapper::ConstraintLine(origin, *back_point);
+
+ // TODO: combine the snap and non-snap branches by modifying snap.h / snap.cpp
+ if (snap) {
+ if (line_front) {
+ fp = sm.constrainedSnap(Inkscape::SnapCandidatePoint(new_pos,
+ _snapSourceType()), *line_front);
+ }
+ if (line_back) {
+ bp = sm.constrainedSnap(Inkscape::SnapCandidatePoint(new_pos,
+ _snapSourceType()), *line_back);
+ }
+ }
+ if (fp.getSnapped() || bp.getSnapped()) {
+ if (fp.isOtherSnapBetter(bp, false)) {
+ fp = bp;
+ }
+ fp.getPoint(new_pos);
+ _desktop->snapindicator->set_new_snaptarget(fp);
+ } else {
+ boost::optional<Geom::Point> pos;
+ if (line_front) {
+ pos = line_front->projection(new_pos);
+ }
+ if (line_back) {
+ Geom::Point pos2 = line_back->projection(new_pos);
+ if (!pos || (pos && Geom::distance(new_pos, *pos) > Geom::distance(new_pos, pos2)))
+ pos = pos2;
+ }
+ if (pos) {
+ new_pos = *pos;
+ } else {
+ new_pos = origin;
+ }
+ }
+ } else {
+ // with Ctrl, constrain to axes
+ // TODO combine the two branches
+ if (snap) {
+ Inkscape::Snapper::ConstraintLine line_x(origin, Geom::Point(1, 0));
+ Inkscape::Snapper::ConstraintLine line_y(origin, Geom::Point(0, 1));
+ fp = sm.constrainedSnap(Inkscape::SnapCandidatePoint(new_pos, _snapSourceType()), line_x);
+ bp = sm.constrainedSnap(Inkscape::SnapCandidatePoint(new_pos, _snapSourceType()), line_y);
+ }
+ if (fp.getSnapped() || bp.getSnapped()) {
+ if (fp.isOtherSnapBetter(bp, false)) {
+ fp = bp;
+ }
+ fp.getPoint(new_pos);
+ if (fp.getTarget() != SNAPTARGET_CONSTRAINT) {
+ _desktop->snapindicator->set_new_snaptarget(fp);
+ }
+ } else {
+ Geom::Point origin = _last_drag_origin();
+ Geom::Point delta = new_pos - origin;
+ Geom::Dim2 d = (fabs(delta[Geom::X]) < fabs(delta[Geom::Y])) ? Geom::X : Geom::Y;
+ new_pos[d] = origin[d];
+ }
+ }
+ } else if (snap) {
+ Inkscape::SnappedPoint p = sm.freeSnap(Inkscape::SnapCandidatePoint(new_pos, _snapSourceType()));
+ if (p.getSnapped()) {
+ p.getPoint(new_pos);
+ _desktop->snapindicator->set_new_snaptarget(p);
+ }
+ }
+
+ SelectableControlPoint::dragged(new_pos, event);
+}
+
+bool Node::clicked(GdkEventButton *event)
+{
+ if(_pm()._nodeClicked(this, event))
+ return true;
+ return SelectableControlPoint::clicked(event);
+}
+
+Inkscape::SnapSourceType Node::_snapSourceType()
+{
+ if (_type == NODE_SMOOTH || _type == NODE_AUTO)
+ return SNAPSOURCE_NODE_SMOOTH;
+ return SNAPSOURCE_NODE_CUSP;
+}
+Inkscape::SnapTargetType Node::_snapTargetType()
+{
+ if (_type == NODE_SMOOTH || _type == NODE_AUTO)
+ return SNAPTARGET_NODE_SMOOTH;
+ return SNAPTARGET_NODE_CUSP;
+}
+
+/** @brief Gets the handle that faces the given adjacent node.
+ * Will abort with error if the given node is not adjacent. */
+Handle *Node::handleToward(Node *to)
+{
+ if (_next() == to) {
+ return front();
+ }
+ if (_prev() == to) {
+ return back();
+ }
+ g_error("Node::handleToward(): second node is not adjacent!");
+}
+
+/** @brief Gets the node in the direction of the given handle.
+ * Will abort with error if the handle doesn't belong to this node. */
+Node *Node::nodeToward(Handle *dir)
+{
+ if (front() == dir) {
+ return _next();
+ }
+ if (back() == dir) {
+ return _prev();
+ }
+ g_error("Node::nodeToward(): handle is not a child of this node!");
+}
+
+/** @brief Gets the handle that goes in the direction opposite to the given adjacent node.
+ * Will abort with error if the given node is not adjacent. */
+Handle *Node::handleAwayFrom(Node *to)
+{
+ if (_next() == to) {
+ return back();
+ }
+ if (_prev() == to) {
+ return front();
+ }
+ g_error("Node::handleAwayFrom(): second node is not adjacent!");
+}
+
+/** @brief Gets the node in the direction opposite to the given handle.
+ * Will abort with error if the handle doesn't belong to this node. */
+Node *Node::nodeAwayFrom(Handle *h)
+{
+ if (front() == h) {
+ return _prev();
+ }
+ if (back() == h) {
+ return _next();
+ }
+ g_error("Node::nodeAwayFrom(): handle is not a child of this node!");
+}
+
+Glib::ustring Node::_getTip(unsigned state)
+{
+ if (state_held_shift(state)) {
+ bool can_drag_out = (_next() && _front.isDegenerate()) || (_prev() && _back.isDegenerate());
+ if (can_drag_out) {
+ /*if (state_held_control(state)) {
+ return format_tip(C_("Path node tip",
+ "<b>Shift+Ctrl:</b> drag out a handle and snap its angle "
+ "to %f° increments"), snap_increment_degrees());
+ }*/
+ return C_("Path node tip",
+ "<b>Shift:</b> drag out a handle, click to toggle selection");
+ }
+ return C_("Path node tip", "<b>Shift:</b> click to toggle selection");
+ }
+
+ if (state_held_control(state)) {
+ if (state_held_alt(state)) {
+ return C_("Path node tip", "<b>Ctrl+Alt:</b> move along handle lines, click to delete node");
+ }
+ return C_("Path node tip",
+ "<b>Ctrl:</b> move along axes, click to change node type");
+ }
+
+ if (state_held_alt(state)) {
+ return C_("Path node tip", "<b>Alt:</b> sculpt nodes");
+ }
+
+ // No modifiers: assemble tip from node type
+ char const *nodetype = node_type_to_localized_string(_type);
+ if (_selection.transformHandlesEnabled() && selected()) {
+ if (_selection.size() == 1) {
+ return format_tip(C_("Path node tip",
+ "<b>%s:</b> drag to shape the path (more: Shift, Ctrl, Alt)"), nodetype);
+ }
+ return format_tip(C_("Path node tip",
+ "<b>%s:</b> drag to shape the path, click to toggle scale/rotation handles (more: Shift, Ctrl, Alt)"), nodetype);
+ }
+ return format_tip(C_("Path node tip",
+ "<b>%s:</b> drag to shape the path, click to select only this node (more: Shift, Ctrl, Alt)"), nodetype);
+}
+
+Glib::ustring Node::_getDragTip(GdkEventMotion */*event*/)
+{
+ Geom::Point dist = position() - _last_drag_origin();
+ GString *x = SP_PX_TO_METRIC_STRING(dist[Geom::X], _desktop->namedview->getDefaultMetric());
+ GString *y = SP_PX_TO_METRIC_STRING(dist[Geom::Y], _desktop->namedview->getDefaultMetric());
+ Glib::ustring ret = format_tip(C_("Path node tip", "Move node by %s, %s"),
+ x->str, y->str);
+ g_string_free(x, TRUE);
+ g_string_free(y, TRUE);
+ return ret;
+}
+
+char const *Node::node_type_to_localized_string(NodeType type)
+{
+ switch (type) {
+ case NODE_CUSP: return _("Cusp node");
+ case NODE_SMOOTH: return _("Smooth node");
+ case NODE_SYMMETRIC: return _("Symmetric node");
+ case NODE_AUTO: return _("Auto-smooth node");
+ default: return "";
+ }
+}
+
+/** Determine whether two nodes are joined by a linear segment. */
+bool Node::_is_line_segment(Node *first, Node *second)
+{
+ if (!first || !second) return false;
+ if (first->_next() == second)
+ return first->_front.isDegenerate() && second->_back.isDegenerate();
+ if (second->_next() == first)
+ return second->_front.isDegenerate() && first->_back.isDegenerate();
+ return false;
+}
+
+SPCtrlShapeType Node::_node_type_to_shape(NodeType type)
+{
+ switch(type) {
+ case NODE_CUSP: return SP_CTRL_SHAPE_DIAMOND;
+ case NODE_SMOOTH: return SP_CTRL_SHAPE_SQUARE;
+ case NODE_AUTO: return SP_CTRL_SHAPE_CIRCLE;
+ case NODE_SYMMETRIC: return SP_CTRL_SHAPE_SQUARE;
+ default: return SP_CTRL_SHAPE_DIAMOND;
+ }
+}
+
+
+/**
+ * @class NodeList
+ * @brief An editable list of nodes representing a subpath.
+ *
+ * It can optionally be cyclic to represent a closed path.
+ * The list has iterators that act like plain node iterators, but can also be used
+ * to obtain shared pointers to nodes.
+ */
+
+NodeList::NodeList(SubpathList &splist)
+ : _list(splist)
+ , _closed(false)
+{
+ this->ln_list = this;
+ this->ln_next = this;
+ this->ln_prev = this;
+}
+
+NodeList::~NodeList()
+{
+ clear();
+}
+
+bool NodeList::empty()
+{
+ return ln_next == this;
+}
+
+NodeList::size_type NodeList::size()
+{
+ size_type sz = 0;
+ for (ListNode *ln = ln_next; ln != this; ln = ln->ln_next) ++sz;
+ return sz;
+}
+
+bool NodeList::closed()
+{
+ return _closed;
+}
+
+/** A subpath is degenerate if it has no segments - either one node in an open path
+ * or no nodes in a closed path */
+bool NodeList::degenerate()
+{
+ return closed() ? empty() : ++begin() == end();
+}
+
+NodeList::iterator NodeList::before(double t, double *fracpart)
+{
+ double intpart;
+ *fracpart = std::modf(t, &intpart);
+ int index = intpart;
+
+ iterator ret = begin();
+ std::advance(ret, index);
+ return ret;
+}
+
+// insert a node before i
+NodeList::iterator NodeList::insert(iterator i, Node *x)
+{
+ ListNode *ins = i._node;
+ x->ln_next = ins;
+ x->ln_prev = ins->ln_prev;
+ ins->ln_prev->ln_next = x;
+ ins->ln_prev = x;
+ x->ln_list = this;
+ return iterator(x);
+}
+
+void NodeList::splice(iterator pos, NodeList &list)
+{
+ splice(pos, list, list.begin(), list.end());
+}
+
+void NodeList::splice(iterator pos, NodeList &list, iterator i)
+{
+ NodeList::iterator j = i;
+ ++j;
+ splice(pos, list, i, j);
+}
+
+void NodeList::splice(iterator pos, NodeList &/*list*/, iterator first, iterator last)
+{
+ ListNode *ins_beg = first._node, *ins_end = last._node, *at = pos._node;
+ for (ListNode *ln = ins_beg; ln != ins_end; ln = ln->ln_next) {
+ ln->ln_list = this;
+ }
+ ins_beg->ln_prev->ln_next = ins_end;
+ ins_end->ln_prev->ln_next = at;
+ at->ln_prev->ln_next = ins_beg;
+
+ ListNode *atprev = at->ln_prev;
+ at->ln_prev = ins_end->ln_prev;
+ ins_end->ln_prev = ins_beg->ln_prev;
+ ins_beg->ln_prev = atprev;
+}
+
+void NodeList::shift(int n)
+{
+ // 1. make the list perfectly cyclic
+ ln_next->ln_prev = ln_prev;
+ ln_prev->ln_next = ln_next;
+ // 2. find new begin
+ ListNode *new_begin = ln_next;
+ if (n > 0) {
+ for (; n > 0; --n) new_begin = new_begin->ln_next;
+ } else {
+ for (; n < 0; ++n) new_begin = new_begin->ln_prev;
+ }
+ // 3. relink begin to list
+ ln_next = new_begin;
+ ln_prev = new_begin->ln_prev;
+ new_begin->ln_prev->ln_next = this;
+ new_begin->ln_prev = this;
+}
+
+void NodeList::reverse()
+{
+ for (ListNode *ln = ln_next; ln != this; ln = ln->ln_prev) {
+ std::swap(ln->ln_next, ln->ln_prev);
+ Node *node = static_cast<Node*>(ln);
+ Geom::Point save_pos = node->front()->position();
+ node->front()->setPosition(node->back()->position());
+ node->back()->setPosition(save_pos);
+ }
+ std::swap(ln_next, ln_prev);
+}
+
+void NodeList::clear()
+{
+ for (iterator i = begin(); i != end();) erase (i++);
+}
+
+NodeList::iterator NodeList::erase(iterator i)
+{
+ // some gymnastics are required to ensure that the node is valid when deleted;
+ // otherwise the code that updates handle visibility will break
+ Node *rm = static_cast<Node*>(i._node);
+ ListNode *rmnext = rm->ln_next, *rmprev = rm->ln_prev;
+ ++i;
+ delete rm;
+ rmprev->ln_next = rmnext;
+ rmnext->ln_prev = rmprev;
+ return i;
+}
+
+// TODO this method is very ugly!
+// converting SubpathList to an intrusive list might allow us to get rid of it
+void NodeList::kill()
+{
+ for (SubpathList::iterator i = _list.begin(); i != _list.end(); ++i) {
+ if (i->get() == this) {
+ _list.erase(i);
+ return;
+ }
+ }
+}
+
+NodeList &NodeList::get(Node *n) {
+ return n->nodeList();
+}
+NodeList &NodeList::get(iterator const &i) {
+ return *(i._node->ln_list);
+}
+
+
+/**
+ * @class SubpathList
+ * @brief Editable path composed of one or more subpaths
+ */
+
+} // 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:encoding=utf-8:textwidth=99 :
diff --git a/src/ui/tool/node.h b/src/ui/tool/node.h
new file mode 100644
index 000000000..af4cd7e3a
--- /dev/null
+++ b/src/ui/tool/node.h
@@ -0,0 +1,411 @@
+/** @file
+ * Editable node and associated data structures.
+ */
+/* Authors:
+ * Krzysztof Kosiński <tweenk.pl@gmail.com>
+ *
+ * Copyright (C) 2009 Authors
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ */
+
+#ifndef SEEN_UI_TOOL_NODE_H
+#define SEEN_UI_TOOL_NODE_H
+
+#include <glib.h>
+#include <iterator>
+#include <iosfwd>
+#include <stdexcept>
+#include <tr1/functional>
+#include <boost/utility.hpp>
+#include <boost/shared_ptr.hpp>
+#include <boost/optional.hpp>
+#include <boost/operators.hpp>
+#include "snapped-point.h"
+#include "ui/tool/selectable-control-point.h"
+#include "ui/tool/node-types.h"
+
+
+namespace Inkscape {
+namespace UI {
+template <typename> class NodeIterator;
+}
+}
+
+namespace std {
+namespace tr1 {
+template <typename N> struct hash< Inkscape::UI::NodeIterator<N> >;
+}
+}
+
+namespace Inkscape {
+namespace UI {
+
+class PathManipulator;
+class MultiPathManipulator;
+
+class Node;
+class Handle;
+class NodeList;
+class SubpathList;
+template <typename> class NodeIterator;
+
+std::ostream &operator<<(std::ostream &, NodeType);
+
+/*
+template <typename T>
+struct ListMember {
+ T *next;
+ T *prev;
+};
+struct SubpathMember : public ListMember<NodeListMember> {
+ Subpath *list;
+};
+struct SubpathListMember : public ListMember<SubpathListMember> {
+ SubpathList *list;
+};
+*/
+
+struct ListNode {
+ ListNode *ln_next;
+ ListNode *ln_prev;
+ NodeList *ln_list;
+};
+
+struct NodeSharedData {
+ SPDesktop *desktop;
+ ControlPointSelection *selection;
+ SPCanvasGroup *node_group;
+ SPCanvasGroup *handle_group;
+ SPCanvasGroup *handle_line_group;
+};
+
+class Handle : public ControlPoint {
+public:
+ virtual ~Handle();
+ inline Geom::Point relativePos();
+ inline double length();
+ bool isDegenerate() { return _degenerate; }
+
+ virtual void setVisible(bool);
+ virtual void move(Geom::Point const &p);
+
+ virtual void setPosition(Geom::Point const &p);
+ inline void setRelativePos(Geom::Point const &p);
+ void setLength(double len);
+ void retract();
+ void setDirection(Geom::Point const &from, Geom::Point const &to);
+ void setDirection(Geom::Point const &dir);
+ Node *parent() { return _parent; }
+ Handle *other();
+
+ static char const *handle_type_to_localized_string(NodeType type);
+protected:
+ Handle(NodeSharedData const &data, Geom::Point const &initial_pos, Node *parent);
+
+ virtual void dragged(Geom::Point &, GdkEventMotion *);
+ virtual bool grabbed(GdkEventMotion *);
+ virtual void ungrabbed(GdkEventButton *);
+ virtual bool clicked(GdkEventButton *);
+
+ virtual Glib::ustring _getTip(unsigned state);
+ virtual Glib::ustring _getDragTip(GdkEventMotion *event);
+ virtual bool _hasDragTips() { return true; }
+private:
+ inline PathManipulator &_pm();
+ Node *_parent; // the handle's lifetime does not extend beyond that of the parent node,
+ // so a naked pointer is OK and allows setting it during Node's construction
+ SPCanvasItem *_handle_line;
+ bool _degenerate; // this is used often internally so it makes sense to cache this
+
+ static Geom::Point _saved_other_pos;
+ static double _saved_length;
+ static bool _drag_out;
+ friend class Node;
+};
+
+class Node : ListNode, public SelectableControlPoint {
+public:
+ Node(NodeSharedData const &data, Geom::Point const &pos);
+ virtual void move(Geom::Point const &p);
+ virtual void transform(Geom::Matrix const &m);
+ virtual Geom::Rect bounds();
+
+ NodeType type() { return _type; }
+ void setType(NodeType type, bool update_handles = true);
+ void showHandles(bool v);
+ void pickBestType(); // automatically determine the type from handle positions
+ bool isDegenerate() { return _front.isDegenerate() && _back.isDegenerate(); }
+ bool isEndNode();
+ Handle *front() { return &_front; }
+ Handle *back() { return &_back; }
+ Handle *handleToward(Node *to);
+ Node *nodeToward(Handle *h);
+ Handle *handleAwayFrom(Node *to);
+ Node *nodeAwayFrom(Handle *h);
+ NodeList &nodeList() { return *(static_cast<ListNode*>(this)->ln_list); }
+ void sink();
+
+ static NodeType parse_nodetype(char x);
+ static char const *node_type_to_localized_string(NodeType type);
+ // temporarily public
+ virtual bool _eventHandler(GdkEvent *event);
+protected:
+ virtual void dragged(Geom::Point &, GdkEventMotion *);
+ virtual bool grabbed(GdkEventMotion *);
+ virtual bool clicked(GdkEventButton *);
+
+ virtual void _setState(State state);
+ virtual Glib::ustring _getTip(unsigned state);
+ virtual Glib::ustring _getDragTip(GdkEventMotion *event);
+ virtual bool _hasDragTips() { return true; }
+private:
+ Node(Node const &);
+ void _fixNeighbors(Geom::Point const &old_pos, Geom::Point const &new_pos);
+ void _updateAutoHandles();
+ void _linearGrow(int dir);
+ Node *_next();
+ Node *_prev();
+ Inkscape::SnapSourceType _snapSourceType();
+ Inkscape::SnapTargetType _snapTargetType();
+ inline PathManipulator &_pm();
+ static SPCtrlShapeType _node_type_to_shape(NodeType type);
+ static bool _is_line_segment(Node *first, Node *second);
+
+ // Handles are always present, but are not visible if they coincide with the node
+ // (are degenerate). A segment that has both handles degenerate is always treated
+ // as a line segment
+ Handle _front; ///< Node handle in the backward direction of the path
+ Handle _back; ///< Node handle in the forward direction of the path
+ NodeType _type; ///< Type of node - cusp, smooth...
+ bool _handles_shown;
+ friend class Handle;
+ friend class NodeList;
+ friend class NodeIterator<Node>;
+ friend class NodeIterator<Node const>;
+};
+
+/// Iterator for editable nodes
+/** Use this class for all operations that require some knowledge about the node's
+ * neighbors. It is a bidirectional iterator.
+ *
+ * Because paths can be cyclic, node iterators have two different ways to
+ * increment and decrement them. When using ++/--, the end iterator will eventually
+ * be returned. Whent using advance()/retreat(), the end iterator will only be returned
+ * when the path is open. If it's closed, calling advance() will cycle indefinitely.
+ * This is particularly useful for cases where the adjacency of nodes is more important
+ * than their sequence order.
+ *
+ * When @a i is a node iterator, then:
+ * - <code>++i</code> moves the iterator to the next node in sequence order;
+ * - <code>--i</code> moves the iterator to the previous node in sequence order;
+ * - <code>i.next()</code> returns the next node with wrap-around;
+ * - <code>i.prev()</code> returns the previous node with wrap-around;
+ * - <code>i.advance()</code> moves the iterator to the next node with wrap-around;
+ * - <code>i.retreat()</code> moves the iterator to the previous node with wrap-around.
+ *
+ * next() and prev() do not change their iterator. They can return the end iterator
+ * if the path is open.
+ *
+ * Unlike most other iterators, you can check whether you've reached the end of the list
+ * without having access to the iterator's container.
+ * Simply use <code>if (i) { ...</code>
+ * */
+template <typename N>
+class NodeIterator
+ : public boost::bidirectional_iterator_helper<NodeIterator<N>, N, std::ptrdiff_t,
+ N *, N &>
+{
+public:
+ typedef NodeIterator self;
+ NodeIterator()
+ : _node(0)
+ {}
+ // default copy, default assign
+
+ self &operator++() {
+ _node = _node->ln_next;
+ return *this;
+ }
+ self &operator--() {
+ _node = _node->ln_prev;
+ return *this;
+ }
+ bool operator==(self const &other) const { return _node == other._node; }
+ N &operator*() const { return *static_cast<N*>(_node); }
+ inline operator bool() const; // define after NodeList
+ /// Get a pointer to the underlying node. Equivalent to <code>&*i</code>.
+ N *get_pointer() const { return static_cast<N*>(_node); }
+ /// @see get_pointer()
+ N *ptr() const { return static_cast<N*>(_node); }
+
+ self next() const {
+ self r(*this);
+ r.advance();
+ return r;
+ }
+ self prev() const {
+ self r(*this);
+ r.retreat();
+ return r;
+ }
+ self &advance();
+ self &retreat();
+private:
+ NodeIterator(ListNode const *n)
+ : _node(const_cast<ListNode*>(n))
+ {}
+ ListNode *_node;
+ friend class NodeList;
+};
+
+class NodeList : ListNode, boost::noncopyable, public boost::enable_shared_from_this<NodeList> {
+public:
+ typedef std::size_t size_type;
+ typedef Node &reference;
+ typedef Node const &const_reference;
+ typedef Node *pointer;
+ typedef Node const *const_pointer;
+ typedef Node value_type;
+ typedef NodeIterator<value_type> iterator;
+ typedef NodeIterator<value_type const> const_iterator;
+
+ // TODO Lame. Make this private and make SubpathList a factory
+ NodeList(SubpathList &_list);
+ ~NodeList();
+
+ // iterators
+ iterator begin() { return iterator(ln_next); }
+ iterator end() { return iterator(this); }
+ const_iterator begin() const { return const_iterator(ln_next); }
+ const_iterator end() const { return const_iterator(this); }
+
+ // size
+ bool empty();
+ size_type size();
+
+ // extra node-specific methods
+ bool closed();
+ bool degenerate();
+ void setClosed(bool c) { _closed = c; }
+ iterator before(double t, double *fracpart = NULL);
+ const_iterator before(double t, double *fracpart = NULL) const {
+ return const_iterator(before(t, fracpart)._node);
+ }
+
+ // list operations
+ iterator insert(iterator pos, Node *x);
+ template <class InputIterator>
+ void insert(iterator pos, InputIterator first, InputIterator last) {
+ for (; first != last; ++first) insert(pos, *first);
+ }
+ void splice(iterator pos, NodeList &list);
+ void splice(iterator pos, NodeList &list, iterator i);
+ void splice(iterator pos, NodeList &list, iterator first, iterator last);
+ void reverse();
+ void shift(int n);
+ void push_front(Node *x) { insert(begin(), x); }
+ void pop_front() { erase(begin()); }
+ void push_back(Node *x) { insert(end(), x); }
+ void pop_back() { erase(--end()); }
+ void clear();
+ iterator erase(iterator pos);
+ iterator erase(iterator first, iterator last) {
+ NodeList::iterator ret = first;
+ while (first != last) ret = erase(first++);
+ return ret;
+ }
+
+ // member access - undefined results when the list is empty
+ Node &front() { return *static_cast<Node*>(ln_next); }
+ Node &back() { return *static_cast<Node*>(ln_prev); }
+
+ // HACK remove this subpath from its path. This will be removed later.
+ void kill();
+ SubpathList &subpathList() { return _list; }
+
+ static iterator get_iterator(Node *n) { return iterator(n); }
+ static const_iterator get_iterator(Node const *n) { return const_iterator(n); }
+ static NodeList &get(Node *n);
+ static NodeList &get(iterator const &i);
+private:
+ // no copy or assign
+ NodeList(NodeList const &);
+ void operator=(NodeList const &);
+
+ SubpathList &_list;
+ bool _closed;
+
+ friend class Node;
+ friend class Handle; // required to access handle and handle line groups
+ friend class NodeIterator<Node>;
+ friend class NodeIterator<Node const>;
+};
+
+/** List of node lists. Represents an editable path. */
+class SubpathList : public std::list< boost::shared_ptr<NodeList> > {
+public:
+ typedef std::list< boost::shared_ptr<NodeList> > list_type;
+
+ SubpathList(PathManipulator &pm) : _path_manipulator(pm) {}
+ PathManipulator &pm() { return _path_manipulator; }
+
+private:
+ list_type _nodelists;
+ PathManipulator &_path_manipulator;
+ friend class NodeList;
+ friend class Node;
+ friend class Handle;
+};
+
+
+
+// define inline Handle funcs after definition of Node
+inline Geom::Point Handle::relativePos() {
+ return position() - _parent->position();
+}
+inline void Handle::setRelativePos(Geom::Point const &p) {
+ setPosition(_parent->position() + p);
+}
+inline double Handle::length() {
+ return relativePos().length();
+}
+inline PathManipulator &Handle::_pm() {
+ return _parent->_pm();
+}
+inline PathManipulator &Node::_pm() {
+ return nodeList().subpathList().pm();
+}
+
+// definitions for node iterator
+template <typename N>
+NodeIterator<N>::operator bool() const {
+ return _node && static_cast<ListNode*>(_node->ln_list) != _node;
+}
+template <typename N>
+NodeIterator<N> &NodeIterator<N>::advance() {
+ ++(*this);
+ if (G_UNLIKELY(!*this) && _node->ln_list->closed()) ++(*this);
+ return *this;
+}
+template <typename N>
+NodeIterator<N> &NodeIterator<N>::retreat() {
+ --(*this);
+ if (G_UNLIKELY(!*this) && _node->ln_list->closed()) --(*this);
+ return *this;
+}
+
+} // namespace UI
+} // namespace Inkscape
+
+#endif
+
+/*
+ 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:encoding=utf-8:textwidth=99 :
diff --git a/src/ui/tool/path-manipulator.cpp b/src/ui/tool/path-manipulator.cpp
new file mode 100644
index 000000000..66f72f379
--- /dev/null
+++ b/src/ui/tool/path-manipulator.cpp
@@ -0,0 +1,1462 @@
+/** @file
+ * Path manipulator - implementation
+ */
+/* Authors:
+ * Krzysztof Kosiński <tweenk.pl@gmail.com>
+ *
+ * Copyright (C) 2009 Authors
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ */
+
+#include <string>
+#include <sstream>
+#include <deque>
+#include <stdexcept>
+#include <boost/shared_ptr.hpp>
+#include <2geom/bezier-curve.h>
+#include <2geom/bezier-utils.h>
+#include <2geom/svg-path.h>
+#include <glibmm.h>
+#include <glibmm/i18n.h>
+#include "ui/tool/path-manipulator.h"
+#include "desktop.h"
+#include "desktop-handles.h"
+#include "display/sp-canvas.h"
+#include "display/sp-canvas-util.h"
+#include "display/curve.h"
+#include "display/canvas-bpath.h"
+#include "document.h"
+#include "live_effects/effect.h"
+#include "live_effects/lpeobject.h"
+#include "live_effects/parameter/path.h"
+#include "sp-path.h"
+#include "helper/geom.h"
+#include "preferences.h"
+#include "style.h"
+#include "ui/tool/control-point-selection.h"
+#include "ui/tool/curve-drag-point.h"
+#include "ui/tool/event-utils.h"
+#include "ui/tool/multi-path-manipulator.h"
+#include "xml/node.h"
+#include "xml/node-observer.h"
+
+namespace Inkscape {
+namespace UI {
+
+namespace {
+/// Types of path changes that we must react to.
+enum PathChange {
+ PATH_CHANGE_D,
+ PATH_CHANGE_TRANSFORM
+};
+
+} // anonymous namespace
+
+/**
+ * Notifies the path manipulator when something changes the path being edited
+ * (e.g. undo / redo)
+ */
+class PathManipulatorObserver : public Inkscape::XML::NodeObserver {
+public:
+ PathManipulatorObserver(PathManipulator *p, Inkscape::XML::Node *node)
+ : _pm(p)
+ , _node(node)
+ , _blocked(false)
+ {
+ Inkscape::GC::anchor(_node);
+ _node->addObserver(*this);
+ }
+
+ ~PathManipulatorObserver() {
+ _node->removeObserver(*this);
+ Inkscape::GC::release(_node);
+ }
+
+ virtual void notifyAttributeChanged(Inkscape::XML::Node &/*node*/, GQuark attr,
+ Util::ptr_shared<char>, Util::ptr_shared<char>)
+ {
+ // do nothing if blocked
+ if (_blocked) return;
+
+ GQuark path_d = g_quark_from_static_string("d");
+ GQuark path_transform = g_quark_from_static_string("transform");
+ GQuark lpe_quark = _pm->_lpe_key.empty() ? 0 : g_quark_from_string(_pm->_lpe_key.data());
+
+ // only react to "d" (path data) and "transform" attribute changes
+ if (attr == lpe_quark || attr == path_d) {
+ _pm->_externalChange(PATH_CHANGE_D);
+ } else if (attr == path_transform) {
+ _pm->_externalChange(PATH_CHANGE_TRANSFORM);
+ }
+ }
+
+ void block() { _blocked = true; }
+ void unblock() { _blocked = false; }
+private:
+ PathManipulator *_pm;
+ Inkscape::XML::Node *_node;
+ bool _blocked;
+};
+
+void build_segment(Geom::PathBuilder &, Node *, Node *);
+
+PathManipulator::PathManipulator(MultiPathManipulator &mpm, SPPath *path,
+ Geom::Matrix const &et, guint32 outline_color, Glib::ustring lpe_key)
+ : PointManipulator(mpm._path_data.node_data.desktop, *mpm._path_data.node_data.selection)
+ , _subpaths(*this)
+ , _multi_path_manipulator(mpm)
+ , _path(path)
+ , _spcurve(new SPCurve())
+ , _dragpoint(new CurveDragPoint(*this))
+ , _observer(new PathManipulatorObserver(this, SP_OBJECT(path)->repr))
+ , _edit_transform(et)
+ , _num_selected(0)
+ , _show_handles(true)
+ , _show_outline(false)
+ , _show_path_direction(false)
+ , _live_outline(true)
+ , _live_objects(true)
+ , _lpe_key(lpe_key)
+{
+ if (_lpe_key.empty()) {
+ _i2d_transform = sp_item_i2d_affine(SP_ITEM(path));
+ } else {
+ _i2d_transform = Geom::identity();
+ }
+ _d2i_transform = _i2d_transform.inverse();
+ _dragpoint->setVisible(false);
+
+ _getGeometry();
+
+ _outline = sp_canvas_bpath_new(_multi_path_manipulator._path_data.outline_group, NULL);
+ sp_canvas_item_hide(_outline);
+ sp_canvas_bpath_set_stroke(SP_CANVAS_BPATH(_outline), outline_color, 1.0,
+ SP_STROKE_LINEJOIN_MITER, SP_STROKE_LINECAP_BUTT);
+ sp_canvas_bpath_set_fill(SP_CANVAS_BPATH(_outline), 0, SP_WIND_RULE_NONZERO);
+
+ _selection.signal_update.connect(
+ sigc::mem_fun(*this, &PathManipulator::update));
+ _selection.signal_point_changed.connect(
+ sigc::mem_fun(*this, &PathManipulator::_selectionChanged));
+ _desktop->signal_zoom_changed.connect(
+ sigc::hide( sigc::mem_fun(*this, &PathManipulator::_updateOutlineOnZoomChange)));
+
+ _createControlPointsFromGeometry();
+}
+
+PathManipulator::~PathManipulator()
+{
+ delete _dragpoint;
+ delete _observer;
+ gtk_object_destroy(_outline);
+ _spcurve->unref();
+ clear();
+}
+
+/** Handle motion events to update the position of the curve drag point. */
+bool PathManipulator::event(GdkEvent *event)
+{
+ if (empty()) return false;
+
+ switch (event->type)
+ {
+ case GDK_MOTION_NOTIFY:
+ _updateDragPoint(event_point(event->motion));
+ break;
+ default: break;
+ }
+ return false;
+}
+
+/** Check whether the manipulator has any nodes. */
+bool PathManipulator::empty() {
+ return !_path || _subpaths.empty();
+}
+
+/** Update the display and the outline of the path. */
+void PathManipulator::update()
+{
+ _createGeometryFromControlPoints();
+}
+
+/** Store the changes to the path in XML. */
+void PathManipulator::writeXML()
+{
+ if (!_live_outline)
+ _updateOutline();
+ if (!_live_objects)
+ _setGeometry();
+
+ if (!_path) return;
+ _observer->block();
+ if (!empty()) {
+ SP_OBJECT(_path)->updateRepr();
+ _getXMLNode()->setAttribute(_nodetypesKey().data(), _createTypeString().data());
+ } else {
+ // this manipulator will have to be destroyed right after this call
+ _getXMLNode()->removeObserver(*_observer);
+ sp_object_ref(_path);
+ _path->deleteObject(true, true);
+ sp_object_unref(_path);
+ _path = 0;
+ }
+ _observer->unblock();
+}
+
+/** Remove all nodes from the path. */
+void PathManipulator::clear()
+{
+ // no longer necessary since nodes remove themselves from selection on destruction
+ //_removeNodesFromSelection();
+ _subpaths.clear();
+}
+
+/** Select all nodes in subpaths that have something selected. */
+void PathManipulator::selectSubpaths()
+{
+ for (std::list<SubpathPtr>::iterator i = _subpaths.begin(); i != _subpaths.end(); ++i) {
+ NodeList::iterator sp_start = (*i)->begin(), sp_end = (*i)->end();
+ for (NodeList::iterator j = sp_start; j != sp_end; ++j) {
+ if (j->selected()) {
+ // if at least one of the nodes from this subpath is selected,
+ // select all nodes from this subpath
+ for (NodeList::iterator ins = sp_start; ins != sp_end; ++ins)
+ _selection.insert(ins.ptr());
+ continue;
+ }
+ }
+ }
+}
+
+/** Move the selection forward or backward by one node in each subpath, based on the sign
+ * of the parameter. */
+void PathManipulator::shiftSelection(int dir)
+{
+ if (dir == 0) return;
+ if (_num_selected == 0) {
+ // select the first node of the path.
+ SubpathList::iterator s = _subpaths.begin();
+ if (s == _subpaths.end()) return;
+ NodeList::iterator n = (*s)->begin();
+ if (n != (*s)->end())
+ _selection.insert(n.ptr());
+ return;
+ }
+ // We cannot do any tricks here, like iterating in different directions based on
+ // the sign and only setting the selection of nodes behind us, because it would break
+ // for closed paths.
+ for (SubpathList::iterator i = _subpaths.begin(); i != _subpaths.end(); ++i) {
+ std::deque<bool> sels; // I hope this is specialized for bools!
+ unsigned num = 0;
+
+ for (NodeList::iterator j = (*i)->begin(); j != (*i)->end(); ++j) {
+ sels.push_back(j->selected());
+ _selection.erase(j.ptr());
+ ++num;
+ }
+ if (num == 0) continue; // should never happen! zero-node subpaths are not allowed
+
+ num = 0;
+ // In closed subpath, shift the selection cyclically. In an open one,
+ // let the selection 'slide into nothing' at ends.
+ if (dir > 0) {
+ if ((*i)->closed()) {
+ bool last = sels.back();
+ sels.pop_back();
+ sels.push_front(last);
+ } else {
+ sels.push_front(false);
+ }
+ } else {
+ if ((*i)->closed()) {
+ bool first = sels.front();
+ sels.pop_front();
+ sels.push_back(first);
+ } else {
+ sels.push_back(false);
+ num = 1;
+ }
+ }
+
+ for (NodeList::iterator j = (*i)->begin(); j != (*i)->end(); ++j) {
+ if (sels[num]) _selection.insert(j.ptr());
+ ++num;
+ }
+ }
+}
+
+/** Invert selection in the selected subpaths. */
+void PathManipulator::invertSelectionInSubpaths()
+{
+ for (SubpathList::iterator i = _subpaths.begin(); i != _subpaths.end(); ++i) {
+ for (NodeList::iterator j = (*i)->begin(); j != (*i)->end(); ++j) {
+ if (j->selected()) {
+ // found selected node - invert selection in this subpath
+ for (NodeList::iterator k = (*i)->begin(); k != (*i)->end(); ++k) {
+ if (k->selected()) _selection.erase(k.ptr());
+ else _selection.insert(k.ptr());
+ }
+ // next subpath
+ break;
+ }
+ }
+ }
+}
+
+/** Insert a new node in the middle of each selected segment. */
+void PathManipulator::insertNodes()
+{
+ if (_num_selected < 2) return;
+
+ for (SubpathList::iterator i = _subpaths.begin(); i != _subpaths.end(); ++i) {
+ for (NodeList::iterator j = (*i)->begin(); j != (*i)->end(); ++j) {
+ NodeList::iterator k = j.next();
+ if (k && j->selected() && k->selected()) {
+ j = subdivideSegment(j, 0.5);
+ _selection.insert(j.ptr());
+ }
+ }
+ }
+}
+
+/** Replace contiguous selections of nodes in each subpath with one node. */
+void PathManipulator::weldNodes(NodeList::iterator preserve_pos)
+{
+ if (_num_selected < 2) return;
+ hideDragPoint();
+
+ bool pos_valid = preserve_pos;
+ for (SubpathList::iterator i = _subpaths.begin(); i != _subpaths.end(); ++i) {
+ SubpathPtr sp = *i;
+ unsigned num_selected = 0, num_unselected = 0;
+ for (NodeList::iterator j = sp->begin(); j != sp->end(); ++j) {
+ if (j->selected()) ++num_selected;
+ else ++num_unselected;
+ }
+ if (num_selected < 2) continue;
+ if (num_unselected == 0) {
+ // if all nodes in a subpath are selected, the operation doesn't make much sense
+ continue;
+ }
+
+ // Start from unselected node in closed paths, so that we don't start in the middle
+ // of a selection
+ NodeList::iterator sel_beg = sp->begin(), sel_end;
+ if (sp->closed()) {
+ while (sel_beg->selected()) ++sel_beg;
+ }
+
+ // Work loop
+ while (num_selected > 0) {
+ // Find selected node
+ while (sel_beg && !sel_beg->selected()) sel_beg = sel_beg.next();
+ if (!sel_beg) throw std::logic_error("Join nodes: end of open path reached, "
+ "but there are still nodes to process!");
+
+ // note: this is initialized to zero, because the loop below counts sel_beg as well
+ // the loop conditions are simpler that way
+ unsigned num_points = 0;
+ bool use_pos = false;
+ Geom::Point back_pos, front_pos;
+ back_pos = *sel_beg->back();
+
+ for (sel_end = sel_beg; sel_end && sel_end->selected(); sel_end = sel_end.next()) {
+ ++num_points;
+ front_pos = *sel_end->front();
+ if (pos_valid && sel_end == preserve_pos) use_pos = true;
+ }
+ if (num_points > 1) {
+ Geom::Point joined_pos;
+ if (use_pos) {
+ joined_pos = preserve_pos->position();
+ pos_valid = false;
+ } else {
+ joined_pos = Geom::middle_point(back_pos, front_pos);
+ }
+ sel_beg->setType(NODE_CUSP, false);
+ sel_beg->move(joined_pos);
+ // do not move handles if they aren't degenerate
+ if (!sel_beg->back()->isDegenerate()) {
+ sel_beg->back()->setPosition(back_pos);
+ }
+ if (!sel_end.prev()->front()->isDegenerate()) {
+ sel_beg->front()->setPosition(front_pos);
+ }
+ sel_beg = sel_beg.next();
+ while (sel_beg != sel_end) {
+ NodeList::iterator next = sel_beg.next();
+ sp->erase(sel_beg);
+ sel_beg = next;
+ --num_selected;
+ }
+ }
+ --num_selected; // for the joined node or single selected node
+ }
+ }
+}
+
+/** Remove nodes in the middle of selected segments. */
+void PathManipulator::weldSegments()
+{
+ if (_num_selected < 2) return;
+ hideDragPoint();
+
+ for (SubpathList::iterator i = _subpaths.begin(); i != _subpaths.end(); ++i) {
+ SubpathPtr sp = *i;
+ unsigned num_selected = 0, num_unselected = 0;
+ for (NodeList::iterator j = sp->begin(); j != sp->end(); ++j) {
+ if (j->selected()) ++num_selected;
+ else ++num_unselected;
+ }
+ if (num_selected < 3) continue;
+ if (num_unselected == 0 && sp->closed()) {
+ // if all nodes in a closed subpath are selected, the operation doesn't make much sense
+ continue;
+ }
+
+ // Start from unselected node in closed paths, so that we don't start in the middle
+ // of a selection
+ NodeList::iterator sel_beg = sp->begin(), sel_end;
+ if (sp->closed()) {
+ while (sel_beg->selected()) ++sel_beg;
+ }
+
+ // Work loop
+ while (num_selected > 0) {
+ // Find selected node
+ while (sel_beg && !sel_beg->selected()) sel_beg = sel_beg.next();
+ if (!sel_beg) throw std::logic_error("Join nodes: end of open path reached, "
+ "but there are still nodes to process!");
+
+ // note: this is initialized to zero, because the loop below counts sel_beg as well
+ // the loop conditions are simpler that way
+ unsigned num_points = 0;
+
+ // find the end of selected segment
+ for (sel_end = sel_beg; sel_end && sel_end->selected(); sel_end = sel_end.next()) {
+ ++num_points;
+ }
+ if (num_points > 2) {
+ // remove nodes in the middle
+ sel_beg = sel_beg.next();
+ while (sel_beg != sel_end.prev()) {
+ NodeList::iterator next = sel_beg.next();
+ sp->erase(sel_beg);
+ sel_beg = next;
+ }
+ sel_beg = sel_end;
+ }
+ num_selected -= num_points;
+ }
+ }
+}
+
+/** Break the subpath at selected nodes. It also works for single node closed paths. */
+void PathManipulator::breakNodes()
+{
+ for (SubpathList::iterator i = _subpaths.begin(); i != _subpaths.end(); ++i) {
+ SubpathPtr sp = *i;
+ NodeList::iterator cur = sp->begin(), end = sp->end();
+ if (!sp->closed()) {
+ // Each open path must have at least two nodes so no checks are required.
+ // For 2-node open paths, cur == end
+ ++cur;
+ --end;
+ }
+ for (; cur != end; ++cur) {
+ if (!cur->selected()) continue;
+ SubpathPtr ins;
+ bool becomes_open = false;
+
+ if (sp->closed()) {
+ // Move the node to break at to the beginning of path
+ if (cur != sp->begin())
+ sp->splice(sp->begin(), *sp, cur, sp->end());
+ sp->setClosed(false);
+ ins = sp;
+ becomes_open = true;
+ } else {
+ SubpathPtr new_sp(new NodeList(_subpaths));
+ new_sp->splice(new_sp->end(), *sp, sp->begin(), cur);
+ _subpaths.insert(i, new_sp);
+ ins = new_sp;
+ }
+
+ Node *n = new Node(_multi_path_manipulator._path_data.node_data, cur->position());
+ ins->insert(ins->end(), n);
+ cur->setType(NODE_CUSP, false);
+ n->back()->setRelativePos(cur->back()->relativePos());
+ cur->back()->retract();
+ n->sink();
+
+ if (becomes_open) {
+ cur = sp->begin(); // this will be increased to ++sp->begin()
+ end = --sp->end();
+ }
+ }
+ }
+}
+
+/** Delete selected nodes in the path, optionally substituting deleted segments with bezier curves
+ * in a way that attempts to preserve the original shape of the curve. */
+void PathManipulator::deleteNodes(bool keep_shape)
+{
+ if (_num_selected == 0) return;
+ hideDragPoint();
+
+ for (SubpathList::iterator i = _subpaths.begin(); i != _subpaths.end();) {
+ SubpathPtr sp = *i;
+
+ // If there are less than 2 unselected nodes in an open subpath or no unselected nodes
+ // in a closed one, delete entire subpath.
+ unsigned num_unselected = 0, num_selected = 0;
+ for (NodeList::iterator j = sp->begin(); j != sp->end(); ++j) {
+ if (j->selected()) ++num_selected;
+ else ++num_unselected;
+ }
+ if (num_selected == 0) {
+ ++i;
+ continue;
+ }
+ if (sp->closed() ? (num_unselected < 1) : (num_unselected < 2)) {
+ _subpaths.erase(i++);
+ continue;
+ }
+
+ // In closed paths, start from an unselected node - otherwise we might start in the middle
+ // of a selected stretch and the resulting bezier fit would be suboptimal
+ NodeList::iterator sel_beg = sp->begin(), sel_end;
+ if (sp->closed()) {
+ while (sel_beg->selected()) ++sel_beg;
+ }
+ sel_end = sel_beg;
+
+ while (num_selected > 0) {
+ while (sel_beg && !sel_beg->selected()) {
+ sel_beg = sel_beg.next();
+ }
+ sel_end = sel_beg;
+
+ while (sel_end && sel_end->selected()) {
+ sel_end = sel_end.next();
+ }
+
+ num_selected -= _deleteStretch(sel_beg, sel_end, keep_shape);
+ sel_beg = sel_end;
+ }
+ ++i;
+ }
+}
+
+/** @brief Delete nodes between the two iterators.
+ * The given range can cross the beginning of the subpath in closed subpaths.
+ * @param start Beginning of the range to delete
+ * @param end End of the range
+ * @param keep_shape Whether to fit the handles at surrounding nodes to approximate
+ * the shape before deletion
+ * @return Number of deleted nodes */
+unsigned PathManipulator::_deleteStretch(NodeList::iterator start, NodeList::iterator end, bool keep_shape)
+{
+ unsigned const samples_per_segment = 10;
+ double const t_step = 1.0 / samples_per_segment;
+
+ unsigned del_len = 0;
+ for (NodeList::iterator i = start; i != end; i = i.next()) {
+ ++del_len;
+ }
+ if (del_len == 0) return 0;
+
+ // set surrounding node types to cusp if:
+ // 1. keep_shape is on, or
+ // 2. we are deleting at the end or beginning of an open path
+ if ((keep_shape || !end) && start.prev()) start.prev()->setType(NODE_CUSP, false);
+ if ((keep_shape || !start.prev()) && end) end->setType(NODE_CUSP, false);
+
+ if (keep_shape && start.prev() && end) {
+ unsigned num_samples = (del_len + 1) * samples_per_segment + 1;
+ Geom::Point *bezier_data = new Geom::Point[num_samples];
+ Geom::Point result[4];
+ unsigned seg = 0;
+
+ for (NodeList::iterator cur = start.prev(); cur != end; cur = cur.next()) {
+ Geom::CubicBezier bc(*cur, *cur->front(), *cur.next(), *cur.next()->back());
+ for (unsigned s = 0; s < samples_per_segment; ++s) {
+ bezier_data[seg * samples_per_segment + s] = bc.pointAt(t_step * s);
+ }
+ ++seg;
+ }
+ // Fill last point
+ bezier_data[num_samples - 1] = end->position();
+ // Compute replacement bezier curve
+ // TODO the fitting algorithm sucks - rewrite it to be awesome
+ bezier_fit_cubic(result, bezier_data, num_samples, 0.5);
+ delete[] bezier_data;
+
+ start.prev()->front()->setPosition(result[1]);
+ end->back()->setPosition(result[2]);
+ }
+
+ // We can't use nl->erase(start, end), because it would break when the stretch
+ // crosses the beginning of a closed subpath
+ NodeList &nl = start->nodeList();
+ while (start != end) {
+ NodeList::iterator next = start.next();
+ nl.erase(start);
+ start = next;
+ }
+
+ return del_len;
+}
+
+/** Removes selected segments */
+void PathManipulator::deleteSegments()
+{
+ if (_num_selected == 0) return;
+ hideDragPoint();
+
+ for (SubpathList::iterator i = _subpaths.begin(); i != _subpaths.end();) {
+ SubpathPtr sp = *i;
+ bool has_unselected = false;
+ unsigned num_selected = 0;
+ for (NodeList::iterator j = sp->begin(); j != sp->end(); ++j) {
+ if (j->selected()) {
+ ++num_selected;
+ } else {
+ has_unselected = true;
+ }
+ }
+ if (!has_unselected) {
+ _subpaths.erase(i++);
+ continue;
+ }
+
+ NodeList::iterator sel_beg = sp->begin();
+ if (sp->closed()) {
+ while (sel_beg && sel_beg->selected()) ++sel_beg;
+ }
+ while (num_selected > 0) {
+ if (!sel_beg->selected()) {
+ sel_beg = sel_beg.next();
+ continue;
+ }
+ NodeList::iterator sel_end = sel_beg;
+ unsigned num_points = 0;
+ while (sel_end && sel_end->selected()) {
+ sel_end = sel_end.next();
+ ++num_points;
+ }
+ if (num_points >= 2) {
+ // Retract end handles
+ sel_end.prev()->setType(NODE_CUSP, false);
+ sel_end.prev()->back()->retract();
+ sel_beg->setType(NODE_CUSP, false);
+ sel_beg->front()->retract();
+ if (sp->closed()) {
+ // In closed paths, relocate the beginning of the path to the last selected
+ // node and then unclose it. Remove the nodes from the first selected node
+ // to the new end of path.
+ if (sel_end.prev() != sp->begin())
+ sp->splice(sp->begin(), *sp, sel_end.prev(), sp->end());
+ sp->setClosed(false);
+ sp->erase(sel_beg.next(), sp->end());
+ } else {
+ // for open paths:
+ // 1. At end or beginning, delete including the node on the end or beginning
+ // 2. In the middle, delete only inner nodes
+ if (sel_beg == sp->begin()) {
+ sp->erase(sp->begin(), sel_end.prev());
+ } else if (sel_end == sp->end()) {
+ sp->erase(sel_beg.next(), sp->end());
+ } else {
+ SubpathPtr new_sp(new NodeList(_subpaths));
+ new_sp->splice(new_sp->end(), *sp, sp->begin(), sel_beg.next());
+ _subpaths.insert(i, new_sp);
+ if (sel_end.prev())
+ sp->erase(sp->begin(), sel_end.prev());
+ }
+ }
+ }
+ sel_beg = sel_end;
+ num_selected -= num_points;
+ }
+ ++i;
+ }
+}
+
+/** Reverse subpaths of the path.
+ * @param selected_only If true, only paths that have at least one selected node
+ * will be reversed. Otherwise all subpaths will be reversed. */
+void PathManipulator::reverseSubpaths(bool selected_only)
+{
+ for (SubpathList::iterator i = _subpaths.begin(); i != _subpaths.end(); ++i) {
+ if (selected_only) {
+ for (NodeList::iterator j = (*i)->begin(); j != (*i)->end(); ++j) {
+ if (j->selected()) {
+ (*i)->reverse();
+ break; // continue with the next subpath
+ }
+ }
+ } else {
+ (*i)->reverse();
+ }
+ }
+}
+
+/** Make selected segments curves / lines. */
+void PathManipulator::setSegmentType(SegmentType type)
+{
+ if (_num_selected == 0) return;
+ for (SubpathList::iterator i = _subpaths.begin(); i != _subpaths.end(); ++i) {
+ for (NodeList::iterator j = (*i)->begin(); j != (*i)->end(); ++j) {
+ NodeList::iterator k = j.next();
+ if (!(k && j->selected() && k->selected())) continue;
+ switch (type) {
+ case SEGMENT_STRAIGHT:
+ if (j->front()->isDegenerate() && k->back()->isDegenerate())
+ break;
+ j->front()->move(*j);
+ k->back()->move(*k);
+ break;
+ case SEGMENT_CUBIC_BEZIER:
+ if (!j->front()->isDegenerate() || !k->back()->isDegenerate())
+ break;
+ // move both handles to 1/3 of the line
+ j->front()->move(j->position() + (k->position() - j->position()) / 3);
+ k->back()->move(k->position() + (j->position() - k->position()) / 3);
+ break;
+ }
+ }
+ }
+}
+
+void PathManipulator::scaleHandle(Node *n, int which, int dir, bool pixel)
+{
+ if (n->type() == NODE_SYMMETRIC || n->type() == NODE_AUTO) {
+ n->setType(NODE_SMOOTH);
+ }
+ Handle *h = _chooseHandle(n, which);
+ double length_change;
+
+ if (pixel) {
+ length_change = 1.0 / _desktop->current_zoom() * dir;
+ } else {
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ length_change = prefs->getDoubleLimited("/options/defaultscale/value", 2, 1, 1000);
+ length_change *= dir;
+ }
+
+ Geom::Point relpos;
+ if (h->isDegenerate()) {
+ if (dir < 0) return;
+ Node *nh = n->nodeToward(h);
+ if (!nh) return;
+ relpos = Geom::unit_vector(nh->position() - n->position()) * length_change;
+ } else {
+ relpos = h->relativePos();
+ double rellen = relpos.length();
+ relpos *= ((rellen + length_change) / rellen);
+ }
+ h->setRelativePos(relpos);
+ update();
+
+ gchar const *key = which < 0 ? "handle:scale:left" : "handle:scale:right";
+ _commit(_("Scale handle"), key);
+}
+
+void PathManipulator::rotateHandle(Node *n, int which, int dir, bool pixel)
+{
+ if (n->type() != NODE_CUSP) {
+ n->setType(NODE_CUSP);
+ }
+ Handle *h = _chooseHandle(n, which);
+ if (h->isDegenerate()) return;
+
+ double angle;
+ if (pixel) {
+ // Rotate by "one pixel"
+ angle = atan2(1.0 / _desktop->current_zoom(), h->length()) * dir;
+ } else {
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ int snaps = prefs->getIntLimited("/options/rotationsnapsperpi/value", 12, 1, 1000);
+ angle = M_PI * dir / snaps;
+ }
+
+ h->setRelativePos(h->relativePos() * Geom::Rotate(angle));
+ update();
+ gchar const *key = which < 0 ? "handle:rotate:left" : "handle:rotate:right";
+ _commit(_("Rotate handle"), key);
+}
+
+Handle *PathManipulator::_chooseHandle(Node *n, int which)
+{
+ NodeList::iterator i = NodeList::get_iterator(n);
+ Node *prev = i.prev().ptr();
+ Node *next = i.next().ptr();
+
+ // on an endnode, the remaining handle automatically wins
+ if (!next) return n->back();
+ if (!prev) return n->front();
+
+ // compare X coord ofline segments
+ Geom::Point npos = next->position();
+ Geom::Point ppos = prev->position();
+ if (which < 0) {
+ // pick left handle.
+ // we just swap the handles and pick the right handle below.
+ std::swap(npos, ppos);
+ }
+
+ if (npos[Geom::X] >= ppos[Geom::X]) {
+ return n->front();
+ } else {
+ return n->back();
+ }
+}
+
+/** Set the visibility of handles. */
+void PathManipulator::showHandles(bool show)
+{
+ if (show == _show_handles) return;
+ if (show) {
+ for (SubpathList::iterator i = _subpaths.begin(); i != _subpaths.end(); ++i) {
+ for (NodeList::iterator j = (*i)->begin(); j != (*i)->end(); ++j) {
+ if (!j->selected()) continue;
+ j->showHandles(true);
+ if (j.prev()) j.prev()->showHandles(true);
+ if (j.next()) j.next()->showHandles(true);
+ }
+ }
+ } else {
+ for (SubpathList::iterator i = _subpaths.begin(); i != _subpaths.end(); ++i) {
+ for (NodeList::iterator j = (*i)->begin(); j != (*i)->end(); ++j) {
+ j->showHandles(false);
+ }
+ }
+ }
+ _show_handles = show;
+}
+
+/** Set the visibility of outline. */
+void PathManipulator::showOutline(bool show)
+{
+ if (show == _show_outline) return;
+ _show_outline = show;
+ _updateOutline();
+}
+
+void PathManipulator::showPathDirection(bool show)
+{
+ if (show == _show_path_direction) return;
+ _show_path_direction = show;
+ _updateOutline();
+}
+
+void PathManipulator::setLiveOutline(bool set)
+{
+ _live_outline = set;
+}
+
+void PathManipulator::setLiveObjects(bool set)
+{
+ _live_objects = set;
+}
+
+void PathManipulator::setControlsTransform(Geom::Matrix const &tnew)
+{
+ Geom::Matrix delta = _i2d_transform.inverse() * _edit_transform.inverse() * tnew * _i2d_transform;
+ _edit_transform = tnew;
+ for (SubpathList::iterator i = _subpaths.begin(); i != _subpaths.end(); ++i) {
+ for (NodeList::iterator j = (*i)->begin(); j != (*i)->end(); ++j) {
+ j->transform(delta);
+ }
+ }
+ _createGeometryFromControlPoints();
+}
+
+/** Hide the curve drag point until the next motion event.
+ * This should be called at the beginning of every method that can delete nodes.
+ * Otherwise the invalidated iterator in the dragpoint can cause crashes. */
+void PathManipulator::hideDragPoint()
+{
+ _dragpoint->setVisible(false);
+ _dragpoint->setIterator(NodeList::iterator());
+}
+
+/** Insert a node in the segment beginning with the supplied iterator,
+ * at the given time value */
+NodeList::iterator PathManipulator::subdivideSegment(NodeList::iterator first, double t)
+{
+ if (!first) throw std::invalid_argument("Subdivide after invalid iterator");
+ NodeList &list = NodeList::get(first);
+ NodeList::iterator second = first.next();
+ if (!second) throw std::invalid_argument("Subdivide after last node in open path");
+
+ // We need to insert the segment after 'first'. We can't simply use 'second'
+ // as the point of insertion, because when 'first' is the last node of closed path,
+ // the new node will be inserted as the first node instead.
+ NodeList::iterator insert_at = first;
+ ++insert_at;
+
+ NodeList::iterator inserted;
+ if (first->front()->isDegenerate() && second->back()->isDegenerate()) {
+ // for a line segment, insert a cusp node
+ Node *n = new Node(_multi_path_manipulator._path_data.node_data,
+ Geom::lerp(t, first->position(), second->position()));
+ n->setType(NODE_CUSP, false);
+ inserted = list.insert(insert_at, n);
+ } else {
+ // build bezier curve and subdivide
+ Geom::CubicBezier temp(first->position(), first->front()->position(),
+ second->back()->position(), second->position());
+ std::pair<Geom::CubicBezier, Geom::CubicBezier> div = temp.subdivide(t);
+ std::vector<Geom::Point> seg1 = div.first.points(), seg2 = div.second.points();
+
+ // set new handle positions
+ Node *n = new Node(_multi_path_manipulator._path_data.node_data, seg2[0]);
+ n->back()->setPosition(seg1[2]);
+ n->front()->setPosition(seg2[1]);
+ n->setType(NODE_SMOOTH, false);
+ inserted = list.insert(insert_at, n);
+
+ first->front()->move(seg1[1]);
+ second->back()->move(seg2[2]);
+ }
+ return inserted;
+}
+
+/** Find the node that is closest/farthest from the origin
+ * @param origin Point of reference
+ * @param search_selected Consider selected nodes
+ * @param search_unselected Consider unselected nodes
+ * @param closest If true, return closest node, if false, return farthest
+ * @return The matching node, or an empty iterator if none found
+ */
+NodeList::iterator PathManipulator::extremeNode(NodeList::iterator origin, bool search_selected,
+ bool search_unselected, bool closest)
+{
+ NodeList::iterator match;
+ double extr_dist = closest ? HUGE_VAL : -HUGE_VAL;
+ if (_num_selected == 0 && !search_unselected) return match;
+
+ for (SubpathList::iterator i = _subpaths.begin(); i != _subpaths.end(); ++i) {
+ for (NodeList::iterator j = (*i)->begin(); j != (*i)->end(); ++j) {
+ if(j->selected()) {
+ if (!search_selected) continue;
+ } else {
+ if (!search_unselected) continue;
+ }
+ double dist = Geom::distance(*j, *origin);
+ bool cond = closest ? (dist < extr_dist) : (dist > extr_dist);
+ if (cond) {
+ match = j;
+ extr_dist = dist;
+ }
+ }
+ }
+ return match;
+}
+
+/** Called by the XML observer when something else than us modifies the path. */
+void PathManipulator::_externalChange(unsigned type)
+{
+ switch (type) {
+ case PATH_CHANGE_D: {
+ _getGeometry();
+
+ // ugly: stored offsets of selected nodes in a vector
+ // vector<bool> should be specialized so that it takes only 1 bit per value
+ std::vector<bool> selpos;
+ for (SubpathList::iterator i = _subpaths.begin(); i != _subpaths.end(); ++i) {
+ for (NodeList::iterator j = (*i)->begin(); j != (*i)->end(); ++j) {
+ selpos.push_back(j->selected());
+ }
+ }
+ unsigned size = selpos.size(), curpos = 0;
+
+ _createControlPointsFromGeometry();
+
+ for (SubpathList::iterator i = _subpaths.begin(); i != _subpaths.end(); ++i) {
+ for (NodeList::iterator j = (*i)->begin(); j != (*i)->end(); ++j) {
+ if (curpos >= size) goto end_restore;
+ if (selpos[curpos]) _selection.insert(j.ptr());
+ ++curpos;
+ }
+ }
+ end_restore:
+
+ _updateOutline();
+ } break;
+ case PATH_CHANGE_TRANSFORM: {
+ Geom::Matrix i2d_change = _d2i_transform;
+ _i2d_transform = sp_item_i2d_affine(SP_ITEM(_path));
+ _d2i_transform = _i2d_transform.inverse();
+ i2d_change *= _i2d_transform;
+ for (SubpathList::iterator i = _subpaths.begin(); i != _subpaths.end(); ++i) {
+ for (NodeList::iterator j = (*i)->begin(); j != (*i)->end(); ++j) {
+ j->transform(i2d_change);
+ }
+ }
+ _updateOutline();
+ } break;
+ default: break;
+ }
+}
+
+/** Create nodes and handles based on the XML of the edited path. */
+void PathManipulator::_createControlPointsFromGeometry()
+{
+ clear();
+
+ // sanitize pathvector and store it in SPCurve,
+ // so that _updateDragPoint doesn't crash on paths with naked movetos
+ Geom::PathVector pathv = pathv_to_linear_and_cubic_beziers(_spcurve->get_pathvector());
+ for (Geom::PathVector::iterator i = pathv.begin(); i != pathv.end(); ) {
+ // NOTE: this utilizes the fact that Geom::PathVector is an std::vector.
+ // When we erase an element, the next one slides into position,
+ // so we do not increment the iterator even though it is theoretically invalidated.
+ if (i->empty()) {
+ pathv.erase(i);
+ } else {
+ ++i;
+ }
+ }
+ _spcurve->set_pathvector(pathv);
+
+ pathv *= (_edit_transform * _i2d_transform);
+
+ // in this loop, we know that there are no zero-segment subpaths
+ for (Geom::PathVector::const_iterator pit = pathv.begin(); pit != pathv.end(); ++pit) {
+ // prepare new subpath
+ SubpathPtr subpath(new NodeList(_subpaths));
+ _subpaths.push_back(subpath);
+
+ Node *previous_node = new Node(_multi_path_manipulator._path_data.node_data, pit->initialPoint());
+ subpath->push_back(previous_node);
+ Geom::Curve const &cseg = pit->back_closed();
+ bool fuse_ends = pit->closed()
+ && Geom::are_near(cseg.initialPoint(), cseg.finalPoint());
+
+ for (Geom::Path::const_iterator cit = pit->begin(); cit != pit->end_open(); ++cit) {
+ Geom::Point pos = cit->finalPoint();
+ Node *current_node;
+ // if the closing segment is degenerate and the path is closed, we need to move
+ // the handle of the first node instead of creating a new one
+ if (fuse_ends && cit == --(pit->end_open())) {
+ current_node = subpath->begin().get_pointer();
+ } else {
+ /* regardless of segment type, create a new node at the end
+ * of this segment (unless this is the last segment of a closed path
+ * with a degenerate closing segment */
+ current_node = new Node(_multi_path_manipulator._path_data.node_data, pos);
+ subpath->push_back(current_node);
+ }
+ // if this is a bezier segment, move handles appropriately
+ if (Geom::CubicBezier const *cubic_bezier =
+ dynamic_cast<Geom::CubicBezier const*>(&*cit))
+ {
+ std::vector<Geom::Point> points = cubic_bezier->points();
+
+ previous_node->front()->setPosition(points[1]);
+ current_node ->back() ->setPosition(points[2]);
+ }
+ previous_node = current_node;
+ }
+ // If the path is closed, make the list cyclic
+ if (pit->closed()) subpath->setClosed(true);
+ }
+
+ // we need to set the nodetypes after all the handles are in place,
+ // so that pickBestType works correctly
+ // TODO maybe migrate to inkscape:node-types?
+ // TODO move this into SPPath - do not manipulate directly
+ gchar const *nts_raw = _path ? _path->repr->attribute(_nodetypesKey().data()) : 0;
+ std::string nodetype_string = nts_raw ? nts_raw : "";
+ /* Calculate the needed length of the nodetype string.
+ * For closed paths, the entry is duplicated for the starting node,
+ * so we can just use the count of segments including the closing one
+ * to include the extra end node. */
+ std::string::size_type nodetype_len = 0;
+ for (Geom::PathVector::const_iterator i = pathv.begin(); i != pathv.end(); ++i) {
+ if (i->empty()) continue;
+ nodetype_len += i->size_closed();
+ }
+ /* pad the string to required length with a bogus value.
+ * 'b' and any other letter not recognized by the parser causes the best fit to be set
+ * as the node type */
+ if (nodetype_len > nodetype_string.size()) {
+ nodetype_string.append(nodetype_len - nodetype_string.size(), 'b');
+ }
+ std::string::iterator tsi = nodetype_string.begin();
+ for (std::list<SubpathPtr>::iterator i = _subpaths.begin(); i != _subpaths.end(); ++i) {
+ for (NodeList::iterator j = (*i)->begin(); j != (*i)->end(); ++j) {
+ j->setType(Node::parse_nodetype(*tsi++), false);
+ }
+ if ((*i)->closed()) {
+ // STUPIDITY ALERT: it seems we need to use the duplicate type symbol instead of
+ // the first one to remain backward compatible.
+ (*i)->begin()->setType(Node::parse_nodetype(*tsi++), false);
+ }
+ }
+}
+
+/** Construct the geometric representation of nodes and handles, update the outline
+ * and display */
+void PathManipulator::_createGeometryFromControlPoints()
+{
+ Geom::PathBuilder builder;
+ for (std::list<SubpathPtr>::iterator spi = _subpaths.begin(); spi != _subpaths.end(); ) {
+ SubpathPtr subpath = *spi;
+ if (subpath->empty()) {
+ _subpaths.erase(spi++);
+ continue;
+ }
+ NodeList::iterator prev = subpath->begin();
+ builder.moveTo(prev->position());
+
+ for (NodeList::iterator i = ++subpath->begin(); i != subpath->end(); ++i) {
+ build_segment(builder, prev.ptr(), i.ptr());
+ prev = i;
+ }
+ if (subpath->closed()) {
+ // Here we link the last and first node if the path is closed.
+ // If the last segment is Bezier, we add it.
+ if (!prev->front()->isDegenerate() || !subpath->begin()->back()->isDegenerate()) {
+ build_segment(builder, prev.ptr(), subpath->begin().ptr());
+ }
+ // if that segment is linear, we just call closePath().
+ builder.closePath();
+ }
+ ++spi;
+ }
+ builder.finish();
+ _spcurve->set_pathvector(builder.peek() * (_edit_transform * _i2d_transform).inverse());
+ if (_live_outline)
+ _updateOutline();
+ if (_live_objects)
+ _setGeometry();
+}
+
+/** Build one segment of the geometric representation.
+ * @relates PathManipulator */
+void build_segment(Geom::PathBuilder &builder, Node *prev_node, Node *cur_node)
+{
+ if (cur_node->back()->isDegenerate() && prev_node->front()->isDegenerate())
+ {
+ // NOTE: It seems like the renderer cannot correctly handle vline / hline segments,
+ // and trying to display a path using them results in funny artifacts.
+ builder.lineTo(cur_node->position());
+ } else {
+ // this is a bezier segment
+ builder.curveTo(
+ prev_node->front()->position(),
+ cur_node->back()->position(),
+ cur_node->position());
+ }
+}
+
+/** Construct a node type string to store in the sodipodi:nodetypes attribute. */
+std::string PathManipulator::_createTypeString()
+{
+ // precondition: no single-node subpaths
+ std::stringstream tstr;
+ for (std::list<SubpathPtr>::iterator i = _subpaths.begin(); i != _subpaths.end(); ++i) {
+ for (NodeList::iterator j = (*i)->begin(); j != (*i)->end(); ++j) {
+ tstr << j->type();
+ }
+ // nodestring format peculiarity: first node is counted twice for closed paths
+ if ((*i)->closed()) tstr << (*i)->begin()->type();
+ }
+ return tstr.str();
+}
+
+/** Update the path outline. */
+void PathManipulator::_updateOutline()
+{
+ if (!_show_outline) {
+ sp_canvas_item_hide(_outline);
+ return;
+ }
+
+ Geom::PathVector pv = _spcurve->get_pathvector();
+ pv *= (_edit_transform * _i2d_transform);
+ // This SPCurve thing has to be killed with extreme prejudice
+ SPCurve *_hc = new SPCurve();
+ if (_show_path_direction) {
+ // To show the direction, we append additional subpaths which consist of a single
+ // linear segment that starts at the time value of 0.5 and extends for 10 pixels
+ // at an angle 150 degrees from the unit tangent. This creates the appearance
+ // of little 'harpoons' that show the direction of the subpaths.
+ Geom::PathVector arrows;
+ for (Geom::PathVector::iterator i = pv.begin(); i != pv.end(); ++i) {
+ Geom::Path &path = *i;
+ for (Geom::Path::const_iterator j = path.begin(); j != path.end_default(); ++j) {
+ Geom::Point at = j->pointAt(0.5);
+ Geom::Point ut = j->unitTangentAt(0.5);
+ // rotate the point
+ ut *= Geom::Rotate(150.0 / 180.0 * M_PI);
+ Geom::Point arrow_end = _desktop->w2d(
+ _desktop->d2w(at) + Geom::unit_vector(_desktop->d2w(ut)) * 10.0);
+
+ Geom::Path arrow(at);
+ arrow.appendNew<Geom::LineSegment>(arrow_end);
+ arrows.push_back(arrow);
+ }
+ }
+ pv.insert(pv.end(), arrows.begin(), arrows.end());
+ }
+ _hc->set_pathvector(pv);
+ sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(_outline), _hc);
+ sp_canvas_item_show(_outline);
+ _hc->unref();
+}
+
+/** Retrieve the geometry of the edited object from the object tree */
+void PathManipulator::_getGeometry()
+{
+ using namespace Inkscape::LivePathEffect;
+ if (!_lpe_key.empty()) {
+ Effect *lpe = LIVEPATHEFFECT(_path)->get_lpe();
+ if (lpe) {
+ PathParam *pathparam = dynamic_cast<PathParam *>(lpe->getParameter(_lpe_key.data()));
+ _spcurve->unref();
+ _spcurve = new SPCurve(pathparam->get_pathvector());
+ }
+ } else {
+ _spcurve->unref();
+ _spcurve = sp_path_get_curve_for_edit(_path);
+ }
+}
+
+/** Set the geometry of the edited object in the object tree, but do not commit to XML */
+void PathManipulator::_setGeometry()
+{
+ using namespace Inkscape::LivePathEffect;
+ if (empty()) return;
+
+ if (!_lpe_key.empty()) {
+ // copied from nodepath.cpp
+ // NOTE: if we are editing an LPE param, _path is not actually an SPPath, it is
+ // a LivePathEffectObject. (mad laughter)
+ Effect *lpe = LIVEPATHEFFECT(_path)->get_lpe();
+ if (lpe) {
+ PathParam *pathparam = dynamic_cast<PathParam *>(lpe->getParameter(_lpe_key.data()));
+ pathparam->set_new_value(_spcurve->get_pathvector(), false);
+ LIVEPATHEFFECT(_path)->requestModified(SP_OBJECT_MODIFIED_FLAG);
+ }
+ } else {
+ if (_path->repr->attribute("inkscape:original-d"))
+ sp_path_set_original_curve(_path, _spcurve, false, false);
+ else
+ sp_shape_set_curve(SP_SHAPE(_path), _spcurve, false);
+ }
+}
+
+/** Figure out in what attribute to store the nodetype string. */
+Glib::ustring PathManipulator::_nodetypesKey()
+{
+ if (_lpe_key.empty()) return "sodipodi:nodetypes";
+ return _lpe_key + "-nodetypes";
+}
+
+/** Return the XML node we are editing.
+ * This method is wrong but necessary at the moment. */
+Inkscape::XML::Node *PathManipulator::_getXMLNode()
+{
+ if (_lpe_key.empty()) return _path->repr;
+ return LIVEPATHEFFECT(_path)->repr;
+}
+
+bool PathManipulator::_nodeClicked(Node *n, GdkEventButton *event)
+{
+ if (event->button != 1) return false;
+ if (held_alt(*event) && held_control(*event)) {
+ // Ctrl+Alt+click: delete nodes
+ hideDragPoint();
+ NodeList::iterator iter = NodeList::get_iterator(n);
+ NodeList &nl = iter->nodeList();
+
+ if (nl.size() <= 1 || (nl.size() <= 2 && !nl.closed())) {
+ // Removing last node of closed path - delete it
+ nl.kill();
+ } else {
+ // In other cases, delete the node under cursor
+ _deleteStretch(iter, iter.next(), true);
+ }
+
+ if (!empty()) {
+ update();
+ }
+ // We need to call MPM's method because it could have been our last node
+ _multi_path_manipulator._doneWithCleanup(_("Delete node"));
+
+ return true;
+ } else if (held_control(*event)) {
+ // Ctrl+click: cycle between node types
+ if (n->isEndNode()) {
+ if (n->type() == NODE_CUSP) {
+ n->setType(NODE_SMOOTH);
+ } else {
+ n->setType(NODE_CUSP);
+ }
+ } else {
+ n->setType(static_cast<NodeType>((n->type() + 1) % NODE_LAST_REAL_TYPE));
+ }
+ update();
+ _commit(_("Cycle node type"));
+ return true;
+ }
+ return false;
+}
+
+void PathManipulator::_handleGrabbed()
+{
+ _selection.hideTransformHandles();
+}
+
+void PathManipulator::_handleUngrabbed()
+{
+ _selection.restoreTransformHandles();
+ _commit(_("Drag handle"));
+}
+
+bool PathManipulator::_handleClicked(Handle *h, GdkEventButton *event)
+{
+ // retracting by Ctrl+click
+ if (event->button == 1 && held_control(*event)) {
+ h->move(h->parent()->position());
+ update();
+ _commit(_("Retract handle"));
+ return true;
+ }
+ return false;
+}
+
+void PathManipulator::_selectionChanged(SelectableControlPoint *p, bool selected)
+{
+ if (selected) ++_num_selected;
+ else --_num_selected;
+
+ // don't do anything if we do not show handles
+ if (!_show_handles) return;
+
+ // only do something if a node changed selection state
+ Node *node = dynamic_cast<Node*>(p);
+ if (!node) return;
+
+ // update handle display
+ NodeList::iterator iters[5];
+ iters[2] = NodeList::get_iterator(node);
+ iters[1] = iters[2].prev();
+ iters[3] = iters[2].next();
+ if (selected) {
+ // selection - show handles on this node and adjacent ones
+ node->showHandles(true);
+ if (iters[1]) iters[1]->showHandles(true);
+ if (iters[3]) iters[3]->showHandles(true);
+ } else {
+ /* Deselection is more complex.
+ * The change might affect 3 nodes - this one and two adjacent.
+ * If the node and both its neighbors are deselected, hide handles.
+ * Otherwise, leave as is. */
+ if (iters[1]) iters[0] = iters[1].prev();
+ if (iters[3]) iters[4] = iters[3].next();
+ bool nodesel[5];
+ for (int i = 0; i < 5; ++i) {
+ nodesel[i] = iters[i] && iters[i]->selected();
+ }
+ for (int i = 1; i < 4; ++i) {
+ if (iters[i] && !nodesel[i-1] && !nodesel[i] && !nodesel[i+1]) {
+ iters[i]->showHandles(false);
+ }
+ }
+ }
+}
+
+/** Removes all nodes belonging to this manipulator from the control pont selection */
+void PathManipulator::_removeNodesFromSelection()
+{
+ // remove this manipulator's nodes from selection
+ for (std::list<SubpathPtr>::iterator i = _subpaths.begin(); i != _subpaths.end(); ++i) {
+ for (NodeList::iterator j = (*i)->begin(); j != (*i)->end(); ++j) {
+ _selection.erase(j.get_pointer());
+ }
+ }
+}
+
+/** Update the XML representation and put the specified annotation on the undo stack */
+void PathManipulator::_commit(Glib::ustring const &annotation)
+{
+ writeXML();
+ sp_document_done(sp_desktop_document(_desktop), SP_VERB_CONTEXT_NODE, annotation.data());
+}
+
+void PathManipulator::_commit(Glib::ustring const &annotation, gchar const *key)
+{
+ writeXML();
+ sp_document_maybe_done(sp_desktop_document(_desktop), key, SP_VERB_CONTEXT_NODE,
+ annotation.data());
+}
+
+/** Update the position of the curve drag point such that it is over the nearest
+ * point of the path. */
+void PathManipulator::_updateDragPoint(Geom::Point const &evp)
+{
+ Geom::Matrix to_desktop = _edit_transform * _i2d_transform;
+ Geom::PathVector pv = _spcurve->get_pathvector();
+ boost::optional<Geom::PathVectorPosition> pvp
+ = Geom::nearestPoint(pv, _desktop->w2d(evp) * to_desktop.inverse());
+ if (!pvp) return;
+ Geom::Point nearest_point = _desktop->d2w(pv.at(pvp->path_nr).pointAt(pvp->t) * to_desktop);
+
+ double fracpart;
+ std::list<SubpathPtr>::iterator spi = _subpaths.begin();
+ for (unsigned i = 0; i < pvp->path_nr; ++i, ++spi) {}
+ NodeList::iterator first = (*spi)->before(pvp->t, &fracpart);
+
+ double stroke_tolerance = _getStrokeTolerance();
+ if (Geom::distance(evp, nearest_point) < stroke_tolerance) {
+ _dragpoint->setVisible(true);
+ _dragpoint->setPosition(_desktop->w2d(nearest_point));
+ _dragpoint->setSize(2 * stroke_tolerance);
+ _dragpoint->setTimeValue(fracpart);
+ _dragpoint->setIterator(first);
+ } else {
+ _dragpoint->setVisible(false);
+ }
+}
+
+/// This is called on zoom change to update the direction arrows
+void PathManipulator::_updateOutlineOnZoomChange()
+{
+ if (_show_path_direction) _updateOutline();
+}
+
+/** Compute the radius from the edge of the path where clicks chould initiate a curve drag
+ * or segment selection, in window coordinates. */
+double PathManipulator::_getStrokeTolerance()
+{
+ /* Stroke event tolerance is equal to half the stroke's width plus the global
+ * drag tolerance setting. */
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ double ret = prefs->getIntLimited("/options/dragtolerance/value", 2, 0, 100);
+ if (_path && SP_OBJECT_STYLE(_path) && !SP_OBJECT_STYLE(_path)->stroke.isNone()) {
+ ret += SP_OBJECT_STYLE(_path)->stroke_width.computed * 0.5
+ * (_edit_transform * _i2d_transform).descrim() // scale to desktop coords
+ * _desktop->current_zoom(); // == _d2w.descrim() - scale to window coords
+ }
+ return ret;
+}
+
+} // 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:encoding=utf-8:textwidth=99 :
diff --git a/src/ui/tool/path-manipulator.h b/src/ui/tool/path-manipulator.h
new file mode 100644
index 000000000..a8f1c957e
--- /dev/null
+++ b/src/ui/tool/path-manipulator.h
@@ -0,0 +1,165 @@
+/** @file
+ * Path manipulator - a component that edits a single path on-canvas
+ */
+/* Authors:
+ * Krzysztof Kosiński <tweenk.pl@gmail.com>
+ *
+ * Copyright (C) 2009 Authors
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ */
+
+#ifndef SEEN_UI_TOOL_PATH_MANIPULATOR_H
+#define SEEN_UI_TOOL_PATH_MANIPULATOR_H
+
+#include <string>
+#include <memory>
+#include <2geom/pathvector.h>
+#include <2geom/matrix.h>
+#include <boost/shared_ptr.hpp>
+#include <boost/weak_ptr.hpp>
+#include "display/display-forward.h"
+#include "forward.h"
+#include "ui/tool/node.h"
+#include "ui/tool/manipulator.h"
+
+struct SPCanvasItem;
+
+namespace Inkscape {
+namespace XML { class Node; }
+
+namespace UI {
+
+class PathManipulator;
+class ControlPointSelection;
+class PathManipulatorObserver;
+class CurveDragPoint;
+class PathCanvasGroups;
+class MultiPathManipulator;
+class Node;
+class Handle;
+
+struct PathSharedData {
+ NodeSharedData node_data;
+ SPCanvasGroup *outline_group;
+ SPCanvasGroup *dragpoint_group;
+};
+
+/**
+ * Manipulator that edits a single path using nodes with handles.
+ * Currently only cubic bezier and linear segments are supported, but this might change
+ * some time in the future.
+ */
+class PathManipulator : public PointManipulator {
+public:
+ typedef SPPath *ItemType;
+
+ PathManipulator(MultiPathManipulator &mpm, SPPath *path, Geom::Matrix const &edit_trans,
+ guint32 outline_color, Glib::ustring lpe_key);
+ ~PathManipulator();
+ virtual bool event(GdkEvent *);
+
+ bool empty();
+ void writeXML();
+ void update(); // update display, but don't commit
+ void clear(); // remove all nodes from manipulator
+ SPPath *item() { return _path; }
+
+ void selectSubpaths();
+ void shiftSelection(int dir);
+ void invertSelectionInSubpaths();
+
+ void insertNodes();
+ void weldNodes(NodeList::iterator preserve_pos = NodeList::iterator());
+ void weldSegments();
+ void breakNodes();
+ void deleteNodes(bool keep_shape = true);
+ void deleteSegments();
+ void reverseSubpaths(bool selected_only);
+ void setSegmentType(SegmentType);
+
+ void scaleHandle(Node *n, int which, int dir, bool pixel);
+ void rotateHandle(Node *n, int which, int dir, bool pixel);
+
+ void showOutline(bool show);
+ void showHandles(bool show);
+ void showPathDirection(bool show);
+ void setLiveOutline(bool set);
+ void setLiveObjects(bool set);
+ void setControlsTransform(Geom::Matrix const &);
+ void hideDragPoint();
+ MultiPathManipulator &mpm() { return _multi_path_manipulator; }
+
+ NodeList::iterator subdivideSegment(NodeList::iterator after, double t);
+ NodeList::iterator extremeNode(NodeList::iterator origin, bool search_selected,
+ bool search_unselected, bool closest);
+
+ static bool is_item_type(void *item);
+private:
+ typedef NodeList Subpath;
+ typedef boost::shared_ptr<NodeList> SubpathPtr;
+
+ void _createControlPointsFromGeometry();
+ void _createGeometryFromControlPoints();
+ unsigned _deleteStretch(NodeList::iterator first, NodeList::iterator last, bool keep_shape);
+ std::string _createTypeString();
+ void _updateOutline();
+ //void _setOutline(Geom::PathVector const &);
+ void _getGeometry();
+ void _setGeometry();
+ Glib::ustring _nodetypesKey();
+ Inkscape::XML::Node *_getXMLNode();
+
+ void _selectionChanged(SelectableControlPoint *p, bool selected);
+ bool _nodeClicked(Node *, GdkEventButton *);
+ void _handleGrabbed();
+ bool _handleClicked(Handle *, GdkEventButton *);
+ void _handleUngrabbed();
+
+ void _externalChange(unsigned type);
+ void _removeNodesFromSelection();
+ void _commit(Glib::ustring const &annotation);
+ void _commit(Glib::ustring const &annotation, gchar const *key);
+ void _updateDragPoint(Geom::Point const &);
+ void _updateOutlineOnZoomChange();
+ double _getStrokeTolerance();
+ Handle *_chooseHandle(Node *n, int which);
+
+ SubpathList _subpaths;
+ MultiPathManipulator &_multi_path_manipulator;
+ SPPath *_path;
+ SPCurve *_spcurve; // in item coordinates
+ SPCanvasItem *_outline;
+ CurveDragPoint *_dragpoint; // an invisible control point hoverng over curve
+ PathManipulatorObserver *_observer;
+ Geom::Matrix _d2i_transform; ///< desktop-to-item transform
+ Geom::Matrix _i2d_transform; ///< item-to-desktop transform, inverse of _d2i_transform
+ Geom::Matrix _edit_transform; ///< additional transform to apply to editing controls
+ unsigned _num_selected; ///< number of selected nodes
+ bool _show_handles;
+ bool _show_outline;
+ bool _show_path_direction;
+ bool _live_outline;
+ bool _live_objects;
+ Glib::ustring _lpe_key;
+
+ friend class PathManipulatorObserver;
+ friend class CurveDragPoint;
+ friend class Node;
+ friend class Handle;
+};
+
+} // namespace UI
+} // namespace Inkscape
+
+#endif
+
+/*
+ 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:encoding=utf-8:textwidth=99 :
diff --git a/src/ui/tool/selectable-control-point.cpp b/src/ui/tool/selectable-control-point.cpp
new file mode 100644
index 000000000..76028dd82
--- /dev/null
+++ b/src/ui/tool/selectable-control-point.cpp
@@ -0,0 +1,139 @@
+/** @file
+ * Desktop-bound selectable control object - implementation
+ */
+/* Authors:
+ * Krzysztof Kosiński <tweenk.pl@gmail.com>
+ *
+ * Copyright (C) 2009 Authors
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ */
+
+#include "ui/tool/control-point-selection.h"
+#include "ui/tool/event-utils.h"
+#include "ui/tool/selectable-control-point.h"
+
+namespace Inkscape {
+namespace UI {
+
+static SelectableControlPoint::ColorSet default_scp_color_set = {
+ {
+ {0xffffff00, 0x01000000}, // normal fill, stroke
+ {0xff0000ff, 0x01000000}, // mouseover fill, stroke
+ {0x0000ffff, 0x01000000} // clicked fill, stroke
+ },
+ {0x0000ffff, 0x000000ff}, // normal fill, stroke when selected
+ {0xff000000, 0x000000ff}, // mouseover fill, stroke when selected
+ {0xff000000, 0x000000ff} // clicked fill, stroke when selected
+};
+
+SelectableControlPoint::SelectableControlPoint(SPDesktop *d, Geom::Point const &initial_pos,
+ Gtk::AnchorType anchor, SPCtrlShapeType shape, unsigned int size,
+ ControlPointSelection &sel, ColorSet *cset, SPCanvasGroup *group)
+ : ControlPoint (d, initial_pos, anchor, shape, size,
+ cset ? reinterpret_cast<ControlPoint::ColorSet*>(cset)
+ : reinterpret_cast<ControlPoint::ColorSet*>(&default_scp_color_set), group)
+ , _selection (sel)
+{
+ _selection.allPoints().insert(this);
+}
+SelectableControlPoint::SelectableControlPoint(SPDesktop *d, Geom::Point const &initial_pos,
+ Gtk::AnchorType anchor, Glib::RefPtr<Gdk::Pixbuf> pixbuf,
+ ControlPointSelection &sel, ColorSet *cset, SPCanvasGroup *group)
+ : ControlPoint (d, initial_pos, anchor, pixbuf,
+ cset ? reinterpret_cast<ControlPoint::ColorSet*>(cset)
+ : reinterpret_cast<ControlPoint::ColorSet*>(&default_scp_color_set), group)
+ , _selection (sel)
+{
+ _selection.allPoints().insert(this);
+}
+
+SelectableControlPoint::~SelectableControlPoint()
+{
+ _selection.erase(this);
+ _selection.allPoints().erase(this);
+}
+
+bool SelectableControlPoint::grabbed(GdkEventMotion *)
+{
+ // if a point is dragged while not selected, it should select itself
+ if (!selected()) {
+ _takeSelection();
+ }
+ _selection._pointGrabbed(this);
+ return false;
+}
+
+void SelectableControlPoint::dragged(Geom::Point &new_pos, GdkEventMotion *event)
+{
+ _selection._pointDragged(new_pos, event);
+}
+
+void SelectableControlPoint::ungrabbed(GdkEventButton *)
+{
+ _selection._pointUngrabbed();
+}
+
+bool SelectableControlPoint::clicked(GdkEventButton *event)
+{
+ if (_selection._pointClicked(this, event))
+ return true;
+
+ if (event->button != 1) return false;
+ if (held_shift(*event)) {
+ if (selected()) {
+ _selection.erase(this);
+ } else {
+ _selection.insert(this);
+ }
+ } else {
+ _takeSelection();
+ }
+ return true;
+}
+
+void SelectableControlPoint::_takeSelection()
+{
+ _selection.clear();
+ _selection.insert(this);
+}
+
+bool SelectableControlPoint::selected() const
+{
+ SelectableControlPoint *p = const_cast<SelectableControlPoint*>(this);
+ return _selection.find(p) != _selection.end();
+}
+
+void SelectableControlPoint::_setState(State state)
+{
+ if (!selected()) {
+ ControlPoint::_setState(state);
+ return;
+ }
+
+ ColorSet *cset = reinterpret_cast<ColorSet*>(_cset);
+ ColorEntry current = {0, 0};
+ switch (state) {
+ case STATE_NORMAL:
+ current = cset->selected_normal; break;
+ case STATE_MOUSEOVER:
+ current = cset->selected_mouseover; break;
+ case STATE_CLICKED:
+ current = cset->selected_clicked; break;
+ }
+ _setColors(current);
+ _state = state;
+}
+
+} // 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:encoding=utf-8:textwidth=99 :
diff --git a/src/ui/tool/selectable-control-point.h b/src/ui/tool/selectable-control-point.h
new file mode 100644
index 000000000..2fde16ea9
--- /dev/null
+++ b/src/ui/tool/selectable-control-point.h
@@ -0,0 +1,72 @@
+/** @file
+ * Desktop-bound selectable control object
+ */
+/* Authors:
+ * Krzysztof Kosiński <tweenk.pl@gmail.com>
+ *
+ * Copyright (C) 2009 Authors
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ */
+
+#ifndef SEEN_UI_TOOL_SELECTABLE_CONTROL_POINT_H
+#define SEEN_UI_TOOL_SELECTABLE_CONTROL_POINT_H
+
+#include <boost/enable_shared_from_this.hpp>
+#include "ui/tool/control-point.h"
+
+namespace Inkscape {
+namespace UI {
+
+class ControlPointSelection;
+
+class SelectableControlPoint : public ControlPoint {
+public:
+ struct ColorSet {
+ ControlPoint::ColorSet cpset;
+ ColorEntry selected_normal;
+ ColorEntry selected_mouseover;
+ ColorEntry selected_clicked;
+ };
+
+ ~SelectableControlPoint();
+ bool selected() const;
+ void updateState() const { const_cast<SelectableControlPoint*>(this)->_setState(_state); }
+ virtual Geom::Rect bounds() {
+ return Geom::Rect(position(), position());
+ }
+protected:
+ SelectableControlPoint(SPDesktop *d, Geom::Point const &initial_pos,
+ Gtk::AnchorType anchor, SPCtrlShapeType shape,
+ unsigned int size, ControlPointSelection &sel, ColorSet *cset = 0,
+ SPCanvasGroup *group = 0);
+ SelectableControlPoint(SPDesktop *d, Geom::Point const &initial_pos,
+ Gtk::AnchorType anchor, Glib::RefPtr<Gdk::Pixbuf> pixbuf,
+ ControlPointSelection &sel, ColorSet *cset = 0, SPCanvasGroup *group = 0);
+
+ virtual void _setState(State state);
+
+ virtual void dragged(Geom::Point &, GdkEventMotion *);
+ virtual bool grabbed(GdkEventMotion *);
+ virtual void ungrabbed(GdkEventButton *);
+ virtual bool clicked(GdkEventButton *);
+
+ ControlPointSelection &_selection;
+private:
+ void _takeSelection();
+};
+
+} // namespace UI
+} // namespace Inkscape
+
+#endif
+
+/*
+ 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:encoding=utf-8:textwidth=99 :
diff --git a/src/ui/tool/selector.cpp b/src/ui/tool/selector.cpp
new file mode 100644
index 000000000..d766d5be3
--- /dev/null
+++ b/src/ui/tool/selector.cpp
@@ -0,0 +1,133 @@
+/** @file
+ * Selector component (click and rubberband)
+ */
+/* Authors:
+ * Krzysztof Kosiński <tweenk.pl@gmail.com>
+ *
+ * Copyright (C) 2009 Authors
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ */
+
+#include "desktop.h"
+#include "desktop-handles.h"
+#include "display/sodipodi-ctrlrect.h"
+#include "event-context.h"
+#include "preferences.h"
+#include "ui/tool/control-point.h"
+#include "ui/tool/event-utils.h"
+#include "ui/tool/selector.h"
+
+namespace Inkscape {
+namespace UI {
+
+/** A hidden control point used for rubberbanding and selection.
+ * It uses a clever hack: the canvas item is hidden and only receives events when they
+ * are passed to it using Selector's event() function. When left mouse button
+ * is pressed, it grabs events and handles drags and clicks in the usual way. */
+class SelectorPoint : public ControlPoint {
+public:
+ SelectorPoint(SPDesktop *d, SPCanvasGroup *group, Selector *s)
+ : ControlPoint(d, Geom::Point(0,0), Gtk::ANCHOR_CENTER, SP_CTRL_SHAPE_SQUARE,
+ 1, &invisible_cset, group)
+ , _selector(s)
+ , _cancel(false)
+ {
+ setVisible(false);
+ _rubber = static_cast<CtrlRect*>(sp_canvas_item_new(sp_desktop_controls(_desktop),
+ SP_TYPE_CTRLRECT, NULL));
+ sp_canvas_item_hide(_rubber);
+ }
+ ~SelectorPoint() {
+ gtk_object_destroy(_rubber);
+ }
+ SPDesktop *desktop() { return _desktop; }
+ bool event(GdkEvent *e) {
+ return _eventHandler(e);
+ }
+
+protected:
+ virtual bool _eventHandler(GdkEvent *event) {
+ if (event->type == GDK_KEY_PRESS && shortcut_key(event->key) == GDK_Escape &&
+ sp_canvas_item_is_visible(_rubber))
+ {
+ _cancel = true;
+ sp_canvas_item_hide(_rubber);
+ return true;
+ }
+ return ControlPoint::_eventHandler(event);
+ }
+
+private:
+ virtual bool grabbed(GdkEventMotion *) {
+ _cancel = false;
+ _start = position();
+ sp_canvas_item_show(_rubber);
+ return false;
+ }
+ virtual void dragged(Geom::Point &new_pos, GdkEventMotion *) {
+ if (_cancel) return;
+ Geom::Rect sel(_start, new_pos);
+ _rubber->setRectangle(sel);
+ }
+ virtual void ungrabbed(GdkEventButton *event) {
+ if (_cancel) return;
+ sp_canvas_item_hide(_rubber);
+ Geom::Rect sel(_start, position());
+ _selector->signal_area.emit(sel, event);
+ }
+ virtual bool clicked(GdkEventButton *event) {
+ if (event->button != 1) return false;
+ _selector->signal_point.emit(position(), event);
+ return true;
+ }
+ CtrlRect *_rubber;
+ Selector *_selector;
+ Geom::Point _start;
+ bool _cancel;
+};
+
+
+Selector::Selector(SPDesktop *d)
+ : Manipulator(d)
+ , _dragger(new SelectorPoint(d, sp_desktop_controls(d), this))
+{
+ _dragger->setVisible(false);
+}
+
+Selector::~Selector()
+{
+ delete _dragger;
+}
+
+bool Selector::event(GdkEvent *event)
+{
+ // The hidden control point will capture all events after it obtains the grab,
+ // but it relies on this function to initiate it. If we pass only first button
+ // press events here, it won't interfere with any other event handling.
+ switch (event->type) {
+ case GDK_BUTTON_PRESS:
+ // Do not pass button presses other than left button to the control point.
+ // This way middle click and right click can be handled in SPEventContext.
+ if (event->button.button == 1) {
+ _dragger->setPosition(_desktop->w2d(event_point(event->motion)));
+ return _dragger->event(event);
+ }
+ break;
+ default: break;
+ }
+ return false;
+}
+
+} // 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:encoding=utf-8:textwidth=99 :
diff --git a/src/ui/tool/selector.h b/src/ui/tool/selector.h
new file mode 100644
index 000000000..f7c00ea71
--- /dev/null
+++ b/src/ui/tool/selector.h
@@ -0,0 +1,59 @@
+/** @file
+ * Selector component (click and rubberband)
+ */
+/* Authors:
+ * Krzysztof Kosiński <tweenk.pl@gmail.com>
+ *
+ * Copyright (C) 2009 Authors
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ */
+
+#ifndef SEEN_UI_TOOL_SELECTOR_H
+#define SEEN_UI_TOOL_SELECTOR_H
+
+#include <memory>
+#include <gdk/gdk.h>
+#include <2geom/rect.h>
+#include "display/display-forward.h"
+#include "ui/tool/manipulator.h"
+
+class SPDesktop;
+class CtrlRect;
+
+namespace Inkscape {
+namespace UI {
+
+class SelectorPoint;
+
+class Selector : public Manipulator {
+public:
+ Selector(SPDesktop *d);
+ virtual ~Selector();
+ virtual bool event(GdkEvent *);
+
+ sigc::signal<void, Geom::Rect const &, GdkEventButton*> signal_area;
+ sigc::signal<void, Geom::Point const &, GdkEventButton*> signal_point;
+private:
+ SelectorPoint *_dragger;
+ Geom::Point _start;
+ CtrlRect *_rubber;
+ gulong _connection;
+ bool _cancel;
+ friend class SelectorPoint;
+};
+
+} // namespace UI
+} // namespace Inkscape
+
+#endif
+
+/*
+ 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:encoding=utf-8:textwidth=99 :
diff --git a/src/ui/tool/shape-record.h b/src/ui/tool/shape-record.h
new file mode 100644
index 000000000..edfad1401
--- /dev/null
+++ b/src/ui/tool/shape-record.h
@@ -0,0 +1,61 @@
+/** @file
+ * Structures that store data needed for shape editing which are not contained
+ * directly in the XML node
+ */
+/* Authors:
+ * Krzysztof Kosiński <tweenk.pl@gmail.com>
+ *
+ * Copyright (C) 2009 Authors
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ */
+
+#ifndef SEEN_UI_TOOL_SHAPE_RECORD_H
+#define SEEN_UI_TOOL_SHAPE_RECORD_H
+
+#include <glibmm/ustring.h>
+#include <boost/operators.hpp>
+#include <2geom/matrix.h>
+
+class SPItem;
+namespace Inkscape {
+namespace UI {
+
+/** Role of the shape in the drawing - affects outline display and color */
+enum ShapeRole {
+ SHAPE_ROLE_NORMAL,
+ SHAPE_ROLE_CLIPPING_PATH,
+ SHAPE_ROLE_MASK,
+ SHAPE_ROLE_LPE_PARAM // implies edit_original set to true in ShapeRecord
+};
+
+struct ShapeRecord :
+ public boost::totally_ordered<ShapeRecord>
+{
+ SPItem *item; // SP node for the edited shape
+ Geom::Matrix edit_transform; // how to transform controls - used for clipping paths and masks
+ ShapeRole role;
+ Glib::ustring lpe_key; // name of LPE shape param being edited
+
+ inline bool operator==(ShapeRecord const &o) const {
+ return item == o.item && lpe_key == o.lpe_key;
+ }
+ inline bool operator<(ShapeRecord const &o) const {
+ return item == o.item ? (lpe_key < o.lpe_key) : (item < o.item);
+ }
+};
+
+} // namespace UI
+} // namespace Inkscape
+
+#endif
+
+/*
+ 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:encoding=utf-8:textwidth=99 :
diff --git a/src/ui/tool/transform-handle-set.cpp b/src/ui/tool/transform-handle-set.cpp
new file mode 100644
index 000000000..1af848b96
--- /dev/null
+++ b/src/ui/tool/transform-handle-set.cpp
@@ -0,0 +1,657 @@
+/** @file
+ * Affine transform handles component
+ */
+/* Authors:
+ * Krzysztof Kosiński <tweenk.pl@gmail.com>
+ *
+ * Copyright (C) 2009 Authors
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ */
+
+#include <math.h>
+#include <algorithm>
+#include <glib.h>
+#include <glib/gi18n.h>
+#include <gdk/gdk.h>
+#include <2geom/transforms.h>
+#include "desktop.h"
+#include "desktop-handles.h"
+#include "display/sodipodi-ctrlrect.h"
+#include "preferences.h"
+#include "ui/tool/commit-events.h"
+#include "ui/tool/control-point.h"
+#include "ui/tool/event-utils.h"
+#include "ui/tool/transform-handle-set.h"
+
+// FIXME BRAIN DAMAGE WARNING: this is a global variable in select-context.cpp
+// It should be moved to a header
+extern GdkPixbuf *handles[];
+GType sp_select_context_get_type();
+
+namespace Inkscape {
+namespace UI {
+
+namespace {
+Gtk::AnchorType corner_to_anchor(unsigned c) {
+ switch (c % 4) {
+ case 0: return Gtk::ANCHOR_NE;
+ case 1: return Gtk::ANCHOR_NW;
+ case 2: return Gtk::ANCHOR_SW;
+ default: return Gtk::ANCHOR_SE;
+ }
+}
+Gtk::AnchorType side_to_anchor(unsigned s) {
+ switch (s % 4) {
+ case 0: return Gtk::ANCHOR_N;
+ case 1: return Gtk::ANCHOR_W;
+ case 2: return Gtk::ANCHOR_S;
+ default: return Gtk::ANCHOR_E;
+ }
+}
+
+// TODO move those two functions into a common place
+double snap_angle(double a) {
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ int snaps = prefs->getIntLimited("/options/rotationsnapsperpi/value", 12, 1, 1000);
+ double unit_angle = M_PI / snaps;
+ return CLAMP(unit_angle * round(a / unit_angle), -M_PI, M_PI);
+}
+double snap_increment_degrees() {
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ int snaps = prefs->getIntLimited("/options/rotationsnapsperpi/value", 12, 1, 1000);
+ return 180.0 / snaps;
+}
+
+ControlPoint::ColorSet thandle_cset = {
+ {0x000000ff, 0x000000ff},
+ {0x00ff6600, 0x000000ff},
+ {0x00ff6600, 0x000000ff}
+};
+
+ControlPoint::ColorSet center_cset = {
+ {0x00000000, 0x000000ff},
+ {0x00000000, 0xff0000b0},
+ {0x00000000, 0xff0000b0}
+};
+} // anonymous namespace
+
+/** Base class for node transform handles to simplify implementation */
+class TransformHandle : public ControlPoint {
+public:
+ TransformHandle(TransformHandleSet &th, Gtk::AnchorType anchor, Glib::RefPtr<Gdk::Pixbuf> pb)
+ : ControlPoint(th._desktop, Geom::Point(), anchor, pb, &thandle_cset,
+ th._transform_handle_group)
+ , _th(th)
+ {
+ setVisible(false);
+ }
+protected:
+ virtual void startTransform() {}
+ virtual void endTransform() {}
+ virtual Geom::Matrix computeTransform(Geom::Point const &pos, GdkEventMotion *event) = 0;
+ virtual CommitEvent getCommitEvent() = 0;
+
+ Geom::Matrix _last_transform;
+ Geom::Point _origin;
+ TransformHandleSet &_th;
+private:
+ virtual bool grabbed(GdkEventMotion *) {
+ _origin = position();
+ _last_transform.setIdentity();
+ startTransform();
+
+ _th._setActiveHandle(this);
+ _cset = &invisible_cset;
+ _setState(_state);
+ return false;
+ }
+ virtual void dragged(Geom::Point &new_pos, GdkEventMotion *event)
+ {
+ Geom::Matrix t = computeTransform(new_pos, event);
+ // protect against degeneracies
+ if (t.isSingular()) return;
+ Geom::Matrix incr = _last_transform.inverse() * t;
+ if (incr.isSingular()) return;
+ _th.signal_transform.emit(incr);
+ _last_transform = t;
+ }
+ virtual void ungrabbed(GdkEventButton *) {
+ _th._clearActiveHandle();
+ _cset = &thandle_cset;
+ _setState(_state);
+ endTransform();
+ _th.signal_commit.emit(getCommitEvent());
+ }
+};
+
+class ScaleHandle : public TransformHandle {
+public:
+ ScaleHandle(TransformHandleSet &th, Gtk::AnchorType anchor, Glib::RefPtr<Gdk::Pixbuf> pb)
+ : TransformHandle(th, anchor, pb)
+ {}
+protected:
+ virtual Glib::ustring _getTip(unsigned state) {
+ if (state_held_control(state)) {
+ if (state_held_shift(state)) {
+ return C_("Transform handle tip",
+ "<b>Shift+Ctrl:</b> scale uniformly about the rotation center");
+ }
+ return C_("Transform handle tip", "<b>Ctrl:</b> scale uniformly");
+ }
+ if (state_held_shift(state)) {
+ if (state_held_alt(state)) {
+ return C_("Transform handle tip",
+ "<b>Shift+Alt:</b> scale using an integer ratio about the rotation center");
+ }
+ return C_("Transform handle tip", "<b>Shift:</b> scale from the rotation center");
+ }
+ if (state_held_alt(state)) {
+ return C_("Transform handle tip", "<b>Alt:</b> scale using an integer ratio");
+ }
+ return C_("Transform handle tip", "<b>Scale handle:</b> drag to scale the selection");
+ }
+
+ virtual Glib::ustring _getDragTip(GdkEventMotion */*event*/) {
+ return format_tip(C_("Transform handle tip",
+ "Scale by %.2f%% x %.2f%%"), _last_scale_x * 100, _last_scale_y * 100);
+ }
+
+ virtual bool _hasDragTips() { return true; }
+
+ static double _last_scale_x, _last_scale_y;
+};
+double ScaleHandle::_last_scale_x = 1.0;
+double ScaleHandle::_last_scale_y = 1.0;
+
+/// Corner scaling handle for node transforms
+class ScaleCornerHandle : public ScaleHandle {
+public:
+ ScaleCornerHandle(TransformHandleSet &th, unsigned corner)
+ : ScaleHandle(th, corner_to_anchor(corner), _corner_to_pixbuf(corner))
+ , _corner(corner)
+ {}
+protected:
+ virtual void startTransform() {
+ _sc_center = _th.rotationCenter();
+ _sc_opposite = _th.bounds().corner(_corner + 2);
+ _last_scale_x = _last_scale_y = 1.0;
+ }
+ virtual Geom::Matrix computeTransform(Geom::Point const &new_pos, GdkEventMotion *event) {
+ Geom::Point scc = held_shift(*event) ? _sc_center : _sc_opposite;
+ Geom::Point vold = _origin - scc, vnew = new_pos - scc;
+ // avoid exploding the selection
+ if (Geom::are_near(vold[Geom::X], 0) || Geom::are_near(vold[Geom::Y], 0))
+ return Geom::identity();
+
+ double scale[2] = { vnew[Geom::X] / vold[Geom::X], vnew[Geom::Y] / vold[Geom::Y] };
+ if (held_alt(*event)) {
+ for (unsigned i = 0; i < 2; ++i) {
+ if (scale[i] >= 1.0) scale[i] = round(scale[i]);
+ else scale[i] = 1.0 / round(1.0 / scale[i]);
+ }
+ } else if (held_control(*event)) {
+ scale[0] = scale[1] = std::min(scale[0], scale[1]);
+ }
+ _last_scale_x = scale[0];
+ _last_scale_y = scale[1];
+ Geom::Matrix t = Geom::Translate(-scc)
+ * Geom::Scale(scale[0], scale[1])
+ * Geom::Translate(scc);
+ return t;
+ }
+ virtual CommitEvent getCommitEvent() {
+ return _last_transform.isUniformScale()
+ ? COMMIT_MOUSE_SCALE_UNIFORM
+ : COMMIT_MOUSE_SCALE;
+ }
+private:
+ static Glib::RefPtr<Gdk::Pixbuf> _corner_to_pixbuf(unsigned c) {
+ sp_select_context_get_type();
+ switch (c % 2) {
+ case 0: return Glib::wrap(handles[1], true);
+ default: return Glib::wrap(handles[0], true);
+ }
+ }
+ Geom::Point _sc_center;
+ Geom::Point _sc_opposite;
+ unsigned _corner;
+};
+
+/// Side scaling handle for node transforms
+class ScaleSideHandle : public ScaleHandle {
+public:
+ ScaleSideHandle(TransformHandleSet &th, unsigned side)
+ : ScaleHandle(th, side_to_anchor(side), _side_to_pixbuf(side))
+ , _side(side)
+ {}
+protected:
+ virtual void startTransform() {
+ _sc_center = _th.rotationCenter();
+ Geom::Rect b = _th.bounds();
+ _sc_opposite = Geom::middle_point(b.corner(_side + 2), b.corner(_side + 3));
+ _last_scale_x = _last_scale_y = 1.0;
+ }
+ virtual Geom::Matrix computeTransform(Geom::Point const &new_pos, GdkEventMotion *event) {
+ Geom::Point scc = held_shift(*event) ? _sc_center : _sc_opposite;
+ Geom::Point vs;
+ Geom::Dim2 d1 = static_cast<Geom::Dim2>((_side + 1) % 2);
+ Geom::Dim2 d2 = static_cast<Geom::Dim2>(_side % 2);
+
+ // avoid exploding the selection
+ if (Geom::are_near(scc[d1], _origin[d1]))
+ return Geom::identity();
+
+ vs[d1] = (new_pos - scc)[d1] / (_origin - scc)[d1];
+ if (held_alt(*event)) {
+ if (vs[d1] >= 1.0) vs[d1] = round(vs[d1]);
+ else vs[d1] = 1.0 / round(1.0 / vs[d1]);
+ }
+ vs[d2] = held_control(*event) ? vs[d1] : 1.0;
+
+ _last_scale_x = vs[Geom::X];
+ _last_scale_y = vs[Geom::Y];
+ Geom::Matrix t = Geom::Translate(-scc)
+ * Geom::Scale(vs)
+ * Geom::Translate(scc);
+ return t;
+ }
+ virtual CommitEvent getCommitEvent() {
+ return _last_transform.isUniformScale()
+ ? COMMIT_MOUSE_SCALE_UNIFORM
+ : COMMIT_MOUSE_SCALE;
+ }
+private:
+ static Glib::RefPtr<Gdk::Pixbuf> _side_to_pixbuf(unsigned c) {
+ sp_select_context_get_type();
+ switch (c % 2) {
+ case 0: return Glib::wrap(handles[3], true);
+ default: return Glib::wrap(handles[2], true);
+ }
+ }
+ Geom::Point _sc_center;
+ Geom::Point _sc_opposite;
+ unsigned _side;
+};
+
+/// Rotation handle for node transforms
+class RotateHandle : public TransformHandle {
+public:
+ RotateHandle(TransformHandleSet &th, unsigned corner)
+ : TransformHandle(th, corner_to_anchor(corner), _corner_to_pixbuf(corner))
+ , _corner(corner)
+ {}
+protected:
+
+ virtual void startTransform() {
+ _rot_center = _th.rotationCenter();
+ _rot_opposite = _th.bounds().corner(_corner + 2);
+ _last_angle = 0;
+ }
+
+ virtual Geom::Matrix computeTransform(Geom::Point const &new_pos, GdkEventMotion *event)
+ {
+ Geom::Point rotc = held_shift(*event) ? _rot_opposite : _rot_center;
+ double angle = Geom::angle_between(_origin - rotc, new_pos - rotc);
+ if (held_control(*event)) {
+ angle = snap_angle(angle);
+ }
+ _last_angle = angle;
+ Geom::Matrix t = Geom::Translate(-rotc)
+ * Geom::Rotate(angle)
+ * Geom::Translate(rotc);
+ return t;
+ }
+
+ virtual CommitEvent getCommitEvent() { return COMMIT_MOUSE_ROTATE; }
+
+ virtual Glib::ustring _getTip(unsigned state) {
+ if (state_held_shift(state)) {
+ if (state_held_control(state)) {
+ return format_tip(C_("Transform handle tip",
+ "<b>Shift+Ctrl:</b> rotate around the opposite corner and snap "
+ "angle to %f° increments"), snap_increment_degrees());
+ }
+ return C_("Transform handle tip", "<b>Shift:</b> rotate around the opposite corner");
+ }
+ if (state_held_control(state)) {
+ return format_tip(C_("Transform handle tip",
+ "<b>Ctrl:</b> snap angle to %f° increments"), snap_increment_degrees());
+ }
+ return C_("Transform handle tip", "<b>Rotation handle:</b> drag to rotate "
+ "the selection around the rotation center");
+ }
+
+ virtual Glib::ustring _getDragTip(GdkEventMotion */*event*/) {
+ return format_tip(C_("Transform handle tip", "Rotate by %.2f°"),
+ _last_angle * 360.0);
+ }
+
+ virtual bool _hasDragTips() { return true; }
+
+private:
+ static Glib::RefPtr<Gdk::Pixbuf> _corner_to_pixbuf(unsigned c) {
+ sp_select_context_get_type();
+ switch (c % 4) {
+ case 0: return Glib::wrap(handles[10], true);
+ case 1: return Glib::wrap(handles[8], true);
+ case 2: return Glib::wrap(handles[6], true);
+ default: return Glib::wrap(handles[4], true);
+ }
+ }
+ Geom::Point _rot_center;
+ Geom::Point _rot_opposite;
+ unsigned _corner;
+ static double _last_angle;
+};
+double RotateHandle::_last_angle = 0;
+
+class SkewHandle : public TransformHandle {
+public:
+ SkewHandle(TransformHandleSet &th, unsigned side)
+ : TransformHandle(th, side_to_anchor(side), _side_to_pixbuf(side))
+ , _side(side)
+ {}
+
+protected:
+
+ virtual void startTransform() {
+ _skew_center = _th.rotationCenter();
+ Geom::Rect b = _th.bounds();
+ _skew_opposite = Geom::middle_point(b.corner(_side + 2), b.corner(_side + 3));
+ _last_angle = 0;
+ _last_horizontal = _side % 2;
+ }
+
+ virtual Geom::Matrix computeTransform(Geom::Point const &new_pos, GdkEventMotion *event)
+ {
+ Geom::Point scc = held_shift(*event) ? _skew_center : _skew_opposite;
+ // d1 and d2 are reversed with respect to ScaleSideHandle
+ Geom::Dim2 d1 = static_cast<Geom::Dim2>(_side % 2);
+ Geom::Dim2 d2 = static_cast<Geom::Dim2>((_side + 1) % 2);
+ Geom::Point proj, scale(1.0, 1.0);
+
+ // Skew handles allow scaling up to integer multiples of the original size
+ // in the second direction; prevent explosions
+ // TODO should the scaling part be only active with Alt?
+ if (!Geom::are_near(_origin[d2], scc[d2])) {
+ scale[d2] = (new_pos - scc)[d2] / (_origin - scc)[d2];
+ }
+
+ if (scale[d2] < 1.0) {
+ scale[d2] = copysign(1.0, scale[d2]);
+ } else {
+ scale[d2] = floor(scale[d2]);
+ }
+
+ // Calculate skew angle. The angle is calculated with regards to the point obtained
+ // by projecting the handle position on the relevant side of the bounding box.
+ // This avoids degeneracies when moving the skew angle over the rotation center
+ proj[d1] = new_pos[d1];
+ proj[d2] = scc[d2] + (_origin[d2] - scc[d2]) * scale[d2];
+ double angle = 0;
+ if (!Geom::are_near(proj[d2], scc[d2]))
+ angle = Geom::angle_between(_origin - scc, proj - scc);
+ if (held_control(*event)) angle = snap_angle(angle);
+
+ // skew matrix has the from [[1, k],[0, 1]] for horizontal skew
+ // and [[1,0],[k,1]] for vertical skew.
+ Geom::Matrix skew = Geom::identity();
+ // correct the sign of the tangent
+ skew[d2 + 1] = (d1 == Geom::X ? -1.0 : 1.0) * tan(angle);
+
+ _last_angle = angle;
+ Geom::Matrix t = Geom::Translate(-scc)
+ * Geom::Scale(scale) * skew
+ * Geom::Translate(scc);
+ return t;
+ }
+
+ virtual CommitEvent getCommitEvent() {
+ return _side % 2
+ ? COMMIT_MOUSE_SKEW_Y
+ : COMMIT_MOUSE_SKEW_X;
+ }
+
+ virtual Glib::ustring _getTip(unsigned state) {
+ if (state_held_shift(state)) {
+ if (state_held_control(state)) {
+ return format_tip(C_("Transform handle tip",
+ "<b>Shift+Ctrl:</b> skew about the rotation center with snapping "
+ "to %f° increments"), snap_increment_degrees());
+ }
+ return C_("Transform handle tip", "<b>Shift:</b> skew about the rotation center");
+ }
+ if (state_held_control(state)) {
+ return format_tip(C_("Transform handle tip",
+ "<b>Ctrl:</b> snap skew angle to %f° increments"), snap_increment_degrees());
+ }
+ return C_("Transform handle tip",
+ "<b>Skew handle:</b> drag to skew (shear) selection about "
+ "the opposite handle");
+ }
+
+ virtual Glib::ustring _getDragTip(GdkEventMotion */*event*/) {
+ if (_last_horizontal) {
+ return format_tip(C_("Transform handle tip", "Skew horizontally by %.2f°"),
+ _last_angle * 360.0);
+ } else {
+ return format_tip(C_("Transform handle tip", "Skew vertically by %.2f°"),
+ _last_angle * 360.0);
+ }
+ }
+
+ virtual bool _hasDragTips() { return true; }
+
+private:
+
+ static Glib::RefPtr<Gdk::Pixbuf> _side_to_pixbuf(unsigned s) {
+ sp_select_context_get_type();
+ switch (s % 4) {
+ case 0: return Glib::wrap(handles[9], true);
+ case 1: return Glib::wrap(handles[7], true);
+ case 2: return Glib::wrap(handles[5], true);
+ default: return Glib::wrap(handles[11], true);
+ }
+ }
+ Geom::Point _skew_center;
+ Geom::Point _skew_opposite;
+ unsigned _side;
+ static bool _last_horizontal;
+ static double _last_angle;
+};
+bool SkewHandle::_last_horizontal = false;
+double SkewHandle::_last_angle = 0;
+
+class RotationCenter : public ControlPoint {
+public:
+ RotationCenter(TransformHandleSet &th)
+ : ControlPoint(th._desktop, Geom::Point(), Gtk::ANCHOR_CENTER, _get_pixbuf(),
+ &center_cset, th._transform_handle_group)
+ , _th(th)
+ {
+ setVisible(false);
+ }
+
+protected:
+
+ virtual Glib::ustring _getTip(unsigned /*state*/) {
+ return C_("Transform handle tip",
+ "<b>Rotation center:</b> drag to change the origin of transforms");
+ }
+
+private:
+
+ static Glib::RefPtr<Gdk::Pixbuf> _get_pixbuf() {
+ sp_select_context_get_type();
+ return Glib::wrap(handles[12], true);
+ }
+
+ TransformHandleSet &_th;
+};
+
+TransformHandleSet::TransformHandleSet(SPDesktop *d, SPCanvasGroup *th_group)
+ : Manipulator(d)
+ , _active(0)
+ , _transform_handle_group(th_group)
+ , _mode(MODE_SCALE)
+ , _in_transform(false)
+ , _visible(true)
+{
+ _trans_outline = static_cast<CtrlRect*>(sp_canvas_item_new(sp_desktop_controls(_desktop),
+ SP_TYPE_CTRLRECT, NULL));
+ sp_canvas_item_hide(_trans_outline);
+ _trans_outline->setDashed(true);
+
+ for (unsigned i = 0; i < 4; ++i) {
+ _scale_corners[i] = new ScaleCornerHandle(*this, i);
+ _scale_sides[i] = new ScaleSideHandle(*this, i);
+ _rot_corners[i] = new RotateHandle(*this, i);
+ _skew_sides[i] = new SkewHandle(*this, i);
+ }
+ _center = new RotationCenter(*this);
+ // when transforming, update rotation center position
+ signal_transform.connect(sigc::mem_fun(*_center, &RotationCenter::transform));
+}
+
+TransformHandleSet::~TransformHandleSet()
+{
+ for (unsigned i = 0; i < 17; ++i) {
+ delete _handles[i];
+ }
+}
+
+/** Sets the mode of transform handles (scale or rotate). */
+void TransformHandleSet::setMode(Mode m)
+{
+ _mode = m;
+ _updateVisibility(_visible);
+}
+
+Geom::Rect TransformHandleSet::bounds()
+{
+ return Geom::Rect(*_scale_corners[0], *_scale_corners[2]);
+}
+
+ControlPoint &TransformHandleSet::rotationCenter()
+{
+ return *_center;
+}
+
+void TransformHandleSet::setVisible(bool v)
+{
+ if (_visible != v) {
+ _visible = v;
+ _updateVisibility(_visible);
+ }
+}
+
+void TransformHandleSet::setBounds(Geom::Rect const &r, bool preserve_center)
+{
+ if (_in_transform) {
+ _trans_outline->setRectangle(r);
+ } else {
+ for (unsigned i = 0; i < 4; ++i) {
+ _scale_corners[i]->move(r.corner(i));
+ _scale_sides[i]->move(Geom::middle_point(r.corner(i), r.corner(i+1)));
+ _rot_corners[i]->move(r.corner(i));
+ _skew_sides[i]->move(Geom::middle_point(r.corner(i), r.corner(i+1)));
+ }
+ if (!preserve_center) _center->move(r.midpoint());
+ if (_visible) _updateVisibility(true);
+ }
+}
+
+bool TransformHandleSet::event(GdkEvent*)
+{
+ return false;
+}
+
+void TransformHandleSet::_emitTransform(Geom::Matrix const &t)
+{
+ signal_transform.emit(t);
+ _center->transform(t);
+}
+
+void TransformHandleSet::_setActiveHandle(ControlPoint *th)
+{
+ _active = th;
+ if (_in_transform)
+ throw std::logic_error("Transform initiated when another transform in progress");
+ _in_transform = true;
+ // hide all handles except the active one
+ _updateVisibility(false);
+ sp_canvas_item_show(_trans_outline);
+}
+
+void TransformHandleSet::_clearActiveHandle()
+{
+ // This can only be called from handles, so they had to be visible before _setActiveHandle
+ sp_canvas_item_hide(_trans_outline);
+ _active = 0;
+ _in_transform = false;
+ _updateVisibility(_visible);
+}
+
+/** Update the visibility of transformation handles according to settings and the dimensions
+ * of the bounding box. It hides the handles that would have no effect or lead to
+ * discontinuities. Additionally, side handles for which there is no space are not shown. */
+void TransformHandleSet::_updateVisibility(bool v)
+{
+ if (v) {
+ Geom::Rect b = bounds();
+ Geom::Point handle_size(
+ gdk_pixbuf_get_width(handles[0]) / _desktop->current_zoom(),
+ gdk_pixbuf_get_height(handles[0]) / _desktop->current_zoom());
+ Geom::Point bp = b.dimensions();
+
+ // do not scale when the bounding rectangle has zero width or height
+ bool show_scale = (_mode == MODE_SCALE) && !Geom::are_near(b.minExtent(), 0);
+ // do not rotate if the bounding rectangle is degenerate
+ bool show_rotate = (_mode == MODE_ROTATE_SKEW) && !Geom::are_near(b.maxExtent(), 0);
+ bool show_scale_side[2], show_skew[2];
+
+ // show sides if:
+ // a) there is enough space between corner handles, or
+ // b) corner handles are not shown, but side handles make sense
+ // this affects horizontal and vertical scale handles; skew handles never
+ // make sense if rotate handles are not shown
+ for (unsigned i = 0; i < 2; ++i) {
+ Geom::Dim2 d = static_cast<Geom::Dim2>(i);
+ Geom::Dim2 otherd = static_cast<Geom::Dim2>((i+1)%2);
+ show_scale_side[i] = (_mode == MODE_SCALE);
+ show_scale_side[i] &= (show_scale ? bp[d] >= handle_size[d]
+ : !Geom::are_near(bp[otherd], 0));
+ show_skew[i] = (show_rotate && bp[d] >= handle_size[d]
+ && !Geom::are_near(bp[otherd], 0));
+ }
+ for (unsigned i = 0; i < 4; ++i) {
+ _scale_corners[i]->setVisible(show_scale);
+ _rot_corners[i]->setVisible(show_rotate);
+ _scale_sides[i]->setVisible(show_scale_side[i%2]);
+ _skew_sides[i]->setVisible(show_skew[i%2]);
+ }
+ // show rotation center if there is enough space (?)
+ _center->setVisible(show_rotate /*&& bp[Geom::X] > handle_size[Geom::X]
+ && bp[Geom::Y] > handle_size[Geom::Y]*/);
+ } else {
+ for (unsigned i = 0; i < 17; ++i) {
+ if (_handles[i] != _active)
+ _handles[i]->setVisible(false);
+ }
+ }
+
+}
+
+} // 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:encoding=utf-8:textwidth=99 :
diff --git a/src/ui/tool/transform-handle-set.h b/src/ui/tool/transform-handle-set.h
new file mode 100644
index 000000000..48ad3af51
--- /dev/null
+++ b/src/ui/tool/transform-handle-set.h
@@ -0,0 +1,96 @@
+/** @file
+ * Affine transform handles component
+ */
+/* Authors:
+ * Krzysztof Kosiński <tweenk.pl@gmail.com>
+ *
+ * Copyright (C) 2009 Authors
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ */
+
+#ifndef SEEN_UI_TOOL_TRANSFORM_HANDLE_SET_H
+#define SEEN_UI_TOOL_TRANSFORM_HANDLE_SET_H
+
+#include <memory>
+#include <gdk/gdk.h>
+#include <2geom/forward.h>
+#include "display/display-forward.h"
+#include "ui/tool/commit-events.h"
+#include "ui/tool/manipulator.h"
+
+class SPDesktop;
+class CtrlRect; // this is not present in display-forward.h!
+namespace Inkscape {
+namespace UI {
+
+//class TransformHandle;
+class RotateHandle;
+class SkewHandle;
+class ScaleCornerHandle;
+class ScaleSideHandle;
+class RotationCenter;
+
+class TransformHandleSet : public Manipulator {
+public:
+ enum Mode {
+ MODE_SCALE,
+ MODE_ROTATE_SKEW
+ };
+
+ TransformHandleSet(SPDesktop *d, SPCanvasGroup *th_group);
+ virtual ~TransformHandleSet();
+ virtual bool event(GdkEvent *);
+
+ bool visible() { return _visible; }
+ Mode mode() { return _mode; }
+ Geom::Rect bounds();
+ void setVisible(bool v);
+ void setMode(Mode);
+ void setBounds(Geom::Rect const &, bool preserve_center = false);
+
+ bool transforming() { return _in_transform; }
+ ControlPoint &rotationCenter();
+
+ sigc::signal<void, Geom::Matrix const &> signal_transform;
+ sigc::signal<void, CommitEvent> signal_commit;
+private:
+ void _emitTransform(Geom::Matrix const &);
+ void _setActiveHandle(ControlPoint *h);
+ void _clearActiveHandle();
+ void _updateVisibility(bool v);
+ union {
+ ControlPoint *_handles[17];
+ struct {
+ ScaleCornerHandle *_scale_corners[4];
+ ScaleSideHandle *_scale_sides[4];
+ RotateHandle *_rot_corners[4];
+ SkewHandle *_skew_sides[4];
+ RotationCenter *_center;
+ };
+ };
+ ControlPoint *_active;
+ SPCanvasGroup *_transform_handle_group;
+ CtrlRect *_trans_outline;
+ Mode _mode;
+ bool _in_transform;
+ bool _visible;
+ bool _rot_center_visible;
+ friend class TransformHandle;
+ friend class RotationCenter;
+};
+
+} // namespace UI
+} // namespace Inkscape
+
+#endif
+
+/*
+ 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:encoding=utf-8:textwidth=99 :
diff --git a/src/ui/uxmanager.cpp b/src/ui/uxmanager.cpp
new file mode 100644
index 000000000..3c6f85b91
--- /dev/null
+++ b/src/ui/uxmanager.cpp
@@ -0,0 +1,174 @@
+/** \file
+ * Desktop widget implementation
+ */
+/* Authors:
+ * Jon A. Cruz <jon@joncruz.org>
+ *
+ * Copyright (C) 2010 Jon A. Cruz
+ *
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ */
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include <algorithm>
+
+#include "uxmanager.h"
+#include "util/ege-tags.h"
+#include "widgets/toolbox.h"
+#include "widgets/desktop-widget.h"
+#include "preferences.h"
+
+#ifdef GDK_WINDOWING_X11
+#include <gdk/gdkx.h>
+#endif // GDK_WINDOWING_X11
+
+using std::map;
+using std::vector;
+
+
+gchar const* KDE_WINDOW_MANAGER_NAME = "KWin";
+gchar const* UNKOWN_WINDOW_MANAGER_NAME = "unknown";
+
+
+static vector<SPDesktop*> desktops;
+static vector<SPDesktopWidget*> dtws;
+static map<SPDesktop*, vector<GtkWidget*> > trackedBoxes;
+
+
+
+namespace Inkscape {
+namespace UI {
+
+UXManager* instance = 0;
+
+UXManager* UXManager::getInstance()
+{
+ if (!instance) {
+ instance = new UXManager();
+ }
+ return instance;
+}
+
+
+UXManager::UXManager() :
+ floatwindowIssues(false)
+{
+ ege::TagSet tags;
+ tags.setLang("en");
+
+ tags.addTag(ege::Tag("General"));
+ tags.addTag(ege::Tag("Icons"));
+
+#if defined(GDK_WINDOWING_X11)
+ char const* wmName = gdk_x11_screen_get_window_manager_name( gdk_screen_get_default() );
+ //g_message("Window manager is [%s]", wmName);
+
+ //if (g_ascii_strcasecmp( wmName, UNKOWN_WINDOW_MANAGER_NAME ) == 0) {
+ if (g_ascii_strcasecmp( wmName, KDE_WINDOW_MANAGER_NAME ) == 0) {
+ floatwindowIssues = true;
+ }
+#elif defined(GDK_WINDOWING_WIN32)
+ floatwindowIssues = true;
+#endif // GDK_WINDOWING_WIN32
+}
+
+UXManager::~UXManager()
+{
+}
+
+
+bool UXManager::isFloatWindowProblem() const
+{
+ return floatwindowIssues;
+}
+
+void UXManager::setTask(SPDesktop* dt, gint val)
+{
+ for (vector<SPDesktopWidget*>::iterator it = dtws.begin(); it != dtws.end(); ++it) {
+ SPDesktopWidget* dtw = *it;
+
+ gboolean notDone = Inkscape::Preferences::get()->getBool("/options/workarounds/dynamicnotdone", false);
+
+ if (dtw->desktop == dt) {
+ switch (val) {
+ default:
+ case 0:
+ dtw->setToolboxPosition("ToolToolbar", GTK_POS_LEFT);
+ dtw->setToolboxPosition("CommandsToolbar", GTK_POS_TOP);
+ if (notDone) {
+ dtw->setToolboxPosition("AuxToolbar", GTK_POS_TOP);
+ }
+ dtw->setToolboxPosition("SnapToolbar", GTK_POS_TOP);
+ break;
+ case 1:
+ dtw->setToolboxPosition("ToolToolbar", GTK_POS_TOP);
+ dtw->setToolboxPosition("CommandsToolbar", GTK_POS_LEFT);
+ if (notDone) {
+ dtw->setToolboxPosition("AuxToolbar", GTK_POS_TOP);
+ }
+ dtw->setToolboxPosition("SnapToolbar", GTK_POS_RIGHT);
+ break;
+ case 2:
+ dtw->setToolboxPosition("ToolToolbar", GTK_POS_LEFT);
+ dtw->setToolboxPosition("CommandsToolbar", GTK_POS_RIGHT);
+ dtw->setToolboxPosition("SnapToolbar", GTK_POS_RIGHT);
+ if (notDone) {
+ dtw->setToolboxPosition("AuxToolbar", GTK_POS_RIGHT);
+ }
+ }
+ }
+ }
+}
+
+
+void UXManager::addTrack( SPDesktopWidget* dtw )
+{
+ if (std::find(dtws.begin(), dtws.end(), dtw) == dtws.end()) {
+ dtws.push_back(dtw);
+ }
+}
+
+void UXManager::delTrack( SPDesktopWidget* dtw )
+{
+ vector<SPDesktopWidget*>::iterator iter = std::find(dtws.begin(), dtws.end(), dtw);
+ if (iter != dtws.end()) {
+ dtws.erase(iter);
+ }
+}
+
+void UXManager::connectToDesktop( vector<GtkWidget *> const & toolboxes, SPDesktop *desktop )
+{
+//static map<SPDesktop*, vector<GtkWidget*> > trackedBoxes;
+
+ for (vector<GtkWidget*>::const_iterator it = toolboxes.begin(); it != toolboxes.end(); ++it ) {
+ GtkWidget* toolbox = *it;
+
+ ToolboxFactory::setToolboxDesktop( toolbox, desktop );
+ vector<GtkWidget*>& tracked = trackedBoxes[desktop];
+ if (find(tracked.begin(), tracked.end(), toolbox) == tracked.end()) {
+ tracked.push_back(toolbox);
+ }
+ }
+
+ if (std::find(desktops.begin(), desktops.end(), desktop) == desktops.end()) {
+ desktops.push_back(desktop);
+ }
+}
+
+
+} // 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 :
diff --git a/src/ui/uxmanager.h b/src/ui/uxmanager.h
new file mode 100644
index 000000000..aecda2b5e
--- /dev/null
+++ b/src/ui/uxmanager.h
@@ -0,0 +1,65 @@
+#ifndef SEEN_UI_UXMANAGER_H
+#define SEEN_UI_UXMANAGER_H
+/*
+ * A simple interface for previewing representations.
+ *
+ * Authors:
+ * Jon A. Cruz <jon@joncruz.org>
+ *
+ * Copyright (C) 2010 Jon A. Cruz
+ *
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ */
+
+#include <glib.h>
+#include <vector>
+
+extern "C"
+{
+ typedef struct _GObject GObject;
+ typedef struct _GtkWidget GtkWidget;
+}
+
+class SPDesktop;
+
+struct SPDesktopWidget;
+
+
+namespace Inkscape {
+namespace UI {
+
+class UXManager
+{
+public:
+ static UXManager* getInstance();
+ virtual ~UXManager();
+
+ void addTrack( SPDesktopWidget* dtw );
+ void delTrack( SPDesktopWidget* dtw );
+
+ void connectToDesktop( std::vector<GtkWidget *> const & toolboxes, SPDesktop *desktop );
+
+ void setTask(SPDesktop* dt, gint val);
+
+ bool isFloatWindowProblem() const;
+
+private:
+ UXManager();
+
+ bool floatwindowIssues;
+};
+
+} // namespace UI
+} // namespace Inkscape
+
+#endif // SEEN_UI_UXMANAGER_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 :
diff --git a/src/ui/view/edit-widget.cpp b/src/ui/view/edit-widget.cpp
index d34b18771..770a9bf87 100644
--- a/src/ui/view/edit-widget.cpp
+++ b/src/ui/view/edit-widget.cpp
@@ -228,12 +228,6 @@ EditWidget::onDialogAlignAndDistribute()
}
void
-EditWidget::onDialogSprayOptionClass()
-{
- _dlg_mgr.showDialog("SprayOptionClass");
-}
-
-void
EditWidget::onDialogDocumentProperties()
{
// manage (Inkscape::UI::Dialog::DocumentPreferences::create());
diff --git a/src/ui/view/edit-widget.h b/src/ui/view/edit-widget.h
index 452641e80..2bb708305 100644
--- a/src/ui/view/edit-widget.h
+++ b/src/ui/view/edit-widget.h
@@ -70,7 +70,6 @@ public:
void onDialogAbout();
void onDialogAlignAndDistribute();
- void onDialogSprayOptionClass();
void onDialogInkscapePreferences();
void onDialogDialog();
void onDialogDocumentProperties();
diff --git a/src/ui/widget/color-picker.cpp b/src/ui/widget/color-picker.cpp
index d6ae50ae3..34cf1d5e3 100644
--- a/src/ui/widget/color-picker.cpp
+++ b/src/ui/widget/color-picker.cpp
@@ -116,12 +116,15 @@ ColorPicker::on_changed (guint32)
void
sp_color_picker_color_mod(SPColorSelector *csel, GObject *cp)
{
- if (_in_use) return;
- else _in_use = true;
+ if (_in_use) {
+ return;
+ } else {
+ _in_use = true;
+ }
SPColor color;
- float alpha;
- csel->base->getColorAlpha(color, &alpha);
+ float alpha = 0;
+ csel->base->getColorAlpha(color, alpha);
guint32 rgba = color.toRGBA32( alpha );
ColorPicker *ptr = (ColorPicker *)(cp);
diff --git a/src/ui/widget/labelled.h b/src/ui/widget/labelled.h
index 3685944a4..5670af0b6 100644
--- a/src/ui/widget/labelled.h
+++ b/src/ui/widget/labelled.h
@@ -39,6 +39,8 @@ public:
Gtk::Widget const *getWidget() const;
Gtk::Label const *getLabel() const;
+ void setLabelText(const Glib::ustring &str) { _label->set_text(str); };
+
protected:
Gtk::Widget *_widget;
Gtk::Label *_label;
diff --git a/src/ui/widget/page-sizer.cpp b/src/ui/widget/page-sizer.cpp
index 68f26792a..e604a24ec 100644
--- a/src/ui/widget/page-sizer.cpp
+++ b/src/ui/widget/page-sizer.cpp
@@ -229,6 +229,11 @@ PageSizer::PageSizer(Registry & _wr)
_dimensionUnits( _("U_nits:"), "units", _wr ),
_dimensionWidth( _("_Width:"), _("Width of paper"), "width", _dimensionUnits, _wr ),
_dimensionHeight( _("_Height:"), _("Height of paper"), "height", _dimensionUnits, _wr ),
+ _marginTop( _("T_op margin:"), _("Top margin"), "fit-margin-top", _wr ),
+ _marginLeft( _("L_eft:"), _("Left margin"), "fit-margin-left", _wr),
+ _marginRight( _("Ri_ght:"), _("Right margin"), "fit-margin-right", _wr),
+ _marginBottom( _("Botto_m:"), _("Bottom margin"), "fit-margin-bottom", _wr),
+
_widgetRegistry(&_wr)
{
//# Set up the Paper Size combo box
@@ -273,16 +278,11 @@ PageSizer::PageSizer(Registry & _wr)
// _paperSizeListSelection->select(iter);
- pack_start (_paperSizeListBox, true, true, 0);
- _paperSizeListLabel.set_label(_("P_age size:"));
- _paperSizeListLabel.set_use_underline();
- _paperSizeListBox.pack_start (_paperSizeListLabel, false, false, 0);
- _paperSizeListLabel.set_mnemonic_widget (_paperSizeList);
- _paperSizeListBox.pack_start (_paperSizeListScroller, true, true, 0);
+ pack_start (_paperSizeListScroller, true, true, 0);
//## Set up orientation radio buttons
pack_start (_orientationBox, false, false, 0);
- _orientationLabel.set_label(_("Page orientation:"));
+ _orientationLabel.set_label(_("Orientation:"));
_orientationBox.pack_start(_orientationLabel, false, false, 0);
_landscapeButton.set_use_underline();
_landscapeButton.set_label(_("_Landscape"));
@@ -299,19 +299,48 @@ PageSizer::PageSizer(Registry & _wr)
//## Set up custom size frame
_customFrame.set_label(_("Custom size"));
pack_start (_customFrame, false, false, 0);
- _customTable.resize(2, 2);
- _customTable.set_border_width (4);
- _customTable.set_row_spacings (4);
- _customTable.set_col_spacings (4);
- _customTable.attach(_dimensionWidth, 0,1,0,1);
- _customTable.attach(_dimensionUnits, 1,2,0,1);
- _customTable.attach(_dimensionHeight, 0,1,1,2);
- _customTable.attach(_fitPageButton, 1,2,1,2);
- _customFrame.add(_customTable);
-
+ _customFrame.add(_customDimTable);
+
+ _customDimTable.resize(3, 2);
+ _customDimTable.set_border_width(4);
+ _customDimTable.set_row_spacings(4);
+ _customDimTable.set_col_spacings(4);
+ _customDimTable.attach(_dimensionWidth, 0,1, 0,1);
+ _customDimTable.attach(_dimensionUnits, 1,2, 0,1);
+ _customDimTable.attach(_dimensionHeight, 0,1, 1,2);
+ _customDimTable.attach(_fitPageMarginExpander, 0,2, 2,3);
+
+ //## Set up fit page expander
+ _fitPageMarginExpander.set_label(_("Resi_ze page to content..."));
+ _fitPageMarginExpander.set_use_underline();
+ _fitPageMarginExpander.add(_marginTable);
+
+ //## Set up margin settings
+ _marginTable.resize(4, 2);
+ _marginTable.set_border_width(4);
+ _marginTable.set_row_spacings(4);
+ _marginTable.set_col_spacings(4);
+ _marginTable.attach(_fitPageButtonAlign, 0,2, 0,1);
+ _marginTable.attach(_marginTopAlign, 0,2, 1,2);
+ _marginTable.attach(_marginLeftAlign, 0,1, 2,3);
+ _marginTable.attach(_marginRightAlign, 1,2, 2,3);
+ _marginTable.attach(_marginBottomAlign, 0,2, 3,4);
+
+ _marginTopAlign.set(0.5, 0.5, 0.0, 1.0);
+ _marginTopAlign.add(_marginTop);
+ _marginLeftAlign.set(0.0, 0.5, 0.0, 1.0);
+ _marginLeftAlign.add(_marginLeft);
+ _marginRightAlign.set(1.0, 0.5, 0.0, 1.0);
+ _marginRightAlign.add(_marginRight);
+ _marginBottomAlign.set(0.5, 0.5, 0.0, 1.0);
+ _marginBottomAlign.add(_marginBottom);
+
+ _fitPageButtonAlign.set(0.5, 0.5, 0.0, 1.0);
+ _fitPageButtonAlign.add(_fitPageButton);
_fitPageButton.set_use_underline();
- _fitPageButton.set_label(_("_Fit page to selection"));
+ _fitPageButton.set_label(_("_Resize page to drawing or selection"));
_tips.set_tip(_fitPageButton, _("Resize the page to fit the current selection, or the entire drawing if there is no selection"));
+
}
@@ -343,7 +372,7 @@ PageSizer::init ()
/**
* Set document dimensions (if not called by Doc prop's update()) and
* set the PageSizer's widgets and text entries accordingly. If
- * 'chageList' is true, then adjust the paperSizeList to show the closest
+ * 'changeList' is true, then adjust the paperSizeList to show the closest
* standard page size.
*
* \param w, h given in px
@@ -454,7 +483,7 @@ PageSizer::find_paper_size (double w, double h) const
/**
- * Tell the desktop to change the page size
+ * Tell the desktop to fit the page size to the selection or drawing.
*/
void
PageSizer::fire_fit_canvas_to_selection_or_drawing()
diff --git a/src/ui/widget/page-sizer.h b/src/ui/widget/page-sizer.h
index f970afe44..718eb95b5 100644
--- a/src/ui/widget/page-sizer.h
+++ b/src/ui/widget/page-sizer.h
@@ -183,24 +183,38 @@ protected:
Gtk::HBox _orientationBox;
Gtk::Label _orientationLabel;
Gtk::RadioButton _portraitButton;
- Gtk::RadioButton _landscapeButton;
+ Gtk::RadioButton _landscapeButton;
//callbacks
void on_portrait();
void on_landscape();
sigc::connection _portrait_connection;
- sigc::connection _landscape_connection;
+ sigc::connection _landscape_connection;
//### Custom size frame
Gtk::Frame _customFrame;
- Gtk::Table _customTable;
+ Gtk::Table _customDimTable;
RegisteredUnitMenu _dimensionUnits;
RegisteredScalarUnit _dimensionWidth;
- RegisteredScalarUnit _dimensionHeight;
- Gtk::Button _fitPageButton;
+ RegisteredScalarUnit _dimensionHeight;
+
+ //### Fit Page options
+ Gtk::Expander _fitPageMarginExpander;
+ Gtk::Table _marginTable;
+ Gtk::Alignment _marginTopAlign;
+ Gtk::Alignment _marginLeftAlign;
+ Gtk::Alignment _marginRightAlign;
+ Gtk::Alignment _marginBottomAlign;
+ RegisteredScalar _marginTop;
+ RegisteredScalar _marginLeft;
+ RegisteredScalar _marginRight;
+ RegisteredScalar _marginBottom;
+ Gtk::Alignment _fitPageButtonAlign;
+ Gtk::Button _fitPageButton;
+
//callback
void on_value_changed();
sigc::connection _changedw_connection;
- sigc::connection _changedh_connection;
+ sigc::connection _changedh_connection;
Registry *_widgetRegistry;
diff --git a/src/ui/widget/point.cpp b/src/ui/widget/point.cpp
index 508a8d961..f27cfe8c6 100644
--- a/src/ui/widget/point.cpp
+++ b/src/ui/widget/point.cpp
@@ -194,7 +194,7 @@ Point::setRange(double min, double max)
/** Sets the value of the spin button */
void
-Point::setValue(Geom::Point & p)
+Point::setValue(Geom::Point const & p)
{
xwidget.setValue(p[0]);
ywidget.setValue(p[1]);
diff --git a/src/ui/widget/point.h b/src/ui/widget/point.h
index 57a46de76..94477d877 100644
--- a/src/ui/widget/point.h
+++ b/src/ui/widget/point.h
@@ -64,7 +64,7 @@ public:
void setDigits(unsigned digits);
void setIncrements(double step, double page);
void setRange(double min, double max);
- void setValue(Geom::Point & p);
+ void setValue(Geom::Point const & p);
void update();
diff --git a/src/ui/widget/registered-widget.cpp b/src/ui/widget/registered-widget.cpp
index 95ddec286..db31d08d3 100644
--- a/src/ui/widget/registered-widget.cpp
+++ b/src/ui/widget/registered-widget.cpp
@@ -587,6 +587,96 @@ RegisteredTransformedPoint::on_value_changed()
}
/*#########################################
+ * Registered TRANSFORMEDPOINT
+ */
+
+RegisteredVector::~RegisteredVector()
+{
+ _value_x_changed_connection.disconnect();
+ _value_y_changed_connection.disconnect();
+}
+
+RegisteredVector::RegisteredVector ( const Glib::ustring& label, const Glib::ustring& tip,
+ const Glib::ustring& key, Registry& wr, Inkscape::XML::Node* repr_in,
+ SPDocument* doc_in )
+ : RegisteredWidget<Point> (label, tip),
+ _polar_coords(false)
+{
+ init_parent(key, wr, repr_in, doc_in);
+
+ setRange (-1e6, 1e6);
+ setDigits (2);
+ setIncrements(0.1, 1.0);
+ _value_x_changed_connection = signal_x_value_changed().connect (sigc::mem_fun (*this, &RegisteredVector::on_value_changed));
+ _value_y_changed_connection = signal_y_value_changed().connect (sigc::mem_fun (*this, &RegisteredVector::on_value_changed));
+}
+
+void
+RegisteredVector::setValue(Geom::Point const & p)
+{
+ if (!_polar_coords) {
+ Point::setValue(p);
+ } else {
+ Geom::Point polar;
+ polar[Geom::X] = atan2(p) *180/M_PI;
+ polar[Geom::Y] = p.length();
+ Point::setValue(polar);
+ }
+}
+
+void
+RegisteredVector::setValue(Geom::Point const & p, Geom::Point const & origin)
+{
+ RegisteredVector::setValue(p);
+ _origin = origin;
+}
+
+/**
+ * Changes the widgets text to polar coordinates. The SVG output will still be a normal carthesian vector.
+ * Careful: when calling getValue(), the return value's X-coord will be the angle, Y-value will be the distance/length.
+ * After changing the coords type (polar/non-polar), the value has to be reset (setValue).
+ */
+void
+RegisteredVector::setPolarCoords(bool polar_coords)
+{
+ _polar_coords = polar_coords;
+ if (polar_coords) {
+ xwidget.setLabelText("Angle:");
+ ywidget.setLabelText("Distance:");
+ } else {
+ xwidget.setLabelText("X:");
+ ywidget.setLabelText("Y:");
+ }
+}
+
+void
+RegisteredVector::on_value_changed()
+{
+ if (setProgrammatically()) {
+ clearProgrammatically();
+ return;
+ }
+
+ if (_wr->isUpdating())
+ return;
+
+ _wr->setUpdating (true);
+
+ Geom::Point origin = _origin;
+ Geom::Point vector = getValue();
+ if (_polar_coords) {
+ vector = Geom::Point::polar(vector[Geom::X]*M_PI/180, vector[Geom::Y]);
+ }
+
+ Inkscape::SVGOStringStream os;
+ os << origin << " , " << vector;
+
+ write_to_xml(os.str().c_str());
+
+ _wr->setUpdating (false);
+}
+
+/*#########################################
* Registered RANDOM
*/
diff --git a/src/ui/widget/registered-widget.h b/src/ui/widget/registered-widget.h
index a5c61f68a..7aefbb90e 100644
--- a/src/ui/widget/registered-widget.h
+++ b/src/ui/widget/registered-widget.h
@@ -334,6 +334,31 @@ protected:
};
+class RegisteredVector : public RegisteredWidget<Point> {
+public:
+ virtual ~RegisteredVector();
+ RegisteredVector (const Glib::ustring& label,
+ const Glib::ustring& tip,
+ const Glib::ustring& key,
+ Registry& wr,
+ Inkscape::XML::Node* repr_in = NULL,
+ SPDocument *doc_in = NULL );
+
+ // redefine setValue, because transform must be applied
+ void setValue(Geom::Point const & p);
+ void setValue(Geom::Point const & p, Geom::Point const & origin);
+ void setPolarCoords(bool polar_coords = true);
+
+protected:
+ sigc::connection _value_x_changed_connection;
+ sigc::connection _value_y_changed_connection;
+ void on_value_changed();
+
+ Geom::Point _origin;
+ bool _polar_coords;
+};
+
+
class RegisteredRandom : public RegisteredWidget<Random> {
public:
virtual ~RegisteredRandom();
diff --git a/src/ui/widget/selected-style.cpp b/src/ui/widget/selected-style.cpp
index e7b0188d8..a8f9f9c60 100644
--- a/src/ui/widget/selected-style.cpp
+++ b/src/ui/widget/selected-style.cpp
@@ -103,7 +103,7 @@ static guint nui_drop_target_entries = ENTRIES_SIZE(ui_drop_target_entries);
static Dialog::FillAndStroke *get_fill_and_stroke_panel(SPDesktop *desktop);
SelectedStyle::SelectedStyle(bool /*layout*/)
- :
+ :
current_stroke_width(0),
_desktop (NULL),
@@ -955,13 +955,13 @@ SelectedStyle::update()
_paintserver_id[i] += ")";
if (SP_IS_LINEARGRADIENT (server)) {
- SPGradient *vector = sp_gradient_get_vector(SP_GRADIENT(server), false);
+ SPGradient *vector = SP_GRADIENT(server)->getVector();
sp_gradient_image_set_gradient ((SPGradientImage *) _gradient_preview_l[i], vector);
place->add(_gradient_box_l[i]);
_tooltips.set_tip(*place, __lgradient[i]);
_mode[i] = SS_LGRADIENT;
} else if (SP_IS_RADIALGRADIENT (server)) {
- SPGradient *vector = sp_gradient_get_vector(SP_GRADIENT(server), false);
+ SPGradient *vector = SP_GRADIENT(server)->getVector();
sp_gradient_image_set_gradient ((SPGradientImage *) _gradient_preview_r[i], vector);
place->add(_gradient_box_r[i]);
_tooltips.set_tip(*place, __rgradient[i]);
@@ -1165,7 +1165,7 @@ RotateableSwatch::RotateableSwatch(SelectedStyle *parent, guint mode) :
undokey("ssrot1"),
cr(0),
cr_set(false)
-
+
{
}
@@ -1426,14 +1426,14 @@ RotateableStrokeWidth::do_release(double by, guint modifier) {
Dialog::FillAndStroke *get_fill_and_stroke_panel(SPDesktop *desktop)
{
- if (Dialog::PanelDialogBase *panel_dialog =
+ if (Dialog::PanelDialogBase *panel_dialog =
dynamic_cast<Dialog::PanelDialogBase *>(desktop->_dlg_mgr->getDialog("FillAndStroke"))) {
try {
- Dialog::FillAndStroke &fill_and_stroke =
+ Dialog::FillAndStroke &fill_and_stroke =
dynamic_cast<Dialog::FillAndStroke &>(panel_dialog->getPanel());
return &fill_and_stroke;
} catch (std::exception e) { }
- }
+ }
return 0;
}