diff options
| author | Krzysztof Kosi??ski <tweenk.pl@gmail.com> | 2010-01-14 15:54:06 +0000 |
|---|---|---|
| committer | Krzysztof Kosiński <tweenk.pl@gmail.com> | 2010-01-14 15:54:06 +0000 |
| commit | 4ffa8666045001bd3822db293ebb0b728b249492 (patch) | |
| tree | c4595e3c98a260bab48d37a342b3fdc4002fd6b6 /src | |
| parent | Do not append a segment when finishing an open path with right click (diff) | |
| parent | Re-enable snapping on release, for now. (diff) | |
| download | inkscape-4ffa8666045001bd3822db293ebb0b728b249492.tar.gz inkscape-4ffa8666045001bd3822db293ebb0b728b249492.zip | |
Merge GSoC 2009 node tool rewrite
(bzr r8976)
Diffstat (limited to 'src')
86 files changed, 8701 insertions, 9116 deletions
diff --git a/src/2geom/chebyshev.cpp b/src/2geom/chebyshev.cpp index 447c5183f..73baf7b6b 100644 --- a/src/2geom/chebyshev.cpp +++ b/src/2geom/chebyshev.cpp @@ -93,7 +93,7 @@ SBasis chebyshev_approximant_interpolating (double (*f)(double,void*), wr.fa = fa; wr.fb = fb; wr.in = in; - printf("%f %f\n", fa, fb); + //printf("%f %f\n", fa, fb); wr.f = f; wr.pp = p; return compose(Linear(in[0], in[1]), Linear(fa, fb)) + chebyshev_approximant(f_interp, order, in, &wr) + Linear(fa, fb); diff --git a/src/2geom/path.cpp b/src/2geom/path.cpp index 981c9f044..88c7a99b9 100644 --- a/src/2geom/path.cpp +++ b/src/2geom/path.cpp @@ -203,11 +203,10 @@ Path::nearestPointPerCurve(Point const& _point) const { //return a single nearest point for each curve in this path std::vector<double> np; - const Path& _path = *this; - for (Sequence::const_iterator it = _path.get_curves().begin() ; it != _path.get_curves().end()-1 ; ++it) + for (const_iterator it = begin() ; it != end_default(); ++it) //for (std::vector<Path>::const_iterator it = _path.begin(); it != _path.end(), ++it){ { - np.push_back((*it)->nearestPoint(_point)); + np.push_back(it->nearestPoint(_point)); } return np; } diff --git a/src/2geom/svg-path-parser.cpp b/src/2geom/svg-path-parser.cpp index 071b171b3..691ddf022 100644 --- a/src/2geom/svg-path-parser.cpp +++ b/src/2geom/svg-path-parser.cpp @@ -1,4 +1,3 @@ -#line 1 "/home/njh/svn/lib2geom/src/2geom/svg-path-parser.rl" /** * \file * \brief parse SVG path specifications diff --git a/src/Makefile.am b/src/Makefile.am index 15c7cb1b1..92e520e5b 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -134,6 +134,7 @@ include algorithms/Makefile_insert include ui/Makefile_insert include ui/cache/Makefile_insert include ui/dialog/Makefile_insert +include ui/tool/Makefile_insert include ui/view/Makefile_insert include ui/widget/Makefile_insert include util/Makefile_insert diff --git a/src/Makefile_insert b/src/Makefile_insert index b32889f65..574dfe084 100644 --- a/src/Makefile_insert +++ b/src/Makefile_insert @@ -57,7 +57,6 @@ ink_common_sources += \ fixes.cpp \ flood-context.cpp flood-context.h \ forward.h \ - gc-allocator.h \ gc-alloc.h \ gc-anchored.h gc-anchored.cpp \ gc-core.h \ @@ -102,8 +101,6 @@ ink_common_sources += \ message-stack.cpp message-stack.h \ mod360.cpp mod360.h \ modifier-fns.h \ - node-context.cpp node-context.h \ - nodepath.cpp nodepath.h \ object-edit.cpp object-edit.h \ object-hierarchy.cpp object-hierarchy.h \ object-snapper.cpp object-snapper.h \ diff --git a/src/desktop.cpp b/src/desktop.cpp index 70f7c4b15..69aec65e0 100644 --- a/src/desktop.cpp +++ b/src/desktop.cpp @@ -91,6 +91,10 @@ #include "widgets/desktop-widget.h" #include "box3d-context.h" +// TODO those includes are only for node tool quick zoom. Remove them after fixing it. +#include "ui/tool/node-tool.h" +#include "ui/tool/control-point-selection.h" + #include "display/sp-canvas.h" namespace Inkscape { namespace XML { class Node; }} @@ -624,9 +628,12 @@ SPDesktop::set_event_context (GtkType type, const gchar *config) while (event_context) { ec = event_context; sp_event_context_deactivate (ec); - event_context = ec->next; + // we have to keep event_context valid during destruction - otherwise writing + // destructors is next to impossible + SPEventContext *next = ec->next; sp_event_context_finish (ec); g_object_unref (G_OBJECT (ec)); + event_context = next; } ec = sp_event_context_new (type, this, config, SP_EVENT_CONTEXT_STATIC); @@ -785,11 +792,12 @@ SPDesktop::set_display_area (double x0, double y0, double x1, double y1, double int clear = FALSE; if (!NR_DF_TEST_CLOSE (newscale, scale, 1e-4 * scale)) { - /* Set zoom factors */ + // zoom changed - set new zoom factors _d2w = Geom::Scale(newscale, -newscale); _w2d = Geom::Scale(1/newscale, 1/-newscale); sp_canvas_item_affine_absolute(SP_CANVAS_ITEM(main), _d2w); clear = TRUE; + signal_zoom_changed.emit(_d2w.descrim()); } /* Calculate top left corner (in document pixels) */ @@ -875,11 +883,6 @@ SPDesktop::next_zoom() zooms_future = g_list_remove (zooms_future, ((NRRect *) zooms_future->data)); } -#include "tools-switch.h" -#include "node-context.h" -#include "shape-editor.h" -#include "nodepath.h" - /** \brief Performs a quick zoom into what the user is working on \param enable Whether we're going in or out of quick zoom @@ -895,67 +898,26 @@ SPDesktop::zoom_quick (bool enable) _quick_zoom_stored_area = get_display_area(); bool zoomed = false; - if (!zoomed) { - SPItem * singleItem = selection->singleItem(); - if (singleItem != NULL && tools_isactive(this, TOOLS_NODES)) { - - Inkscape::NodePath::Path * nodepath = event_context->shape_editor->get_nodepath(); - // printf("I've got a nodepath, crazy\n"); - - if (nodepath) { - Geom::Rect nodes; - bool firstnode = true; - - if (nodepath->selected) { - for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) { - Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data; - for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) { - Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data; - if (node->selected) { - // printf("\tSelected node\n"); - if (firstnode) { - nodes = Geom::Rect(node->pos, node->pos); - firstnode = false; - } else { - nodes.expandTo(node->pos); - } - - if (node->p.other != NULL) { - /* Include previous node pos */ - nodes.expandTo(node->p.other->pos); - - /* Include previous handle */ - if (!sp_node_side_is_line(node, &node->p)) { - nodes.expandTo(node->p.pos); - } - } - - if (node->n.other != NULL) { - /* Include previous node pos */ - nodes.expandTo(node->n.other->pos); - - /* Include previous handle */ - if (!sp_node_side_is_line(node, &node->n)) { - nodes.expandTo(node->n.pos); - } - } - } - } - } - - if (!firstnode && nodes.area() * 2.0 < _quick_zoom_stored_area.area()) { - set_display_area(nodes, 10); - zoomed = true; - } - } - } + // TODO This needs to migrate into the node tool, but currently the design + // of this method is sufficiently wrong to prevent this. + if (!zoomed && INK_IS_NODE_TOOL(event_context)) { + InkNodeTool *nt = static_cast<InkNodeTool*>(event_context); + if (!nt->_selected_nodes->empty()) { + Geom::Rect nodes = *nt->_selected_nodes->bounds(); + double area = nodes.area(); + // do not zoom if a single cusp node is selected aand the bounds + // have zero area. + if (!Geom::are_near(area, 0) && area * 2.0 < _quick_zoom_stored_area.area()) { + set_display_area(nodes, true); + zoomed = true; + } } } if (!zoomed) { Geom::OptRect const d = selection->bounds(); if (d && d->area() * 2.0 < _quick_zoom_stored_area.area()) { - set_display_area(*d, 10); + set_display_area(*d, true); zoomed = true; } } @@ -965,7 +927,7 @@ SPDesktop::zoom_quick (bool enable) zoomed = true; } } else { - set_display_area(_quick_zoom_stored_area, 0); + set_display_area(_quick_zoom_stored_area, false); } _quick_zoom_enabled = enable; @@ -1806,7 +1768,7 @@ Geom::Point SPDesktop::dt2doc(Geom::Point const &p) const } -/** +/* * Pop event context from desktop's context stack. Never used. */ // void @@ -1848,4 +1810,4 @@ Geom::Point SPDesktop::dt2doc(Geom::Point const &p) const fill-column:99 End: */ -// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 : +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 : diff --git a/src/desktop.h b/src/desktop.h index a661d9900..00f6cfdd5 100644 --- a/src/desktop.h +++ b/src/desktop.h @@ -140,6 +140,11 @@ struct SPDesktop : public Inkscape::UI::View::View sigc::signal<void, SPObject *> _layer_changed_signal; sigc::signal<bool, const SPCSSAttr *>::accumulated<StopOnTrue> _set_style_signal; sigc::signal<int, SPStyle *, int>::accumulated<StopOnNonZero> _query_style_signal; + + /// Emitted when the zoom factor changes (not emitted when scrolling). + /// The parameter is the new zoom factor + sigc::signal<void, double> signal_zoom_changed; + sigc::connection connectDocumentReplaced (const sigc::slot<void,SPDesktop*,SPDocument*> & slot) { return _document_replaced_signal.connect (slot); diff --git a/src/display/sp-canvas-util.cpp b/src/display/sp-canvas-util.cpp index 30cd0dfa0..a23b157df 100644 --- a/src/display/sp-canvas-util.cpp +++ b/src/display/sp-canvas-util.cpp @@ -112,10 +112,11 @@ void sp_canvas_item_move_to_z (SPCanvasItem * item, gint z) if (z == current_z) return; - if (z > current_z) + if (z > current_z) { sp_canvas_item_raise (item, z - current_z); - - sp_canvas_item_lower (item, current_z - z); + } else { + sp_canvas_item_lower (item, current_z - z); + } } gint diff --git a/src/display/sp-canvas.cpp b/src/display/sp-canvas.cpp index aee53838f..6d996fbe2 100644 --- a/src/display/sp-canvas.cpp +++ b/src/display/sp-canvas.cpp @@ -103,6 +103,7 @@ static void group_remove (SPCanvasGroup *group, SPCanvasItem *item); /* SPCanvasItem */ enum {ITEM_EVENT, ITEM_LAST_SIGNAL}; +enum {PROP_0, PROP_VISIBLE}; static void sp_canvas_request_update (SPCanvas *canvas); @@ -113,12 +114,11 @@ static void sp_canvas_item_init (SPCanvasItem *item); static void sp_canvas_item_dispose (GObject *object); static void sp_canvas_item_construct (SPCanvasItem *item, SPCanvasGroup *parent, gchar const *first_arg_name, va_list args); + static int emit_event (SPCanvas *canvas, GdkEvent *event); static guint item_signals[ITEM_LAST_SIGNAL] = { 0 }; -static GtkObjectClass *item_parent_class; - /** * Registers the SPCanvasItem class with Glib and returns its type number. */ @@ -151,9 +151,6 @@ sp_canvas_item_class_init (SPCanvasItemClass *klass) { GObjectClass *object_class = (GObjectClass *) klass; - /* fixme: Derive from GObject */ - item_parent_class = (GtkObjectClass*)gtk_type_class (GTK_TYPE_OBJECT); - item_signals[ITEM_EVENT] = g_signal_new ("event", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, @@ -172,6 +169,9 @@ sp_canvas_item_class_init (SPCanvasItemClass *klass) static void sp_canvas_item_init (SPCanvasItem *item) { + // TODO items should not be visible on creation - this causes kludges with items + // that should be initially invisible; examples of such items: node handles, the CtrlRect + // used for rubberbanding, path outline, etc. item->flags |= SP_CANVAS_ITEM_VISIBLE; item->xform = Geom::Matrix(Geom::identity()); } @@ -277,7 +277,7 @@ sp_canvas_item_dispose (GObject *object) group_remove (SP_CANVAS_GROUP (item->parent), item); } - G_OBJECT_CLASS (item_parent_class)->dispose (object); + G_OBJECT_CLASS (g_type_class_peek(g_type_parent(sp_canvas_item_get_type())))->dispose (object); } /** @@ -477,6 +477,13 @@ sp_canvas_item_lower (SPCanvasItem *item, int positions) item->canvas->need_repick = TRUE; } +bool +sp_canvas_item_is_visible (SPCanvasItem *item) +{ + return item->flags & SP_CANVAS_ITEM_VISIBLE; +} + + /** * Sets visible flag on item and requests a redraw. */ @@ -542,8 +549,13 @@ sp_canvas_item_grab (SPCanvasItem *item, guint event_mask, GdkCursor *cursor, gu if (item->canvas->grabbed_item) return -1; - if (!(item->flags & SP_CANVAS_ITEM_VISIBLE)) - return -1; + // This test disallows grabbing events by an invisible item, which may be useful + // sometimes. An example is the hidden control point used for the selector component, + // where it is used for object selection and rubberbanding. There seems to be nothing + // preventing this except this test, so I removed it. + // -- Krzysztof Kosiński, 2009.08.12 + //if (!(item->flags & SP_CANVAS_ITEM_VISIBLE)) + // return -1; if (HAS_BROKEN_MOTION_HINTS) { event_mask &= ~GDK_POINTER_MOTION_HINT_MASK; diff --git a/src/display/sp-canvas.h b/src/display/sp-canvas.h index 35e3fb5de..a2af080ef 100644 --- a/src/display/sp-canvas.h +++ b/src/display/sp-canvas.h @@ -101,6 +101,7 @@ void sp_canvas_item_affine_absolute(SPCanvasItem *item, Geom::Matrix const &aff) void sp_canvas_item_raise(SPCanvasItem *item, int positions); void sp_canvas_item_lower(SPCanvasItem *item, int positions); +bool sp_canvas_item_is_visible(SPCanvasItem *item); void sp_canvas_item_show(SPCanvasItem *item); void sp_canvas_item_hide(SPCanvasItem *item); int sp_canvas_item_grab(SPCanvasItem *item, unsigned int event_mask, GdkCursor *cursor, guint32 etime); diff --git a/src/dom/io/httpclient.cpp b/src/dom/io/httpclient.cpp index 4245d71f2..97c8575cf 100644 --- a/src/dom/io/httpclient.cpp +++ b/src/dom/io/httpclient.cpp @@ -73,7 +73,7 @@ bool HttpClient::openGet(const URI &uri) socket.enableSSL(true); else { - printf("Bad proto scheme:%d\n", uri.getScheme()); + //printf("Bad proto scheme:%d\n", uri.getScheme()); return false; } @@ -106,7 +106,7 @@ bool HttpClient::openGet(const URI &uri) { if (!socket.readLine(msg)) return false; - printf("header:'%s'\n", msg.c_str()); + //printf("header:'%s'\n", msg.c_str()); if (msg.size() < 1) break; } diff --git a/src/dom/odf/SvgOdg.cpp b/src/dom/odf/SvgOdg.cpp index 2b1161310..e69de29bb 100644 --- a/src/dom/odf/SvgOdg.cpp +++ b/src/dom/odf/SvgOdg.cpp @@ -1,1551 +0,0 @@ -/** - * - * This is a small experimental class for converting between - * SVG and OpenDocument .odg files. This code is not intended - * to be a permanent solution for SVG-to-ODG conversion. Rather, - * it is a quick-and-easy test bed for ideas which will be later - * recoded into C++. - * - * --------------------------------------------------------------------- - * - * SvgOdg - A program to experiment with conversions between SVG and ODG - * Copyright (C) 2006 Bob Jamison - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * For more information, please write to rwjj@earthlink.net - * - */ - - -/** - * - */ -public class SvgOdg -{ - - - -/** - * Namespace declarations - */ -public static final String SVG_NS = - "http://www.w3.org/2000/svg"; -public static final String XLINK_NS = - "http://www.w3.org/1999/xlink"; -public static final String ODF_NS = - "urn:oasis:names:tc:opendocument:xmlns:office:1.0"; -public static final String ODG_NS = - "urn:oasis:names:tc:opendocument:xmlns:drawing:1.0"; -public static final String ODSVG_NS = - "urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0"; - - -DecimalFormat nrfmt; -//static final double pxToCm = 0.0339; -static final double pxToCm = 0.0275; -static final double piToRad = 0.0174532925; -BufferedWriter out; -BufferedReader in; -int imageNr; -int styleNr; - -//######################################################################## -//# M E S S A G E S -//######################################################################## - -/** - * - */ -void err(String msg) -{ - System.out.println("SvgOdg ERROR:" + msg); -} - -/** - * - */ -void trace(String msg) -{ - System.out.println("SvgOdg:" + msg); -} - - - - -//######################################################################## -//# I N P U T / O U T P U T -//######################################################################## - -boolean po(String s) -{ - try - { - out.write(s); - } - catch(IOException e) - { - return false; - } - return true; -} - -//######################################################################## -//# U T I L I T Y -//######################################################################## - -public void dumpDocument(Document doc) -{ - String s = ""; - try - { - TransformerFactory factory = TransformerFactory.newInstance(); - Transformer trans = factory.newTransformer(); - DOMSource source = new DOMSource(doc); - ByteArrayOutputStream bos = new ByteArrayOutputStream(); - StreamResult result = new StreamResult(bos); - trans.transform(source, result); - byte buf[] = bos.toByteArray(); - s = new String(buf); - } - catch (javax.xml.transform.TransformerException e) - { - } - trace("doc:" + s); -} - - -//######################################################################## -//# I N N E R C L A S S ImageInfo -//######################################################################## -public class ImageInfo -{ -String name; -String newName; -byte buf[]; - -public String getName() -{ - return name; -} - -public String getNewName() -{ - return newName; -} - - -public byte[] getBuf() -{ - return buf; -} - -public ImageInfo(String name, String newName, byte buf[]) -{ - this.name = name; - this.name = newName; - this.buf = buf; -} - -} -//######################################################################## -//# I N N E R C L A S S StyleInfo -//######################################################################## -public class StyleInfo -{ - -String name; -public String getName() -{ - return name; -} - -String cssStyle; -public String getCssStyle() -{ - return cssStyle; -} - -String stroke; -public String getStroke() -{ - return stroke; -} - -String strokeColor; -public String getStrokeColor() -{ - return strokeColor; -} - -String strokeWidth; -public String getStrokeWidth() -{ - return strokeWidth; -} - -String fill; -public String getFill() -{ - return fill; -} - -String fillColor; -public String getFillColor() -{ - return fillColor; -} - -public StyleInfo(String name, String cssStyle) -{ - this.name = name; - this.cssStyle = cssStyle; - fill = "none"; - stroke = "none"; -} - -} -//######################################################################## -//# E N D I N N E R C L A S S E S -//######################################################################## - - - - -//######################################################################## -//# V A R I A B L E S -//######################################################################## - -/** - * ODF content.xml file - */ -Document content; -public Document getContent() -{ - return content; -} - -/** - * ODF meta.xml file - */ -Document meta; -public Document getMeta() -{ - return meta; -} - -/** - * SVG file - */ -Document svg; -public Document getSvg() -{ - return svg; -} - -/** - * Loaded ODF or SVG images - */ -ArrayList<ImageInfo> images; -public ArrayList<ImageInfo> getImages() -{ - return images; -} - -/** - * CSS styles - */ -HashMap<String, StyleInfo> styles; -public HashMap<String, StyleInfo> getStyles() -{ - return styles; -} - - - - - -//######################################################################## -//# S V G T O O D F -//######################################################################## - -class PathData -{ -String cmd; -double nr[]; -PathData(String s, double buf[]) -{ - cmd=s; nr = buf; -} -} - -double getPathNum(StringTokenizer st) -{ - if (!st.hasMoreTokens()) - return 0.0; - String s = st.nextToken(); - double nr = Double.parseDouble(s); - return nr; -} - -String parsePathData(String pathData, double bounds[]) -{ - double minx = Double.MAX_VALUE; - double maxx = Double.MIN_VALUE; - double miny = Double.MAX_VALUE; - double maxy = Double.MIN_VALUE; - //trace("#### pathData:" + pathData); - ArrayList<PathData> data = new ArrayList<PathData>(); - StringTokenizer st = new StringTokenizer(pathData, " ,"); - while (true) - { - String s = st.nextToken(); - if ( s.equals("z") || s.equals("Z") ) - { - PathData pd = new PathData(s, new double[0]); - data.add(pd); - break; - } - else if ( s.equals("h") || s.equals("H") ) - { - double d[] = new double[1]; - d[0] = getPathNum(st) * pxToCm; - if (d[0] < minx) minx = d[0]; - else if (d[0] > maxx) maxx = d[0]; - PathData pd = new PathData(s, d); - data.add(pd); - } - else if ( s.equals("v") || s.equals("V") ) - { - double d[] = new double[1]; - d[0] = getPathNum(st) * pxToCm; - if (d[0] < miny) miny = d[0]; - else if (d[0] > maxy) maxy = d[0]; - PathData pd = new PathData(s, d); - data.add(pd); - } - else if ( s.equals("m") || s.equals("M") || - s.equals("l") || s.equals("L") || - s.equals("t") || s.equals("T") ) - { - double d[] = new double[2]; - d[0] = getPathNum(st) * pxToCm; - d[1] = getPathNum(st) * pxToCm; - if (d[0] < minx) minx = d[0]; - else if (d[0] > maxx) maxx = d[0]; - if (d[1] < miny) miny = d[1]; - else if (d[1] > maxy) maxy = d[1]; - PathData pd = new PathData(s, d); - data.add(pd); - } - else if ( s.equals("q") || s.equals("Q") || - s.equals("s") || s.equals("S") ) - { - double d[] = new double[4]; - d[0] = getPathNum(st) * pxToCm; - d[1] = getPathNum(st) * pxToCm; - if (d[0] < minx) minx = d[0]; - else if (d[0] > maxx) maxx = d[0]; - if (d[1] < miny) miny = d[1]; - else if (d[1] > maxy) maxy = d[1]; - d[2] = getPathNum(st) * pxToCm; - d[3] = getPathNum(st) * pxToCm; - if (d[2] < minx) minx = d[2]; - else if (d[2] > maxx) maxx = d[2]; - if (d[3] < miny) miny = d[3]; - else if (d[3] > maxy) maxy = d[3]; - PathData pd = new PathData(s, d); - data.add(pd); - } - else if ( s.equals("c") || s.equals("C") ) - { - double d[] = new double[6]; - d[0] = getPathNum(st) * pxToCm; - d[1] = getPathNum(st) * pxToCm; - if (d[0] < minx) minx = d[0]; - else if (d[0] > maxx) maxx = d[0]; - if (d[1] < miny) miny = d[1]; - else if (d[1] > maxy) maxy = d[1]; - d[2] = getPathNum(st) * pxToCm; - d[3] = getPathNum(st) * pxToCm; - if (d[2] < minx) minx = d[2]; - else if (d[2] > maxx) maxx = d[2]; - if (d[3] < miny) miny = d[3]; - else if (d[3] > maxy) maxy = d[3]; - d[4] = getPathNum(st) * pxToCm; - d[5] = getPathNum(st) * pxToCm; - if (d[4] < minx) minx = d[4]; - else if (d[4] > maxx) maxx = d[4]; - if (d[5] < miny) miny = d[5]; - else if (d[5] > maxy) maxy = d[5]; - PathData pd = new PathData(s, d); - data.add(pd); - } - else if ( s.equals("a") || s.equals("A") ) - { - double d[] = new double[6]; - d[0] = getPathNum(st) * pxToCm; - d[1] = getPathNum(st) * pxToCm; - if (d[0] < minx) minx = d[0]; - else if (d[0] > maxx) maxx = d[0]; - if (d[1] < miny) miny = d[1]; - else if (d[1] > maxy) maxy = d[1]; - d[2] = getPathNum(st) * piToRad;//angle - d[3] = getPathNum(st) * piToRad;//angle - d[4] = getPathNum(st) * pxToCm; - d[5] = getPathNum(st) * pxToCm; - if (d[4] < minx) minx = d[4]; - else if (d[4] > maxx) maxx = d[4]; - if (d[5] < miny) miny = d[5]; - else if (d[5] > maxy) maxy = d[5]; - PathData pd = new PathData(s, d); - data.add(pd); - } - //trace("x:" + x + " y:" + y); - } - - trace("minx:" + minx + " maxx:" + maxx + - " miny:" + miny + " maxy:" + maxy); - - StringBuffer buf = new StringBuffer(); - for (PathData pd : data) - { - buf.append(pd.cmd); - buf.append(" "); - for (double d:pd.nr) - { - buf.append(nrfmt.format(d * 1000.0)); - buf.append(" "); - } - } - - bounds[0] = minx; - bounds[1] = miny; - bounds[2] = maxx; - bounds[3] = maxy; - - return buf.toString(); -} - - - -boolean parseTransform(String transStr, AffineTransform trans) -{ - trace("== transform:"+ transStr); - StringTokenizer st = new StringTokenizer(transStr, ")"); - while (st.hasMoreTokens()) - { - String chunk = st.nextToken(); - StringTokenizer st2 = new StringTokenizer(chunk, " ,("); - if (!st2.hasMoreTokens()) - continue; - String name = st2.nextToken(); - trace(" ++name:"+ name); - if (name.equals("matrix")) - { - double v[] = new double[6]; - for (int i=0 ; i<6 ; i++) - { - if (!st2.hasMoreTokens()) - break; - v[i] = Double.parseDouble(st2.nextToken()) * pxToCm; - } - AffineTransform mat = new AffineTransform(v); - trans.concatenate(mat); - } - else if (name.equals("translate")) - { - double dx = 0.0; - double dy = 0.0; - if (!st2.hasMoreTokens()) - continue; - dx = Double.parseDouble(st2.nextToken()) * pxToCm; - if (st2.hasMoreTokens()) - dy = Double.parseDouble(st2.nextToken()) * pxToCm; - trans.translate(dx, dy); - } - else if (name.equals("scale")) - { - double sx = 1.0; - double sy = 1.0; - if (!st2.hasMoreTokens()) - continue; - sx = sy = Double.parseDouble(st2.nextToken()); - if (st2.hasMoreTokens()) - sy = Double.parseDouble(st2.nextToken()); - trans.scale(sx, sy); - } - else if (name.equals("rotate")) - { - double r = 0.0; - double cx = 0.0; - double cy = 0.0; - if (!st2.hasMoreTokens()) - continue; - r = Double.parseDouble(st2.nextToken()) * piToRad; - if (st2.hasMoreTokens()) - { - cx = Double.parseDouble(st2.nextToken()) * pxToCm; - if (!st2.hasMoreTokens()) - continue; - cy = Double.parseDouble(st2.nextToken()) * pxToCm; - trans.rotate(r, cx, cy); - } - else - { - trans.rotate(r); - } - } - else if (name.equals("skewX")) - { - double angle = 0.0; - if (!st2.hasMoreTokens()) - continue; - angle = Double.parseDouble(st2.nextToken()); - trans.shear(angle, 0.0); - } - else if (name.equals("skewY")) - { - double angle = 0.0; - if (!st2.hasMoreTokens()) - continue; - angle = Double.parseDouble(st2.nextToken()); - trans.shear(0.0, angle); - } - } - return true; -} - - - -String coordToOdg(String sval) -{ - double nr = Double.parseDouble(sval); - nr = nr * pxToCm; - String s = nrfmt.format(nr) + "cm"; - return s; -} - - -boolean writeSvgAttributes(Element elem, AffineTransform trans) -{ - NamedNodeMap attrs = elem.getAttributes(); - String ename = elem.getLocalName(); - for (int i=0 ; i<attrs.getLength() ; i++) - { - Attr attr = (Attr)attrs.item(i); - String aname = attr.getName(); - String aval = attr.getValue(); - if (aname.startsWith("xmlns")) - continue; - else if (aname.equals("d"))//already handled - continue; - else if (aname.startsWith("transform")) - { - parseTransform(aval, trans); - continue; - } - else if (aname.equals("style")) - { - StyleInfo style = styles.get(aval); - if (style != null) - { - po(" draw:style-name=\""); - po(style.getName()); - po("\""); - } - continue; - } - if (aname.equals("x") || aname.equals("y") || - aname.equals("width") || aname.equals("height")) - { - aval = coordToOdg(aval); - } - if ("id".equals(aname)) - po(" "); - else if ("transform".equals(aname)) - po(" draw:"); - else - po(" svg:"); - po(aname); - po("=\""); - po(aval); - po("\""); - } - - //Output the current transform - if (!trans.isIdentity() && - !( - ename.equals("g") || - ename.equals("defs") || - ename.equals("metadata") - ) - ) - { - double v[] = new double[6]; - trans.getMatrix(v); - po(" draw:transform=\"matrix(" + - nrfmt.format(v[0]) + "," + - nrfmt.format(v[1]) + "," + - nrfmt.format(v[2]) + "," + - nrfmt.format(v[3]) + "," + - nrfmt.format(v[4]) + "," + - nrfmt.format(v[5]) + ")\""); - } - return true; -} - -public boolean writeOdfContent(Element elem, AffineTransform trans) -{ - String ns = elem.getNamespaceURI(); - String tagName = elem.getLocalName(); - //trace("ns:" + ns + " tagName:" + tagName); - if (!ns.equals(SVG_NS)) - return true; - if (tagName.equals("svg")) - { - NodeList children = elem.getChildNodes(); - for (int i=0 ; i<children.getLength() ; i++) - { - Node n = children.item(i); - if (n.getNodeType() == Node.ELEMENT_NODE) - if (!writeOdfContent((Element)n, - (AffineTransform)trans.clone())) - return false; - } - } - else if (tagName.equals("g")) - { - //String transform = elem.getAttribute("transform"); - po("<draw:g"); - writeSvgAttributes(elem, trans); po(">\n"); - NodeList children = elem.getChildNodes(); - for (int i=0 ; i<children.getLength() ; i++) - { - Node n = children.item(i); - if (n.getNodeType() == Node.ELEMENT_NODE) - if (!writeOdfContent((Element)n, - (AffineTransform)trans.clone())) - return false; - } - po("</draw:g>\n"); - } - else if (tagName.equals("text")) - { - String x = coordToOdg(elem.getAttribute("x")); - String y = coordToOdg(elem.getAttribute("y")); - String width = "5cm"; - String height = "2cm"; - String txt = elem.getTextContent(); - po("<draw:frame draw:style-name=\"grx1\" draw:layer=\"layout\" "); - po("svg:x=\"" + x + "\" svg:y=\"" + y + "\" "); - po("svg:width=\"" + width + "\" svg:height=\"" + height + "\""); - po(">\n"); - po(" <draw:text-box draw:auto-grow-height=\"true\" draw:auto-grow-width=\"true\">\n"); - po(" <text:p text:style-name=\"P1\"> " + txt + "</text:p>\n"); - po(" </draw:text-box>\n"); - po("</draw:frame>\n"); - return true; - } - else if (tagName.equals("image")) - { - String x = coordToOdg(elem.getAttribute("x")); - String y = coordToOdg(elem.getAttribute("y")); - String width = coordToOdg(elem.getAttribute("width")); - String height = coordToOdg(elem.getAttribute("height")); - String imageName = elem.getAttributeNS(XLINK_NS, "href"); - po("<draw:frame draw:style-name=\"grx1\" draw:layer=\"layout\" "); - po("svg:x=\"" + x + "\" svg:y=\"" + y + "\" "); - po("svg:width=\"" + width + "\" svg:height=\"" + height + "\">\n"); - po(" <draw:image xlink:href=\"Pictures/" + imageName + "\" xlink:type=\"simple\" xlink:show=\"embed\" xlink:actuate=\"onLoad\"><text:p/></draw:image>\n"); - po("</draw:frame>\n"); - return true; - } - else if (tagName.equals("path")) - { - double bounds[] = new double[4]; - String d = elem.getAttribute("d"); - String newd = parsePathData(d, bounds); - double x = bounds[0]; - double y = bounds[1]; - double width = (bounds[2]-bounds[0]); - double height = (bounds[3]-bounds[1]); - po("<draw:path draw:layer=\"layout\" \n"); - po(" svg:x=\"" + nrfmt.format(x) + "cm\" "); - po("svg:y=\"" + nrfmt.format(y) + "cm\" "); - po("svg:width=\"" + nrfmt.format(width) + "cm\" "); - po("svg:height=\"" + nrfmt.format(height) + "cm\" "); - po("svg:viewBox=\"0.0 0.0 " + - nrfmt.format(bounds[2] * 1000.0) + " " + - nrfmt.format(bounds[3] * 1000.0) + "\"\n"); - po(" svg:d=\"" + newd + "\"\n "); - - writeSvgAttributes(elem, trans); po("/>\n"); - //po(" svg:d=\"" + d + "\"/>\n"); - return true; - } - - else - { - //Verbatim tab mapping - po("<draw:"); po(tagName); - writeSvgAttributes(elem, trans); po(">\n"); - po("</draw:"); po(tagName); po(">\n"); - } - - return true; -} - - -boolean writeOdfContent(ZipOutputStream outs) -{ - try - { - ZipEntry ze = new ZipEntry("content.xml"); - outs.putNextEntry(ze); - out = new BufferedWriter(new OutputStreamWriter(outs)); - } - catch (IOException e) - { - return false; - } - - NodeList res = svg.getElementsByTagNameNS(SVG_NS, "svg"); - if (res.getLength() < 1) - { - err("saveOdf: no <svg> root in .svg file"); - return false; - } - Element root = (Element)res.item(0); - - - po("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"); - po("<office:document-content\n"); - po(" xmlns:office=\"urn:oasis:names:tc:opendocument:xmlns:office:1.0\"\n"); - po(" xmlns:style=\"urn:oasis:names:tc:opendocument:xmlns:style:1.0\"\n"); - po(" xmlns:text=\"urn:oasis:names:tc:opendocument:xmlns:text:1.0\"\n"); - po(" xmlns:table=\"urn:oasis:names:tc:opendocument:xmlns:table:1.0\"\n"); - po(" xmlns:draw=\"urn:oasis:names:tc:opendocument:xmlns:drawing:1.0\"\n"); - po(" xmlns:fo=\"urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0\"\n"); - po(" xmlns:xlink=\"http://www.w3.org/1999/xlink\"\n"); - po(" xmlns:dc=\"http://purl.org/dc/elements/1.1/\"\n"); - po(" xmlns:meta=\"urn:oasis:names:tc:opendocument:xmlns:meta:1.0\"\n"); - po(" xmlns:number=\"urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0\"\n"); - po(" xmlns:presentation=\"urn:oasis:names:tc:opendocument:xmlns:presentation:1.0\"\n"); - po(" xmlns:svg=\"urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0\"\n"); - po(" xmlns:chart=\"urn:oasis:names:tc:opendocument:xmlns:chart:1.0\"\n"); - po(" xmlns:dr3d=\"urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0\"\n"); - po(" xmlns:math=\"http://www.w3.org/1998/Math/MathML\"\n"); - po(" xmlns:form=\"urn:oasis:names:tc:opendocument:xmlns:form:1.0\"\n"); - po(" xmlns:script=\"urn:oasis:names:tc:opendocument:xmlns:script:1.0\"\n"); - po(" xmlns:ooo=\"http://openoffice.org/2004/office\"\n"); - po(" xmlns:ooow=\"http://openoffice.org/2004/writer\"\n"); - po(" xmlns:oooc=\"http://openoffice.org/2004/calc\"\n"); - po(" xmlns:dom=\"http://www.w3.org/2001/xml-events\"\n"); - po(" xmlns:xforms=\"http://www.w3.org/2002/xforms\"\n"); - po(" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\"\n"); - po(" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n"); - po(" xmlns:smil=\"urn:oasis:names:tc:opendocument:xmlns:smil-compatible:1.0\"\n"); - po(" xmlns:anim=\"urn:oasis:names:tc:opendocument:xmlns:animation:1.0\"\n"); - po(" office:version=\"1.0\">\n"); - po("\n"); - po("\n"); - po("<office:scripts/>\n"); - po("<office:automatic-styles>\n"); - po("<style:style style:name=\"dp1\" style:family=\"drawing-page\"/>\n"); - po("<style:style style:name=\"grx1\" style:family=\"graphic\" style:parent-style-name=\"standard\">\n"); - po(" <style:graphic-properties draw:stroke=\"none\" draw:fill=\"solid\" draw:textarea-horizontal-align=\"center\" draw:textarea-vertical-align=\"middle\" draw:color-mode=\"standard\" draw:luminance=\"0%\" draw:contrast=\"0%\" draw:gamma=\"100%\" draw:red=\"0%\" draw:green=\"0%\" draw:blue=\"0%\" fo:clip=\"rect(0cm 0cm 0cm 0cm)\" draw:image-opacity=\"100%\" style:mirror=\"none\"/>\n"); - po("</style:style>\n"); - po("<style:style style:name=\"P1\" style:family=\"paragraph\">\n"); - po(" <style:paragraph-properties fo:text-align=\"center\"/>\n"); - po("</style:style>\n"); - - //## Dump our style table - for (StyleInfo s : styles.values()) - { - po("<style:style style:name=\"" + s.getName() + "\" style:family=\"graphic\" style:parent-style-name=\"standard\">\n"); - po(" <style:graphic-properties"); - po(" draw:fill=\"" + s.getFill() + "\""); - if (!s.getFill().equals("none")) - po(" draw:fill-color=\"" + s.getFillColor() + "\""); - po(" draw:stroke=\"" + s.getStroke() + "\""); - if (!s.getStroke().equals("none")) - { - po(" svg:stroke-width=\"" + s.getStrokeWidth() + "\""); - po(" svg:stroke-color=\"" + s.getStrokeColor() + "\""); - } - po("/>\n"); - po("</style:style>\n"); - } - po("</office:automatic-styles>\n"); - po("\n"); - po("\n"); - po("<office:body>\n"); - po("<office:drawing>\n"); - po("<draw:page draw:name=\"page1\" draw:style-name=\"dp1\" draw:master-page-name=\"Default\">\n"); - po("\n\n\n"); - AffineTransform trans = new AffineTransform(); - //trans.scale(12.0, 12.0); - po("<!-- ######### CONVERSION FROM SVG STARTS ######## -->\n"); - writeOdfContent(root, trans); - po("<!-- ######### CONVERSION FROM SVG ENDS ######## -->\n"); - po("\n\n\n"); - - po("</draw:page>\n"); - po("</office:drawing>\n"); - po("</office:body>\n"); - po("</office:document-content>\n"); - - - try - { - out.flush(); - outs.closeEntry(); - } - catch (IOException e) - { - err("writeOdfContent:" + e); - return false; - } - return true; -} - -boolean writeOdfMeta(ZipOutputStream outs) -{ - try - { - ZipEntry ze = new ZipEntry("meta.xml"); - outs.putNextEntry(ze); - out = new BufferedWriter(new OutputStreamWriter(outs)); - } - catch (IOException e) - { - return false; - } - - po("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"); - po("<office:document-meta\n"); - po(" xmlns:office=\"urn:oasis:names:tc:opendocument:xmlns:office:1.0\"\n"); - po(" xmlns:xlink=\"http://www.w3.org/1999/xlink\"\n"); - po(" xmlns:dc=\"http://purl.org/dc/elements/1.1/\"\n"); - po(" xmlns:meta=\"urn:oasis:names:tc:opendocument:xmlns:meta:1.0\"\n"); - po(" xmlns:presentation=\"urn:oasis:names:tc:opendocument:xmlns:presentation:1.0\"\n"); - po(" xmlns:ooo=\"http://openoffice.org/2004/office\"\n"); - po(" xmlns:smil=\"urn:oasis:names:tc:opendocument:xmlns:smil-compatible:1.0\"\n"); - po(" xmlns:anim=\"urn:oasis:names:tc:opendocument:xmlns:animation:1.0\"\n"); - po(" office:version=\"1.0\">\n"); - po("<office:meta>\n"); - po(" <meta:generator>Inkscape-0.43</meta:generator>\n"); - po(" <meta:initial-creator>clark kent</meta:initial-creator>\n"); - po(" <meta:creation-date>2005-12-10T10:55:13</meta:creation-date>\n"); - po(" <dc:creator>clark kent</dc:creator>\n"); - po(" <dc:date>2005-12-10T10:56:20</dc:date>\n"); - po(" <dc:language>en-US</dc:language>\n"); - po(" <meta:editing-cycles>2</meta:editing-cycles>\n"); - po(" <meta:editing-duration>PT1M13S</meta:editing-duration>\n"); - po(" <meta:user-defined meta:name=\"Info 1\"/>\n"); - po(" <meta:user-defined meta:name=\"Info 2\"/>\n"); - po(" <meta:user-defined meta:name=\"Info 3\"/>\n"); - po(" <meta:user-defined meta:name=\"Info 4\"/>\n"); - po(" <meta:document-statistic meta:object-count=\"2\"/>\n"); - po("</office:meta>\n"); - po("</office:document-meta>\n"); - - - try - { - out.flush(); - outs.closeEntry(); - } - catch (IOException e) - { - err("writeOdfContent:" + e); - return false; - } - return true; -} - - -boolean writeOdfManifest(ZipOutputStream outs) -{ - try - { - ZipEntry ze = new ZipEntry("META-INF/manifest.xml"); - outs.putNextEntry(ze); - out = new BufferedWriter(new OutputStreamWriter(outs)); - } - catch (IOException e) - { - return false; - } - - po("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"); - po("<!DOCTYPE manifest:manifest PUBLIC \"-//OpenOffice.org//DTD Manifest 1.0//EN\" \"Manifest.dtd\">\n"); - po("<manifest:manifest xmlns:manifest=\"urn:oasis:names:tc:opendocument:xmlns:manifest:1.0\">\n"); - po(" <manifest:file-entry manifest:media-type=\"application/vnd.oasis.opendocument.graphics\" manifest:full-path=\"/\"/>\n"); - po(" <manifest:file-entry manifest:media-type=\"text/xml\" manifest:full-path=\"content.xml\"/>\n"); - po(" <manifest:file-entry manifest:media-type=\"text/xml\" manifest:full-path=\"meta.xml\"/>\n"); - po(" <!--List our images here-->\n"); - for (int i=0 ; i<images.size() ; i++) - { - ImageInfo ie = images.get(i); - String fname = ie.getName(); - if (fname.length() < 5) - { - err("image file name too short:" + fname); - return false; - } - String ext = fname.substring(fname.length() - 4); - po(" <manifest:file-entry manifest:media-type=\""); - if (ext.equals(".gif")) - po("image/gif"); - else if (ext.equals(".png")) - po("image/png"); - else if (ext.equals(".jpg") || ext.equals(".jpeg")) - po("image/jpeg"); - po("\" manifest:full-path=\""); - po(fname); - po("\"/>\n"); - } - po("</manifest:manifest>\n"); - - try - { - out.flush(); - outs.closeEntry(); - } - catch (IOException e) - { - err("writeOdfContent:" + e); - return false; - } - return true; -} - -boolean writeOdfImages(ZipOutputStream outs) -{ - for (int i=0 ; i<images.size() ; i++) - { - ImageInfo ie = images.get(i); - try - { - String iname = "Pictures/" + ie.getName(); - ZipEntry ze = new ZipEntry(iname); - outs.putNextEntry(ze); - outs.write(ie.getBuf()); - outs.closeEntry(); - } - catch (IOException e) - { - err("writing images:" + e); - return false; - } - } - return true; -} - -/** - * - */ -public boolean writeOdf(OutputStream outs) -{ - try - { - ZipOutputStream zos = new ZipOutputStream(outs); - if (!writeOdfContent(zos)) - return false; - if (!writeOdfManifest(zos)) - return false; - if (!writeOdfMeta(zos)) - return false; - if (!writeOdfImages(zos)) - return false; - //if (!writeOdfStyles(zos)) - // return false; - zos.close(); - } - catch (IOException e) - { - err("closing ODF zip output stream:" + e); - return false; - } - return true; -} - - - -/** - * - */ -public boolean saveOdf(String fileName) -{ - boolean ret = true; - try - { - FileOutputStream fos = new FileOutputStream(fileName); - ret = writeOdf(fos); - fos.close(); - } - catch (IOException e) - { - err("writing odf " + fileName + " : " + e); - return false; - } - return ret; -} - - - -boolean parseCss(String css) -{ - trace("##### STYLE ### :" + css); - String name = "gr" + styleNr; - styleNr++; - StyleInfo si = new StyleInfo(name, css); - StringTokenizer st = new StringTokenizer(css, ";"); - while (st.hasMoreTokens()) - { - String style = st.nextToken(); - //trace(" " + style); - int pos = style.indexOf(':'); - if (pos < 1 || pos > style.length()-2) - continue; - String attrName = style.substring(0, pos); - String attrVal = style.substring(pos+1); - trace(" =" + attrName + ':' + attrVal); - if ("stroke".equals(attrName)) - { - si.stroke = "solid"; - si.strokeColor = attrVal; - } - else if ("stroke-width".equals(attrName)) - { - si.strokeWidth = attrVal; - } - else if ("fill".equals(attrName)) - { - si.fill = "solid"; - si.fillColor = attrVal; - } - } - styles.put(css, si); - return true; -} - -boolean readSvg(InputStream ins) -{ - //### LOAD XML - try - { - DocumentBuilderFactory factory = - DocumentBuilderFactory.newInstance(); - factory.setNamespaceAware(true); - DocumentBuilder builder = - factory.newDocumentBuilder(); - builder.setEntityResolver(new EntityResolver() - { - public InputSource resolveEntity(String publicId, String systemId) - { - return new InputSource(new ByteArrayInputStream(new byte[0])); - } - }); - Document doc = builder.parse(ins); - svg = doc; - } - catch (javax.xml.parsers.ParserConfigurationException e) - { - err("making DOM parser:"+e); - return false; - } - catch (org.xml.sax.SAXException e) - { - err("parsing svg document:"+e); - return false; - } - catch (IOException e) - { - err("parsing svg document:"+e); - return false; - } - //dumpDocument(svg); - - //### LOAD IMAGES - imageNr = 0; - images = new ArrayList<ImageInfo>(); - NodeList res = svg.getElementsByTagNameNS(SVG_NS, "image"); - for (int i=0 ; i<res.getLength() ; i++) - { - Element elem = (Element) res.item(i); - String fileName = elem.getAttributeNS(XLINK_NS, "href"); - if (fileName == null) - { - err("No xlink:href pointer to image data for image"); - return false; - } - File f = new File(fileName); - if (!f.exists()) - { - err("image '" + fileName + "' does not exist"); - return false; - } - int bufSize = (int)f.length(); - byte buf[] = new byte[bufSize]; - int pos = 0; - try - { - FileInputStream fis = new FileInputStream(fileName); - while (pos < bufSize) - { - int len = fis.read(buf, pos, bufSize - pos); - if (len < 0) - break; - pos += len; - } - fis.close(); - } - catch (IOException e) - { - err("reading image '" + fileName + "' :" + e); - return false; - } - if (pos != bufSize) - { - err("reading image entry. expected " + - bufSize + ", found " + pos + " bytes."); - return false; - } - ImageInfo ie = new ImageInfo(fileName, fileName, buf); - images.add(ie); - } - - //### Parse styles - styleNr = 0; - styles = new HashMap<String, StyleInfo>(); - res = svg.getElementsByTagName("*"); - for (int i=0 ; i<res.getLength() ; i++) - { - Element elem = (Element) res.item(i); - trace("elem:"+ elem.getNodeName()); - String style = elem.getAttribute("style"); - if (style != null && style.length() > 0) - parseCss(style); - } - - return true; -} - -boolean readSvg(String fileName) -{ - try - { - FileInputStream fis = new FileInputStream(fileName); - if (!readSvg(fis)) - return false; - fis.close(); - } - catch (IOException e) - { - err("opening svg file:"+e); - return false; - } - return true; -} - - -//######################################################################## -//# O D F T O S V G -//######################################################################## - -/** - * - */ -public boolean readOdfEntry(ZipInputStream zis, ZipEntry ent) -{ - String fileName = ent.getName(); - trace("fileName:" + fileName); - if (fileName.length() < 4) - return true; - String ext = fileName.substring(fileName.length() - 4); - trace("ext:" + ext); - ArrayList<Byte> arr = new ArrayList<Byte>(); - try - { - while (true) - { - int inb = zis.read(); - if (inb < 0) - break; - arr.add((byte)inb); - } - } - catch (IOException e) - { - return false; - } - byte buf[] = new byte[arr.size()]; - for (int i=0 ; i<buf.length ; i++) - buf[i] = arr.get(i); - trace("bufsize:" + buf.length); - - if (ext.equals(".xml")) - { - try - { - DocumentBuilderFactory factory = - DocumentBuilderFactory.newInstance(); - factory.setNamespaceAware(true); - DocumentBuilder builder = - factory.newDocumentBuilder(); - builder.setEntityResolver(new EntityResolver() - { - public InputSource resolveEntity(String publicId, String systemId) - { - return new InputSource(new ByteArrayInputStream(new byte[0])); - } - }); - //trace("doc:"+new String(buf)); - Document doc = builder.parse(new ByteArrayInputStream(buf)); - if (fileName.equals("content.xml")) - content = doc; - else if (fileName.equals("meta.xml")) - meta = doc; - else if (fileName.equals("styles.xml")) - { - //styles = doc; - } - } - catch (javax.xml.parsers.ParserConfigurationException e) - { - err("making DOM parser:"+e); - return false; - } - catch (org.xml.sax.SAXException e) - { - err("parsing document:"+e); - return false; - } - catch (IOException e) - { - err("parsing document:"+e); - return false; - } - } - else if (ext.equals(".png") || - ext.equals(".gif") || - ext.equals(".jpg") || - ext.equals(".jpeg") ) - { - String imageName = "image" + imageNr + ext; - imageNr++; - ImageInfo ie = new ImageInfo(fileName, imageName, buf); - trace("added image '" + imageName + "'. " + buf.length +" bytes."); - images.add(ie); - - } - dumpDocument(content); - return true; -} - - -/** - * - */ -public boolean readOdf(InputStream ins) -{ - imageNr = 0; - images = new ArrayList<ImageInfo>(); - styleNr = 0; - styles = new HashMap<String, StyleInfo>(); - ZipInputStream zis = new ZipInputStream(ins); - while (true) - { - try - { - ZipEntry ent = zis.getNextEntry(); - if (ent == null) - break; - if (!readOdfEntry(zis, ent)) - return false; - zis.closeEntry(); - } - catch (IOException e) - { - err("reading zip entry"); - return false; - } - } - - return true; -} - - -/** - * - */ -public boolean readOdf(String fileName) -{ - boolean ret = true; - try - { - FileInputStream fis = new FileInputStream(fileName); - ret = readOdf(fis); - fis.close(); - } - catch (IOException e) - { - err("reading " + fileName + " : " + e); - ret = false; - } - return true; -} - - - - -public boolean writeSvgElement(Element elem) -{ - String ns = elem.getNamespaceURI(); - String tagName = elem.getLocalName(); - trace("tag:" + tagName + " : " + ns); - if (ns.equals(ODSVG_NS)) - { - po("<"); po(tagName); - NamedNodeMap attrs = elem.getAttributes(); - for (int i=0 ; i<attrs.getLength() ; i++) - { - Attr attr = (Attr)attrs.item(i); - String aname = attr.getName(); - String aval = attr.getValue(); - //Replace image name - if ("xlink:href".equals(aname) && "image".equals(tagName)) - { - for (int j=0 ; j<images.size() ; j++) - { - ImageInfo ie = images.get(i); - if (aval.equals(ie.getName())) - aval = ie.getNewName(); - } - } - po(" "); - po(aname); - po("=\""); - po(aval); - po("\""); - } - po(">\n"); - } - NodeList children = elem.getChildNodes(); - for (int i=0 ; i<children.getLength() ; i++) - { - Node n = children.item(i); - if (n.getNodeType() == Node.ELEMENT_NODE) - if (!writeSvgElement((Element)n)) - return false; - } - if (ns.equals(ODSVG_NS)) - { - po("</"); po(tagName); po(">\n"); - } - return true; -} - - -public boolean saveSvg(String svgFileName) -{ - trace("====== Saving images ==========="); - try - { - for (int i=0 ; i<images.size() ; i++) - { - ImageInfo entry = images.get(i); - trace("saving:" + entry.name); - FileOutputStream fos = new FileOutputStream(entry.name); - fos.write(entry.buf); - fos.close(); - } - } - catch (IOException e) - { - err("saveAsSVG:" + e); - return false; - } - - try - { - out = new BufferedWriter(new FileWriter(svgFileName)); - } - catch (IOException e) - { - err("save:" + e); - return false; - } - - if (content == null) - { - err("no content in odf"); - return false; - } - - NodeList res = content.getElementsByTagNameNS(ODF_NS, "drawing"); - if (res.getLength() < 1) - { - err("save: no drawing in document"); - return false; - } - Element root = (Element)res.item(0); - trace("NS:"+root.getNamespaceURI()); - - res = root.getElementsByTagNameNS(ODG_NS, "page"); - if (res.getLength() < 1) - { - err("save: no page in drawing"); - return false; - } - Element page = (Element)res.item(0); - - po("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"); - po("<svg\n"); - po(" xmlns:svg=\"http://www.w3.org/2000/svg\"\n"); - po(" xmlns=\"http://www.w3.org/2000/svg\"\n"); - po(" xmlns:xlink=\"http://www.w3.org/1999/xlink\"\n"); - po(" version=\"1.0\"\n"); - po(" >\n"); - - writeSvgElement(page); - - po("</svg>\n"); - - try - { - out.close(); - } - catch (IOException e) - { - err("save:close:" + e); - return false; - } - return true; -} - - - -//######################################################################## -//# C O N S T R U C T O R -//######################################################################## - -SvgOdg() -{ - //init, if necessary - nrfmt = new DecimalFormat("#.####"); -} - - -//######################################################################## -//# C O M M A N D L I N E -//######################################################################## - -public boolean odfToSvg(String odfName, String svgName) -{ - if (!readOdf(odfName)) - return false; - if (!saveSvg(svgName)) - return false; - return true; -} - -public boolean svgToOdf(String svgName, String odfName) -{ - if (!readSvg(svgName)) - return false; - System.out.println("ok"); - if (!saveOdf(odfName)) - return false; - return true; -} - -void usage() -{ - System.out.println("usage: SvgOdf input_file.odg output_file.svg"); - System.out.println(" SvgOdf input_file.svg output_file.odg"); -} - - -boolean parseArguments(String argv[]) -{ - if (argv.length != 2) - { - usage(); - return false; - } - - String fileName1 = argv[0]; - String fileName2 = argv[1]; - - if (fileName1.length()<5 || fileName2.length()<5) - { - System.out.println("one or more file names is too short"); - usage(); - return false; - } - - String ext1 = fileName1.substring(fileName1.length()-4); - String ext2 = fileName2.substring(fileName2.length()-4); - //System.out.println("ext1:"+ext1+" ext2:"+ext2); - - //##### ODG -> SVG ##### - if ((ext1.equals(".odg") || ext1.equals(".odf") || ext1.equals(".zip") ) && - ext2.equals(".svg")) - { - if (!odfToSvg(argv[0], argv[1])) - { - System.out.println("Conversion from ODG to SVG failed"); - return false; - } - } - - //##### SVG -> ODG ##### - else if (ext1.equals(".svg") && - ( ext2.equals(".odg") || ext2.equals(".odf") || ext2.equals(".zip") ) ) - { - if (!svgToOdf(fileName1, fileName2)) - { - System.out.println("Conversion from SVG to ODG failed"); - return false; - } - } - - //##### none of the above ##### - else - { - usage(); - return false; - } - return true; -} - - -public static void main(String argv[]) -{ - SvgOdg svgodg = new SvgOdg(); - svgodg.parseArguments(argv); -} - - -} - -//######################################################################## -//# E N D O F F I L E -//######################################################################## - diff --git a/src/doxygen-main.cpp b/src/doxygen-main.cpp index fd8f4bb1a..c6a6bb3ab 100644 --- a/src/doxygen-main.cpp +++ b/src/doxygen-main.cpp @@ -224,7 +224,7 @@ namespace XML {} * - SPEllipse * - SPLine [\ref sp-line.cpp, \ref sp-line.h] * - SPOffset [\ref sp-offset.cpp, \ref sp-offset.h] - * - SPPath [\ref sp-path.cpp, \ref sp-path.h, \ref path-chemistry.cpp, \ref nodepath.cpp, \ref nodepath.h, \ref splivarot.cpp] + * - SPPath [\ref sp-path.cpp, \ref sp-path.h, \ref path-chemistry.cpp, \ref splivarot.cpp] * - SPPolygon [\ref sp-polygon.cpp, \ref sp-polygon.h] * - SPStar [\ref sp-star.cpp, \ref sp-star.h] * - SPPolyLine [\ref sp-polyline.cpp, \ref sp-polyline.h] diff --git a/src/event-context.cpp b/src/event-context.cpp index 918e3d419..363f9fe71 100644 --- a/src/event-context.cpp +++ b/src/event-context.cpp @@ -56,8 +56,8 @@ #include "attributes.h" #include "rubberband.h" #include "selcue.h" -#include "node-context.h" #include "lpe-tool-context.h" +#include "ui/tool/control-point.h" static void sp_event_context_class_init(SPEventContextClass *klass); static void sp_event_context_init(SPEventContext *event_context); @@ -1277,6 +1277,13 @@ gboolean sp_event_context_snap_watchdog_callback(gpointer data) } } break; + case DelayedSnapEvent::CONTROL_POINT_HANDLER: + { + using Inkscape::UI::ControlPoint; + ControlPoint *point = reinterpret_cast<ControlPoint*>(dse->getKnot()); + point->_eventHandler(dse->getEvent()); + } + break; default: g_warning("Origin of snap-delay event has not been defined!;"); break; diff --git a/src/event-context.h b/src/event-context.h index 5285bdb87..5be2e19fb 100644 --- a/src/event-context.h +++ b/src/event-context.h @@ -49,7 +49,8 @@ public: UNDEFINED_HANDLER = 0, EVENTCONTEXT_ROOT_HANDLER, EVENTCONTEXT_ITEM_HANDLER, - KNOT_HANDLER + KNOT_HANDLER, + CONTROL_POINT_HANDLER }; DelayedSnapEvent(SPEventContext *event_context, SPItem* const item, SPKnot* knot, GdkEventMotion const *event, DelayedSnapEvent::DelayedSnapEventOrigin const origin) diff --git a/src/extension/dxf2svg/entities2elements.cpp b/src/extension/dxf2svg/entities2elements.cpp index ab160e265..f280bb2a8 100644 --- a/src/extension/dxf2svg/entities2elements.cpp +++ b/src/extension/dxf2svg/entities2elements.cpp @@ -20,10 +20,12 @@ SoC 2005 */ -#include"entities2elements.h" -#include"tables2svg_info.h" -#include<iostream> -#include<math.h> +#include "entities2elements.h" +#include "tables2svg_info.h" +#include <iostream> +#include <math.h> +#include <string.h> +#include <stdlib.h> // The names indicate the DXF entitiy first and the SVG element last // Common elements diff --git a/src/extension/dxf2svg/read_dxf.cpp b/src/extension/dxf2svg/read_dxf.cpp index 8a6a6d6ac..ecda343c6 100644 --- a/src/extension/dxf2svg/read_dxf.cpp +++ b/src/extension/dxf2svg/read_dxf.cpp @@ -11,11 +11,13 @@ -#include<fstream> -#include<string> -#include"read_dxf.h" +#include <fstream> +#include <string> +#include "read_dxf.h" -#include<iostream> +#include <iostream> +#include <string.h> +#include <stdlib.h> using namespace std; diff --git a/src/extension/dxf2svg/tables2svg_info.cpp b/src/extension/dxf2svg/tables2svg_info.cpp index 17bc47beb..3b27a9c38 100644 --- a/src/extension/dxf2svg/tables2svg_info.cpp +++ b/src/extension/dxf2svg/tables2svg_info.cpp @@ -10,9 +10,11 @@ */ -#include"tables2svg_info.h" -#include<math.h> -#include<iostream> +#include "tables2svg_info.h" +#include <math.h> +#include <iostream> +#include <stdlib.h> +#include <string.h> char* pattern2dasharray(ltype info, int precision, double scaling, char* out){ std::vector< double > pattern = info.ret_pattern(); diff --git a/src/extension/internal/cairo-renderer.cpp b/src/extension/internal/cairo-renderer.cpp index 8cc386135..0e68ae130 100644 --- a/src/extension/internal/cairo-renderer.cpp +++ b/src/extension/internal/cairo-renderer.cpp @@ -29,6 +29,7 @@ #include <errno.h> #include "libnr/nr-rect.h" +#include "libnrtype/Layout-TNG.h" #include <2geom/transforms.h> #include <2geom/pathvector.h> diff --git a/src/gc-allocator.h b/src/gc-allocator.h index 4d809cfe4..e69de29bb 100644 --- a/src/gc-allocator.h +++ b/src/gc-allocator.h @@ -1,106 +0,0 @@ -/** @file - * @brief Garbage-collected STL allocator for standard containers - */ -/* Authors: - * Krzysztof Kosiński <tweenk.pl@gmail.com> - * - * Copyright 2008 Authors - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * See the file COPYING for details. - */ - -#ifndef SEEN_INKSCAPE_GC_ALLOCATOR_H -#define SEEN_INKSCAPE_GC_ALLOCATOR_H - -#include <cstddef> -#include <limits> -#include "gc-core.h" - -namespace Inkscape { -namespace GC { - -/** - * @brief Garbage-collected allocator for the standard containers - * - * STL containers with default parameters cannot be used as members in garbage-collected - * objects, because by default the destructors are not called, causing a memory leak - * (the memory allocated by the container is not freed). To address this, STL containers - * can be told to use this garbage-collected allocator. It usually is the last template - * parameter. For example, to define a GC-managed map of ints to Unicode strings: - * - * @code typedef std::map<int, Glib::ustring, less<int>, Inkscape::GC::Allocator> gcmap; @endcode - * - * Afterwards, you can place gcmap as a member in a non-finalized GC-managed object, because - * all memory used by gcmap will also be reclaimable by the garbage collector, therefore - * avoiding memory leaks. - */ -template <typename T> -class Allocator { - // required typedefs - typedef T value_type; - typedef size_t size_type; - typedef ptrdiff_t difference_type; - typedef T * pointer; - typedef T const * const_pointer; - typedef T & reference; - typedef T const & const_reference; - - // required structure that allows accessing the same allocator for a different type - template <typename U> - struct rebind { - typedef Allocator<U> other; - }; - - // constructors - no-ops since the allocator doesn't have any state - Allocator() throw() {} - Allocator(Allocator const &) throw() {} - template <typename U> Allocator(Allocator<U> const &) throw() {} - ~Allocator() throw() {} - - // trivial required methods - pointer address(reference ref) { return &ref; } - const_pointer address(const_reference ref) { return &ref; } - void construct(pointer p, T const &value) { new (static_cast<void*>(p)) T(value); } - void destroy(pointer p) { p->~T(); } - - // maximum meaningful memory amount that can be requested from the allocator - size_type max_size() { - return numeric_limits<size_type>::max() / sizeof(T); - } - - // allocate memory for num elements without initializing them - pointer allocate(size_type num, Allocator<void>::const_pointer) { - return static_cast<pointer>( Inkscape::GC::Core::malloc(num * sizeof(T)) ); - } - - // deallocate memory at p - void deallocate(pointer p, size_type) { - Inkscape::GC::Core::free(p); - } -}; - -// required comparison operators -template <typename T1, typename T2> -bool operator==(Allocator<T1> const &, Allocator<T2> const &) { return true; } -template <typename T1, typename T2> -bool operator!=(Allocator<T1> const &, Allocator<T2> const &) { return false; } - -} // namespace GC -} // namespace Inkscape - -#endif // !SEEN_INKSCAPE_GC_ALLOCATOR_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/libnrtype/FontFactory.cpp b/src/libnrtype/FontFactory.cpp index 1f85ee5ca..a63f70d75 100644 --- a/src/libnrtype/FontFactory.cpp +++ b/src/libnrtype/FontFactory.cpp @@ -26,10 +26,10 @@ /* Freetype2 */ # include <pango/pangoft2.h> -#include <ext/hash_map> +#include <tr1/unordered_map> -typedef __gnu_cxx::hash_map<PangoFontDescription*, font_instance*, font_descr_hash, font_descr_equal> FaceMapType; +typedef std::tr1::unordered_map<PangoFontDescription*, font_instance*, font_descr_hash, font_descr_equal> FaceMapType; // need to avoid using the size field size_t font_descr_hash::operator()( PangoFontDescription *const &x) const { @@ -47,7 +47,7 @@ size_t font_descr_hash::operator()( PangoFontDescription *const &x) const { h += (int)pango_font_description_get_stretch(x); return h; } -bool font_descr_equal::operator()( PangoFontDescription *const&a, PangoFontDescription *const &b) { +bool font_descr_equal::operator()( PangoFontDescription *const&a, PangoFontDescription *const &b) const { //if ( pango_font_description_equal(a,b) ) return true; char const *fa = pango_font_description_get_family(a); char const *fb = pango_font_description_get_family(b); diff --git a/src/libnrtype/FontFactory.h b/src/libnrtype/FontFactory.h index 8d85bcf3e..0118c862d 100644 --- a/src/libnrtype/FontFactory.h +++ b/src/libnrtype/FontFactory.h @@ -45,7 +45,7 @@ struct font_descr_hash : public std::unary_function<PangoFontDescription*,size_t size_t operator()(PangoFontDescription *const &x) const; }; struct font_descr_equal : public std::binary_function<PangoFontDescription*, PangoFontDescription*, bool> { - bool operator()(PangoFontDescription *const &a, PangoFontDescription *const &b); + bool operator()(PangoFontDescription *const &a, PangoFontDescription *const &b) const; }; // Comparison functions for style names diff --git a/src/libnrtype/FontInstance.cpp b/src/libnrtype/FontInstance.cpp index f34a230c1..6b0725b34 100644 --- a/src/libnrtype/FontInstance.cpp +++ b/src/libnrtype/FontInstance.cpp @@ -29,7 +29,7 @@ # include FT_TRUETYPE_TABLES_H # include <pango/pangoft2.h> -#include <ext/hash_map> +#include <tr1/unordered_map> // the various raster_font in use at a given time are held in a hash_map whose indices are the @@ -39,11 +39,11 @@ struct font_style_hash : public std::unary_function<font_style, size_t> { }; struct font_style_equal : public std::binary_function<font_style, font_style, bool> { - bool operator()(font_style const &a, font_style const &b); + bool operator()(font_style const &a, font_style const &b) const; }; -typedef __gnu_cxx::hash_map<font_style, raster_font*, font_style_hash, font_style_equal> StyleMap; +typedef std::tr1::unordered_map<font_style, raster_font*, font_style_hash, font_style_equal> StyleMap; @@ -76,7 +76,7 @@ size_t font_style_hash::operator()(const font_style &x) const { return h; } -bool font_style_equal::operator()(const font_style &a,const font_style &b) { +bool font_style_equal::operator()(const font_style &a,const font_style &b) const { for (int i=0;i<6;i++) { if ( (int)(100*a.transform[i]) != (int)(100*b.transform[i]) ) return false; } diff --git a/src/live_effects/effect.cpp b/src/live_effects/effect.cpp index 9232792f6..d53048c76 100644 --- a/src/live_effects/effect.cpp +++ b/src/live_effects/effect.cpp @@ -28,7 +28,6 @@ #include "tools-switch.h" #include "message-stack.h" #include "desktop.h" -#include "nodepath.h" #include "knotholder.h" #include "live_effects/lpeobject.h" @@ -665,13 +664,6 @@ Effect::resetDefaults(SPItem * /*item*/) } void -Effect::setup_nodepath(Inkscape::NodePath::Path *np) -{ - np->helperpath_rgba = 0xff0000ff; - np->helperpath_width = 1.0; -} - -void Effect::transform_multiply(Geom::Matrix const& postmul, bool set) { // cycle through all parameters. Most parameters will not need transformation, but path and point params do. diff --git a/src/live_effects/effect.h b/src/live_effects/effect.h index 5d67ed016..a8d34a233 100644 --- a/src/live_effects/effect.h +++ b/src/live_effects/effect.h @@ -92,8 +92,6 @@ public: */ virtual void resetDefaults(SPItem * item); - virtual void setup_nodepath(Inkscape::NodePath::Path *np); - /// /todo: is this method really necessary? it causes UI inconsistensies... (johan) virtual void transform_multiply(Geom::Matrix const& postmul, bool set); diff --git a/src/live_effects/lpe-constructgrid.cpp b/src/live_effects/lpe-constructgrid.cpp index 144f4720d..4725573d7 100644 --- a/src/live_effects/lpe-constructgrid.cpp +++ b/src/live_effects/lpe-constructgrid.cpp @@ -16,8 +16,6 @@ #include <2geom/path.h> #include <2geom/transforms.h> -#include "nodepath.h" - namespace Inkscape { namespace LivePathEffect { @@ -81,13 +79,6 @@ LPEConstructGrid::doEffect_path (std::vector<Geom::Path> const & path_in) } } -void -LPEConstructGrid::setup_nodepath(Inkscape::NodePath::Path *np) -{ - Effect::setup_nodepath(np); - sp_nodepath_make_straight_path(np); -} - } //namespace LivePathEffect } /* namespace Inkscape */ diff --git a/src/live_effects/lpe-constructgrid.h b/src/live_effects/lpe-constructgrid.h index 716960d32..c7e695794 100644 --- a/src/live_effects/lpe-constructgrid.h +++ b/src/live_effects/lpe-constructgrid.h @@ -27,8 +27,6 @@ public: virtual std::vector<Geom::Path> doEffect_path (std::vector<Geom::Path> const & path_in); - virtual void setup_nodepath(Inkscape::NodePath::Path *np); - private: ScalarParam nr_x; ScalarParam nr_y; diff --git a/src/live_effects/lpe-gears.cpp b/src/live_effects/lpe-gears.cpp index e211483c6..16eb19f44 100644 --- a/src/live_effects/lpe-gears.cpp +++ b/src/live_effects/lpe-gears.cpp @@ -14,8 +14,6 @@ #include <2geom/bezier-to-sbasis.h> #include <2geom/path.h> -#include "nodepath.h" - using std::vector; using namespace Geom; @@ -261,13 +259,6 @@ LPEGears::doEffect_path (std::vector<Geom::Path> const & path_in) return path_out; } -void -LPEGears::setup_nodepath(Inkscape::NodePath::Path *np) -{ - Effect::setup_nodepath(np); - sp_nodepath_make_straight_path(np); -} - } // namespace LivePathEffect } /* namespace Inkscape */ diff --git a/src/live_effects/lpe-gears.h b/src/live_effects/lpe-gears.h index 4c3a9938b..bd5e4c4f9 100644 --- a/src/live_effects/lpe-gears.h +++ b/src/live_effects/lpe-gears.h @@ -24,8 +24,6 @@ public: virtual std::vector<Geom::Path> doEffect_path (std::vector<Geom::Path> const & path_in); - virtual void setup_nodepath(Inkscape::NodePath::Path *np); - private: ScalarParam teeth; ScalarParam phi; diff --git a/src/live_effects/lpe-lattice.cpp b/src/live_effects/lpe-lattice.cpp index 0beedb537..50ecdf04b 100644 --- a/src/live_effects/lpe-lattice.cpp +++ b/src/live_effects/lpe-lattice.cpp @@ -22,7 +22,6 @@ #include "sp-path.h" #include "display/curve.h" #include "svg/svg.h" -#include "nodepath.h" #include <2geom/sbasis.h> #include <2geom/sbasis-2d.h> diff --git a/src/live_effects/lpe-spiro.cpp b/src/live_effects/lpe-spiro.cpp index 794fd980e..7c8262af6 100644 --- a/src/live_effects/lpe-spiro.cpp +++ b/src/live_effects/lpe-spiro.cpp @@ -7,7 +7,6 @@ #include "live_effects/lpe-spiro.h" #include "display/curve.h" -#include "nodepath.h" #include <typeinfo> #include <2geom/pathvector.h> #include <2geom/matrix.h> @@ -116,14 +115,6 @@ LPESpiro::~LPESpiro() } void -LPESpiro::setup_nodepath(Inkscape::NodePath::Path *np) -{ - Effect::setup_nodepath(np); - sp_nodepath_show_handles(np, false); -// sp_nodepath_show_helperpath(np, false); -} - -void LPESpiro::doEffect(SPCurve * curve) { using Geom::X; diff --git a/src/live_effects/lpe-spiro.h b/src/live_effects/lpe-spiro.h index 7256665a2..4fcd9eaaa 100644 --- a/src/live_effects/lpe-spiro.h +++ b/src/live_effects/lpe-spiro.h @@ -24,7 +24,6 @@ public: virtual LPEPathFlashType pathFlashType() { return SUPPRESS_FLASH; } - virtual void setup_nodepath(Inkscape::NodePath::Path *np); virtual void doEffect(SPCurve * curve); private: diff --git a/src/live_effects/lpe-vonkoch.cpp b/src/live_effects/lpe-vonkoch.cpp index 7fd0ac0b4..85f8cde0c 100644 --- a/src/live_effects/lpe-vonkoch.cpp +++ b/src/live_effects/lpe-vonkoch.cpp @@ -8,7 +8,6 @@ #include <cstdio> #include "live_effects/lpe-vonkoch.h" -#include "nodepath.h" #include <2geom/transforms.h> //using std::vector; @@ -19,7 +18,7 @@ void VonKochPathParam::param_setup_nodepath(Inkscape::NodePath::Path *np) { PathParam::param_setup_nodepath(np); - sp_nodepath_make_straight_path(np); + //sp_nodepath_make_straight_path(np); } //FIXME: a path is used here instead of 2 points to work around path/point param incompatibility bug. @@ -27,12 +26,12 @@ void VonKochRefPathParam::param_setup_nodepath(Inkscape::NodePath::Path *np) { PathParam::param_setup_nodepath(np); - sp_nodepath_make_straight_path(np); + //sp_nodepath_make_straight_path(np); } bool VonKochRefPathParam::param_readSVGValue(const gchar * strvalue) { - std::vector<Geom::Path> old = _pathvector; + Geom::PathVector old = _pathvector; bool res = PathParam::param_readSVGValue(strvalue); if (res && _pathvector.size()==1 && _pathvector.front().size()==1){ return true; diff --git a/src/live_effects/parameter/path.cpp b/src/live_effects/parameter/path.cpp index 33e50155c..43f4b5725 100644 --- a/src/live_effects/parameter/path.cpp +++ b/src/live_effects/parameter/path.cpp @@ -28,10 +28,8 @@ // needed for on-canvas editting: #include "tools-switch.h" #include "shape-editor.h" -#include "node-context.h" #include "desktop-handles.h" #include "selection.h" -#include "nodepath.h" // clipboard support #include "ui/clipboard.h" // required for linking to other paths @@ -40,6 +38,10 @@ #include "sp-text.h" #include "display/curve.h" +#include "ui/tool/node-tool.h" +#include "ui/tool/multi-path-manipulator.h" +#include "ui/tool/shape-record.h" + namespace Inkscape { @@ -195,26 +197,33 @@ PathParam::param_newWidget(Gtk::Tooltips * tooltips) void PathParam::param_editOncanvas(SPItem * item, SPDesktop * dt) { - // If not already in nodecontext, goto it! + using namespace Inkscape::UI; + + // TODO remove the tools_switch atrocity. if (!tools_isactive(dt, TOOLS_NODES)) { tools_switch(dt, TOOLS_NODES); } - ShapeEditor * shape_editor = dt->event_context->shape_editor; + InkNodeTool *nt = static_cast<InkNodeTool*>(dt->event_context); + std::set<ShapeRecord> shapes; + ShapeRecord r; + + r.role = SHAPE_ROLE_LPE_PARAM; + r.edit_transform = Geom::identity(); // TODO this is almost certainly wrong if (!href) { - shape_editor->set_item_lpe_path_parameter(item, param_effect->getLPEObj(), param_key.c_str()); + r.item = reinterpret_cast<SPItem*>(param_effect->getLPEObj()); + r.lpe_key = param_key; } else { - // set referred item for editing - shape_editor->set_item(ref.getObject(), SH_NODEPATH); + r.item = ref.getObject(); } + shapes.insert(r); + nt->_multipath->setItems(shapes); } void -PathParam::param_setup_nodepath(Inkscape::NodePath::Path *np) -{ - np->show_helperpath = true; - np->helperpath_rgba = 0x009000ff; - np->helperpath_width = 1.0; +PathParam::param_setup_nodepath(Inkscape::NodePath::Path *) +{ + // TODO this method should not exist at all! } void diff --git a/src/lpe-tool-context.cpp b/src/lpe-tool-context.cpp index be465e324..7072f47b3 100644 --- a/src/lpe-tool-context.cpp +++ b/src/lpe-tool-context.cpp @@ -179,9 +179,6 @@ sp_lpetool_context_setup(SPEventContext *ec) } lc->_lpetool_message_context = new Inkscape::MessageContext((ec->desktop)->messageStack()); - - - lc->shape_editor->update_statusbar(); } /** @@ -193,13 +190,9 @@ sp_lpetool_context_selection_changed(Inkscape::Selection *selection, gpointer da { SPLPEToolContext *lc = SP_LPETOOL_CONTEXT(data); - // TODO: update ShapeEditorsCollective instead - lc->shape_editor->unset_item(SH_NODEPATH); lc->shape_editor->unset_item(SH_KNOTHOLDER); SPItem *item = selection->singleItem(); - lc->shape_editor->set_item(item, SH_NODEPATH); lc->shape_editor->set_item(item, SH_KNOTHOLDER); - lc->shape_editor->update_statusbar(); } static void @@ -280,7 +273,6 @@ sp_lpetool_context_root_handler(SPEventContext *event_context, GdkEvent *event) event_context->xp = (gint) event->button.x; event_context->yp = (gint) event->button.y; event_context->within_tolerance = true; - lc->shape_editor->cancel_hit(); using namespace Inkscape::LivePathEffect; @@ -296,28 +288,6 @@ sp_lpetool_context_root_handler(SPEventContext *event_context, GdkEvent *event) ret = ((SPEventContextClass *) lpetool_parent_class)->root_handler(event_context, event); } break; - case GDK_MOTION_NOTIFY: - { - if (!lc->shape_editor->has_nodepath() || selection->singleItem() == NULL) { - break; - } - - bool over_stroke = false; - over_stroke = lc->shape_editor->is_over_stroke(Geom::Point(event->motion.x, event->motion.y), false); - - if (over_stroke) { - event_context->cursor_shape = cursor_node_xpm; - event_context->hot_x = 1; - event_context->hot_y = 1; - sp_event_context_update_cursor(event_context); - } else { - lc->cursor_shape = cursor_crosshairs_xpm; - lc->hot_x = 7; - lc->hot_y = 7; - sp_event_context_update_cursor(event_context); - } - } - break; case GDK_BUTTON_RELEASE: diff --git a/src/node-context.cpp b/src/node-context.cpp deleted file mode 100644 index 7efa57290..000000000 --- a/src/node-context.cpp +++ /dev/null @@ -1,868 +0,0 @@ -#define __SP_NODE_CONTEXT_C__ - -/* - * Node editing context - * - * Authors: - * Lauris Kaplinski <lauris@kaplinski.com> - * bulia byak <buliabyak@users.sf.net> - * - * This code is in public domain - */ - -#ifdef HAVE_CONFIG_H -# include "config.h" -#endif -#include <cstring> -#include <string> -#include <gdk/gdkkeysyms.h> -#include "macros.h" -#include <glibmm/i18n.h> -#include "display/sp-canvas-util.h" -#include "object-edit.h" -#include "sp-path.h" -#include "path-chemistry.h" -#include "rubberband.h" -#include "desktop.h" -#include "desktop-handles.h" -#include "selection.h" -#include "pixmaps/cursor-node.xpm" -#include "message-context.h" -#include "node-context.h" -#include "pixmaps/cursor-node-d.xpm" -#include "preferences.h" -#include "xml/node-event-vector.h" -#include "style.h" -#include "splivarot.h" -#include "shape-editor.h" -#include "live_effects/effect.h" - -#include "sp-lpe-item.h" - -// needed for flash nodepath upon mouseover: -#include "display/canvas-bpath.h" -#include "display/curve.h" - -static void sp_node_context_class_init(SPNodeContextClass *klass); -static void sp_node_context_init(SPNodeContext *node_context); -static void sp_node_context_dispose(GObject *object); - -static void sp_node_context_setup(SPEventContext *ec); -static gint sp_node_context_root_handler(SPEventContext *event_context, GdkEvent *event); -static gint sp_node_context_item_handler(SPEventContext *event_context, - SPItem *item, GdkEvent *event); - -static SPEventContextClass *parent_class; - -GType -sp_node_context_get_type() -{ - static GType type = 0; - if (!type) { - GTypeInfo info = { - sizeof(SPNodeContextClass), - NULL, NULL, - (GClassInitFunc) sp_node_context_class_init, - NULL, NULL, - sizeof(SPNodeContext), - 4, - (GInstanceInitFunc) sp_node_context_init, - NULL, /* value_table */ - }; - type = g_type_register_static(SP_TYPE_EVENT_CONTEXT, "SPNodeContext", &info, (GTypeFlags)0); - } - return type; -} - -static void -sp_node_context_class_init(SPNodeContextClass *klass) -{ - GObjectClass *object_class = (GObjectClass *) klass; - SPEventContextClass *event_context_class = (SPEventContextClass *) klass; - - parent_class = (SPEventContextClass*)g_type_class_peek_parent(klass); - - object_class->dispose = sp_node_context_dispose; - - event_context_class->setup = sp_node_context_setup; - event_context_class->root_handler = sp_node_context_root_handler; - event_context_class->item_handler = sp_node_context_item_handler; -} - -static void -sp_node_context_init(SPNodeContext *node_context) -{ - SPEventContext *event_context = SP_EVENT_CONTEXT(node_context); - - event_context->cursor_shape = cursor_node_xpm; - event_context->hot_x = 1; - event_context->hot_y = 1; - - node_context->leftalt = FALSE; - node_context->rightalt = FALSE; - node_context->leftctrl = FALSE; - node_context->rightctrl = FALSE; - - new (&node_context->sel_changed_connection) sigc::connection(); - - node_context->flash_tempitem = NULL; - node_context->flashed_item = NULL; - node_context->remove_flash_counter = 0; -} - -static void -sp_node_context_dispose(GObject *object) -{ - SPNodeContext *nc = SP_NODE_CONTEXT(object); - SPEventContext *ec = SP_EVENT_CONTEXT(object); - - ec->enableGrDrag(false); - - if (nc->grabbed) { - sp_canvas_item_ungrab(nc->grabbed, GDK_CURRENT_TIME); - nc->grabbed = NULL; - } - - nc->sel_changed_connection.disconnect(); - nc->sel_changed_connection.~connection(); - - delete ec->shape_editor; - - if (nc->_node_message_context) { - delete nc->_node_message_context; - } - - G_OBJECT_CLASS(parent_class)->dispose(object); -} - -static void -sp_node_context_setup(SPEventContext *ec) -{ - SPNodeContext *nc = SP_NODE_CONTEXT(ec); - - if (((SPEventContextClass *) parent_class)->setup) - ((SPEventContextClass *) parent_class)->setup(ec); - - Inkscape::Selection *selection = sp_desktop_selection (ec->desktop); - nc->sel_changed_connection.disconnect(); - nc->sel_changed_connection = - selection->connectChanged(sigc::bind(sigc::ptr_fun(&sp_node_context_selection_changed), (gpointer)nc)); - - SPItem *item = selection->singleItem(); - - ec->shape_editor = new ShapeEditor(ec->desktop); - - nc->rb_escaped = false; - - nc->cursor_drag = false; - - nc->added_node = false; - - nc->current_state = SP_NODE_CONTEXT_INACTIVE; - - if (item) { - ec->shape_editor->set_item(item, SH_NODEPATH); - ec->shape_editor->set_item(item, SH_KNOTHOLDER); - } - - Inkscape::Preferences *prefs = Inkscape::Preferences::get(); - if (prefs->getBool("/tools/nodes/selcue")) { - ec->enableSelectionCue(); - } - if (prefs->getBool("/tools/nodes/gradientdrag")) { - ec->enableGrDrag(); - } - - ec->desktop->emitToolSubselectionChanged(NULL); // sets the coord entry fields to inactive - - nc->_node_message_context = new Inkscape::MessageContext((ec->desktop)->messageStack()); - - ec->shape_editor->update_statusbar(); -} - -static void -sp_node_context_flash_path(SPEventContext *event_context, SPItem *item, guint timeout) { - SPNodeContext *nc = SP_NODE_CONTEXT(event_context); - - nc->remove_flash_counter = 3; // for some reason root_handler is called twice after each item_handler... - if (nc->flashed_item != item) { - // we entered a new item - nc->flashed_item = item; - SPDesktop *desktop = event_context->desktop; - if (nc->flash_tempitem) { - desktop->remove_temporary_canvasitem(nc->flash_tempitem); - nc->flash_tempitem = NULL; - } - - SPCanvasItem *canvasitem = sp_nodepath_generate_helperpath(desktop, item); - - if (canvasitem) { - nc->flash_tempitem = desktop->add_temporary_canvasitem (canvasitem, timeout); - } - } -} - -/** -\brief Callback that processes the "changed" signal on the selection; -destroys old and creates new nodepath and reassigns listeners to the new selected item's repr -*/ -void -sp_node_context_selection_changed(Inkscape::Selection *selection, gpointer data) -{ - SPEventContext *ec = SP_EVENT_CONTEXT(data); - - // TODO: update ShapeEditorsCollective instead - ec->shape_editor->unset_item(SH_NODEPATH); - ec->shape_editor->unset_item(SH_KNOTHOLDER); - SPItem *item = selection->singleItem(); - ec->shape_editor->set_item(item, SH_NODEPATH); - ec->shape_editor->set_item(item, SH_KNOTHOLDER); - ec->shape_editor->update_statusbar(); -} - -void -sp_node_context_show_modifier_tip(SPEventContext *event_context, GdkEvent *event) -{ - sp_event_show_modifier_tip - (event_context->defaultMessageContext(), event, - _("<b>Ctrl</b>: toggle node type, snap handle angle, move hor/vert; <b>Ctrl+Alt</b>: move along handles"), - _("<b>Shift</b>: toggle node selection, disable snapping, rotate both handles"), - _("<b>Alt</b>: lock handle length; <b>Ctrl+Alt</b>: move along handles")); -} - -static gint -sp_node_context_item_handler(SPEventContext *event_context, SPItem *item, GdkEvent *event) -{ - gint ret = FALSE; - Inkscape::Preferences *prefs = Inkscape::Preferences::get(); - SPDesktop *desktop = event_context->desktop; - - switch (event->type) { - case GDK_MOTION_NOTIFY: - { - // find out actual item we're over, disregarding groups - SPItem *actual_item = sp_event_context_find_item (desktop, - Geom::Point(event->button.x, event->button.y), FALSE, TRUE); - if (!actual_item) - break; - - - if (prefs->getBool("/tools/nodes/pathflash_enabled")) { - if (prefs->getBool("/tools/nodes/pathflash_unselected")) { - // do not flash if we have some path selected and a single item in selection (i.e. it - // is the same path that we're editing) - SPDesktop *desktop = event_context->desktop; - ShapeEditor* se = event_context->shape_editor; - Inkscape::Selection *selection = sp_desktop_selection (desktop); - if (se->has_nodepath() && selection->singleItem()) { - break; - } - } - if (SP_IS_LPE_ITEM(actual_item)) { - Inkscape::LivePathEffect::Effect *lpe = sp_lpe_item_get_current_lpe(SP_LPE_ITEM(actual_item)); - if (lpe && (lpe->providesOwnFlashPaths() || - lpe->pathFlashType() == Inkscape::LivePathEffect::SUPPRESS_FLASH)) { - // path should be suppressed or permanent; this is handled in - // sp_node_context_selection_changed() - break; - } - } - guint timeout = prefs->getInt("/tools/nodes/pathflash_timeout", 500); - sp_node_context_flash_path(event_context, actual_item, timeout); - } - } - break; - - default: - break; - } - - if (((SPEventContextClass *) parent_class)->item_handler) - ret = ((SPEventContextClass *) parent_class)->item_handler(event_context, item, event); - - return ret; -} - -static gint -sp_node_context_root_handler(SPEventContext *event_context, GdkEvent *event) -{ - SPDesktop *desktop = event_context->desktop; - ShapeEditor* se = event_context->shape_editor; - Inkscape::Selection *selection = sp_desktop_selection (desktop); - Inkscape::Preferences *prefs = Inkscape::Preferences::get(); - - SPNodeContext *nc = SP_NODE_CONTEXT(event_context); - double const nudge = prefs->getDoubleLimited("/options/nudgedistance/value", 2, 0, 1000); // in px - event_context->tolerance = prefs->getIntLimited("/options/dragtolerance/value", 0, 0, 100); // read every time, to make prefs changes really live - int const snaps = prefs->getInt("/options/rotationsnapsperpi/value", 12); - double const offset = prefs->getDoubleLimited("/options/defaultscale/value", 2, 0, 1000); - - if ( (nc->flash_tempitem) && (nc->remove_flash_counter <= 0) ) { - desktop->remove_temporary_canvasitem(nc->flash_tempitem); - nc->flash_tempitem = NULL; - nc->flashed_item = NULL; // also reset this one, so the next time the same object is hovered over it shows again the highlight - } else { - nc->remove_flash_counter--; - } - - gint ret = FALSE; - switch (event->type) { - case GDK_BUTTON_PRESS: - if (event->button.button == 1 && !event_context->space_panning) { - // save drag origin - event_context->xp = (gint) event->button.x; - event_context->yp = (gint) event->button.y; - event_context->within_tolerance = true; - se->cancel_hit(); - - if (!(event->button.state & GDK_SHIFT_MASK)) { - if (!nc->drag) { - if (se->has_nodepath() && selection->single() /* && item_over */) { - // save drag origin - bool over_stroke = se->is_over_stroke(Geom::Point(event->button.x, event->button.y), true); - //only dragging curves - if (over_stroke) { - ret = TRUE; - break; - } - } - } - } - Geom::Point const button_w(event->button.x, - event->button.y); - Geom::Point const button_dt(desktop->w2d(button_w)); - Inkscape::Rubberband::get(desktop)->start(desktop, button_dt); - - if (nc->grabbed) { - sp_canvas_item_ungrab(nc->grabbed, event->button.time); - nc->grabbed = NULL; - } - - sp_canvas_item_grab(SP_CANVAS_ITEM(desktop->acetate), - GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK | GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK, - NULL, event->button.time); - nc->grabbed = SP_CANVAS_ITEM(desktop->acetate); - - nc->current_state = SP_NODE_CONTEXT_INACTIVE; - desktop->updateNow(); - ret = TRUE; - } - break; - case GDK_MOTION_NOTIFY: - if (event->motion.state & GDK_BUTTON1_MASK && !event_context->space_panning) { - - if ( event_context->within_tolerance - && ( abs( (gint) event->motion.x - event_context->xp ) < event_context->tolerance ) - && ( abs( (gint) event->motion.y - event_context->yp ) < event_context->tolerance ) ) { - break; // do not drag if we're within tolerance from origin - } - - // The path went away while dragging; throw away any further motion - // events until the mouse pointer is released. - - if (se->hits_curve() && !se->has_nodepath()) { - break; - } - - // Once the user has moved farther than tolerance from the original location - // (indicating they intend to move the object, not click), then always process the - // motion notify coordinates as given (no snapping back to origin) - event_context->within_tolerance = false; - - // Once we determine what the user is doing (dragging either a node or the - // selection rubberband), make sure we continue to perform that operation - // until the mouse pointer is lifted. - if (nc->current_state == SP_NODE_CONTEXT_INACTIVE) { - if (se->hits_curve() && se->has_nodepath()) { - nc->current_state = SP_NODE_CONTEXT_NODE_DRAGGING; - } else { - nc->current_state = SP_NODE_CONTEXT_RUBBERBAND_DRAGGING; - } - } - - switch (nc->current_state) { - case SP_NODE_CONTEXT_NODE_DRAGGING: - { - se->curve_drag (event->motion.x, event->motion.y); - - gobble_motion_events(GDK_BUTTON1_MASK); - break; - } - case SP_NODE_CONTEXT_RUBBERBAND_DRAGGING: - if (Inkscape::Rubberband::get(desktop)->is_started()) { - Geom::Point const motion_w(event->motion.x, - event->motion.y); - Geom::Point const motion_dt(desktop->w2d(motion_w)); - Inkscape::Rubberband::get(desktop)->move(motion_dt); - } - break; - } - - nc->drag = TRUE; - ret = TRUE; - } else { - if (!se->has_nodepath() || selection->singleItem() == NULL) { - break; - } - - bool over_stroke = false; - over_stroke = se->is_over_stroke(Geom::Point(event->motion.x, event->motion.y), false); - - if (nc->cursor_drag && !over_stroke) { - event_context->cursor_shape = cursor_node_xpm; - event_context->hot_x = 1; - event_context->hot_y = 1; - sp_event_context_update_cursor(event_context); - nc->cursor_drag = false; - } else if (!nc->cursor_drag && over_stroke) { - event_context->cursor_shape = cursor_node_d_xpm; - event_context->hot_x = 1; - event_context->hot_y = 1; - sp_event_context_update_cursor(event_context); - nc->cursor_drag = true; - } - } - break; - - case GDK_2BUTTON_PRESS: - case GDK_BUTTON_RELEASE: - if ( (event->button.button == 1) && (!nc->drag) && !event_context->space_panning) { - // find out clicked item, disregarding groups, honoring Alt - SPItem *item_clicked = sp_event_context_find_item (desktop, - Geom::Point(event->button.x, event->button.y), - (event->button.state & GDK_MOD1_MASK) && !(event->button.state & GDK_CONTROL_MASK), TRUE); - - event_context->xp = event_context->yp = 0; - - bool over_stroke = false; - if (se->has_nodepath()) { - over_stroke = se->is_over_stroke(Geom::Point(event->button.x, event->button.y), false); - } - - if (item_clicked || over_stroke) { - if (over_stroke || nc->added_node) { - switch (event->type) { - case GDK_BUTTON_RELEASE: - if (event->button.state & GDK_CONTROL_MASK && event->button.state & GDK_MOD1_MASK) { - //add a node - se->add_node_near_point(); - } else { - if (nc->added_node) { // we just received double click, ignore release - nc->added_node = false; - break; - } - //select the segment - if (event->button.state & GDK_SHIFT_MASK) { - se->select_segment_near_point(true); - } else { - se->select_segment_near_point(false); - } - desktop->updateNow(); - } - break; - case GDK_2BUTTON_PRESS: - //add a node - se->add_node_near_point(); - nc->added_node = true; - break; - default: - break; - } - } else if (event->button.state & GDK_SHIFT_MASK) { - selection->toggle(item_clicked); - desktop->updateNow(); - } else { - selection->set(item_clicked); - desktop->updateNow(); - } - Inkscape::Rubberband::get(desktop)->stop(); - if (nc->grabbed) { - sp_canvas_item_ungrab(nc->grabbed, event->button.time); - nc->grabbed = NULL; - } - ret = TRUE; - break; - } - } - if (event->type == GDK_BUTTON_RELEASE) { - event_context->xp = event_context->yp = 0; - if (event->button.button == 1) { - Geom::OptRect b = Inkscape::Rubberband::get(desktop)->getRectangle(); - - if (se->hits_curve() && !event_context->within_tolerance) { //drag curve - se->finish_drag(); - } else if (b && !event_context->within_tolerance) { // drag to select - se->select_rect(*b, event->button.state & GDK_SHIFT_MASK); - } else { - if (!(nc->rb_escaped)) { // unless something was canceled - if (se->has_selection()) - se->deselect(); - else - sp_desktop_selection(desktop)->clear(); - } - } - ret = TRUE; - Inkscape::Rubberband::get(desktop)->stop(); - - if (nc->grabbed) { - sp_canvas_item_ungrab(nc->grabbed, event->button.time); - nc->grabbed = NULL; - } - - desktop->updateNow(); - nc->rb_escaped = false; - nc->drag = FALSE; - se->cancel_hit(); - nc->current_state = SP_NODE_CONTEXT_INACTIVE; - } - } - break; - case GDK_KEY_PRESS: - switch (get_group0_keyval(&event->key)) { - case GDK_Insert: - case GDK_KP_Insert: - // with any modifiers - se->add_node(); - ret = TRUE; - break; - case GDK_I: - case GDK_i: - // apple keyboards have no Insert - if (MOD__SHIFT_ONLY) { - se->add_node(); - ret = TRUE; - } - break; - case GDK_Delete: - case GDK_KP_Delete: - case GDK_BackSpace: - if (MOD__CTRL_ONLY) { - se->delete_nodes(); - } else { - se->delete_nodes_preserving_shape(); - } - ret = TRUE; - break; - case GDK_C: - case GDK_c: - if (MOD__SHIFT_ONLY) { - se->set_node_type(Inkscape::NodePath::NODE_CUSP); - ret = TRUE; - } - break; - case GDK_S: - case GDK_s: - if (MOD__SHIFT_ONLY) { - se->set_node_type(Inkscape::NodePath::NODE_SMOOTH); - ret = TRUE; - } - break; - case GDK_A: - case GDK_a: - if (MOD__SHIFT_ONLY) { - se->set_node_type(Inkscape::NodePath::NODE_AUTO); - ret = TRUE; - } - break; - case GDK_Y: - case GDK_y: - if (MOD__SHIFT_ONLY) { - se->set_node_type(Inkscape::NodePath::NODE_SYMM); - ret = TRUE; - } - break; - case GDK_B: - case GDK_b: - if (MOD__SHIFT_ONLY) { - se->break_at_nodes(); - ret = TRUE; - } - break; - case GDK_J: - case GDK_j: - if (MOD__SHIFT_ONLY) { - se->join_nodes(); - ret = TRUE; - } - break; - case GDK_D: - case GDK_d: - if (MOD__SHIFT_ONLY) { - se->duplicate_nodes(); - ret = TRUE; - } - break; - case GDK_L: - case GDK_l: - if (MOD__SHIFT_ONLY) { - se->set_type_of_segments(NR_LINETO); - ret = TRUE; - } - break; - case GDK_U: - case GDK_u: - if (MOD__SHIFT_ONLY) { - se->set_type_of_segments(NR_CURVETO); - ret = TRUE; - } - break; - case GDK_R: - case GDK_r: - if (MOD__SHIFT_ONLY) { - // FIXME: add top panel button - sp_selected_path_reverse(desktop); - ret = TRUE; - } - break; - case GDK_x: - case GDK_X: - if (MOD__ALT_ONLY) { - desktop->setToolboxFocusTo ("altx-nodes"); - ret = TRUE; - } - break; - case GDK_Left: // move selection left - case GDK_KP_Left: - case GDK_KP_4: - if (!MOD__CTRL) { // not ctrl - gint mul = 1 + gobble_key_events( - get_group0_keyval(&event->key), 0); // with any mask - if (MOD__ALT) { // alt - if (MOD__SHIFT) se->move_nodes_screen(desktop, mul*-10, 0); // shift - else se->move_nodes_screen(desktop, mul*-1, 0); // no shift - } - else { // no alt - if (MOD__SHIFT) se->move_nodes(mul*-10*nudge, 0); // shift - else se->move_nodes(mul*-nudge, 0); // no shift - } - ret = TRUE; - } - break; - case GDK_Up: // move selection up - case GDK_KP_Up: - case GDK_KP_8: - if (!MOD__CTRL) { // not ctrl - gint mul = 1 + gobble_key_events( - get_group0_keyval(&event->key), 0); // with any mask - if (MOD__ALT) { // alt - if (MOD__SHIFT) se->move_nodes_screen(desktop, 0, mul*10); // shift - else se->move_nodes_screen(desktop, 0, mul*1); // no shift - } - else { // no alt - if (MOD__SHIFT) se->move_nodes(0, mul*10*nudge); // shift - else se->move_nodes(0, mul*nudge); // no shift - } - ret = TRUE; - } - break; - case GDK_Right: // move selection right - case GDK_KP_Right: - case GDK_KP_6: - if (!MOD__CTRL) { // not ctrl - gint mul = 1 + gobble_key_events( - get_group0_keyval(&event->key), 0); // with any mask - if (MOD__ALT) { // alt - if (MOD__SHIFT) se->move_nodes_screen(desktop, mul*10, 0); // shift - else se->move_nodes_screen(desktop, mul*1, 0); // no shift - } - else { // no alt - if (MOD__SHIFT) se->move_nodes(mul*10*nudge, 0); // shift - else se->move_nodes(mul*nudge, 0); // no shift - } - ret = TRUE; - } - break; - case GDK_Down: // move selection down - case GDK_KP_Down: - case GDK_KP_2: - if (!MOD__CTRL) { // not ctrl - gint mul = 1 + gobble_key_events( - get_group0_keyval(&event->key), 0); // with any mask - if (MOD__ALT) { // alt - if (MOD__SHIFT) se->move_nodes_screen(desktop, 0, mul*-10); // shift - else se->move_nodes_screen(desktop, 0, mul*-1); // no shift - } - else { // no alt - if (MOD__SHIFT) se->move_nodes(0, mul*-10*nudge); // shift - else se->move_nodes(0, mul*-nudge); // no shift - } - ret = TRUE; - } - break; - case GDK_Escape: - { - Geom::OptRect const b = Inkscape::Rubberband::get(desktop)->getRectangle(); - if (b) { - Inkscape::Rubberband::get(desktop)->stop(); - nc->current_state = SP_NODE_CONTEXT_INACTIVE; - nc->rb_escaped = true; - } else { - if (se->has_selection()) { - se->deselect(); - } else { - sp_desktop_selection(desktop)->clear(); - } - } - ret = TRUE; - break; - } - - case GDK_bracketleft: - if ( MOD__CTRL && !MOD__ALT && ( snaps != 0 ) ) { - if (nc->leftctrl) - se->rotate_nodes (M_PI/snaps, -1, false); - if (nc->rightctrl) - se->rotate_nodes (M_PI/snaps, 1, false); - } else if ( MOD__ALT && !MOD__CTRL ) { - if (nc->leftalt && nc->rightalt) - se->rotate_nodes (1, 0, true); - else { - if (nc->leftalt) - se->rotate_nodes (1, -1, true); - if (nc->rightalt) - se->rotate_nodes (1, 1, true); - } - } else if ( snaps != 0 ) { - se->rotate_nodes (M_PI/snaps, 0, false); - } - ret = TRUE; - break; - case GDK_bracketright: - if ( MOD__CTRL && !MOD__ALT && ( snaps != 0 ) ) { - if (nc->leftctrl) - se->rotate_nodes (-M_PI/snaps, -1, false); - if (nc->rightctrl) - se->rotate_nodes (-M_PI/snaps, 1, false); - } else if ( MOD__ALT && !MOD__CTRL ) { - if (nc->leftalt && nc->rightalt) - se->rotate_nodes (-1, 0, true); - else { - if (nc->leftalt) - se->rotate_nodes (-1, -1, true); - if (nc->rightalt) - se->rotate_nodes (-1, 1, true); - } - } else if ( snaps != 0 ) { - se->rotate_nodes (-M_PI/snaps, 0, false); - } - ret = TRUE; - break; - case GDK_less: - case GDK_comma: - if (MOD__CTRL) { - if (nc->leftctrl) - se->scale_nodes(-offset, -1); - if (nc->rightctrl) - se->scale_nodes(-offset, 1); - } else if (MOD__ALT) { - if (nc->leftalt && nc->rightalt) - se->scale_nodes_screen (-1, 0); - else { - if (nc->leftalt) - se->scale_nodes_screen (-1, -1); - if (nc->rightalt) - se->scale_nodes_screen (-1, 1); - } - } else { - se->scale_nodes (-offset, 0); - } - ret = TRUE; - break; - case GDK_greater: - case GDK_period: - if (MOD__CTRL) { - if (nc->leftctrl) - se->scale_nodes (offset, -1); - if (nc->rightctrl) - se->scale_nodes (offset, 1); - } else if (MOD__ALT) { - if (nc->leftalt && nc->rightalt) - se->scale_nodes_screen (1, 0); - else { - if (nc->leftalt) - se->scale_nodes_screen (1, -1); - if (nc->rightalt) - se->scale_nodes_screen (1, 1); - } - } else { - se->scale_nodes (offset, 0); - } - ret = TRUE; - break; - - case GDK_Alt_L: - nc->leftalt = TRUE; - sp_node_context_show_modifier_tip(event_context, event); - break; - case GDK_Alt_R: - nc->rightalt = TRUE; - sp_node_context_show_modifier_tip(event_context, event); - break; - case GDK_Control_L: - nc->leftctrl = TRUE; - sp_node_context_show_modifier_tip(event_context, event); - break; - case GDK_Control_R: - nc->rightctrl = TRUE; - sp_node_context_show_modifier_tip(event_context, event); - break; - case GDK_Shift_L: - case GDK_Shift_R: - case GDK_Meta_L: - case GDK_Meta_R: - sp_node_context_show_modifier_tip(event_context, event); - break; - default: - ret = node_key(event); - break; - } - break; - case GDK_KEY_RELEASE: - switch (get_group0_keyval(&event->key)) { - case GDK_Alt_L: - nc->leftalt = FALSE; - event_context->defaultMessageContext()->clear(); - break; - case GDK_Alt_R: - nc->rightalt = FALSE; - event_context->defaultMessageContext()->clear(); - break; - case GDK_Control_L: - nc->leftctrl = FALSE; - event_context->defaultMessageContext()->clear(); - break; - case GDK_Control_R: - nc->rightctrl = FALSE; - event_context->defaultMessageContext()->clear(); - break; - case GDK_Shift_L: - case GDK_Shift_R: - case GDK_Meta_L: - case GDK_Meta_R: - event_context->defaultMessageContext()->clear(); - break; - } - break; - default: - break; - } - - if (!ret) { - if (((SPEventContextClass *) parent_class)->root_handler) - ret = ((SPEventContextClass *) parent_class)->root_handler(event_context, event); - } - - return ret; -} - - -/* - 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/node-context.h b/src/node-context.h deleted file mode 100644 index 2345ffc7e..000000000 --- a/src/node-context.h +++ /dev/null @@ -1,87 +0,0 @@ -#ifndef __SP_NODE_CONTEXT_H__ -#define __SP_NODE_CONTEXT_H__ - -/* - * Node editing context - * - * Authors: - * Lauris Kaplinski <lauris@kaplinski.com> - * bulia byak <buliabyak@users.sf.net> - * - * This code is in public domain - */ - -#include <gtk/gtktypeutils.h> -#include <sigc++/sigc++.h> -#include "event-context.h" -#include "forward.h" -#include "display/display-forward.h" -#include "nodepath.h" -namespace Inkscape { class Selection; } - -#define SP_TYPE_NODE_CONTEXT (sp_node_context_get_type ()) -#define SP_NODE_CONTEXT(obj) (GTK_CHECK_CAST ((obj), SP_TYPE_NODE_CONTEXT, SPNodeContext)) -#define SP_NODE_CONTEXT_CLASS(klass) (GTK_CHECK_CLASS_CAST ((klass), SP_TYPE_NODE_CONTEXT, SPNodeContextClass)) -#define SP_IS_NODE_CONTEXT(obj) (GTK_CHECK_TYPE ((obj), SP_TYPE_NODE_CONTEXT)) -#define SP_IS_NODE_CONTEXT_CLASS(klass) (GTK_CHECK_CLASS_TYPE ((klass), SP_TYPE_NODE_CONTEXT)) - -enum { SP_NODE_CONTEXT_INACTIVE, - SP_NODE_CONTEXT_NODE_DRAGGING, - SP_NODE_CONTEXT_RUBBERBAND_DRAGGING }; - -class SPNodeContext; -class SPNodeContextClass; - -struct SPNodeContext { - // FIXME: shouldn't this be a pointer??? - SPEventContext event_context; - - guint drag : 1; - - gboolean leftalt; - gboolean rightalt; - gboolean leftctrl; - gboolean rightctrl; - - /// If true, rubberband was cancelled by esc, so the next button release should not deselect. - bool rb_escaped; - - sigc::connection sel_changed_connection; - - Inkscape::MessageContext *_node_message_context; - - bool cursor_drag; - - bool added_node; - - unsigned int current_state; - - SPItem * flashed_item; - SPCanvasItem *grabbed; - Inkscape::Display::TemporaryItem * flash_tempitem; - int remove_flash_counter; -}; - -struct SPNodeContextClass { - SPEventContextClass parent_class; -}; - -/* Standard Gtk function */ - -GtkType sp_node_context_get_type (void); - -void sp_node_context_selection_changed (Inkscape::Selection * selection, gpointer data); -void sp_node_context_selection_modified (Inkscape::Selection * selection, guint flags, gpointer data); - -#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 : diff --git a/src/nodepath.cpp b/src/nodepath.cpp deleted file mode 100644 index 069b3c5bc..000000000 --- a/src/nodepath.cpp +++ /dev/null @@ -1,5146 +0,0 @@ -#define __SP_NODEPATH_C__ - -/** \file - * Path handler in node edit mode - * - * Authors: - * Lauris Kaplinski <lauris@kaplinski.com> - * bulia byak <buliabyak@users.sf.net> - * - * Portions of this code are in public domain; node sculpting functions written by bulia byak are under GNU GPL - */ - -#ifdef HAVE_CONFIG_H -# include "config.h" -#endif - -#include <gdk/gdkkeysyms.h> -#include "display/canvas-bpath.h" -#include "display/curve.h" -#include "display/sp-ctrlline.h" -#include "display/sodipodi-ctrl.h" -#include "display/sp-canvas-util.h" -#include <glibmm/i18n.h> -#include "2geom/pathvector.h" -#include "2geom/sbasis-to-bezier.h" -#include "2geom/bezier-curve.h" -#include "2geom/hvlinesegment.h" -#include "helper/units.h" -#include "helper/geom.h" -#include "knot.h" -#include "inkscape.h" -#include "document.h" -#include "sp-namedview.h" -#include "desktop.h" -#include "desktop-handles.h" -#include "snap.h" -#include "message-stack.h" -#include "message-context.h" -#include "node-context.h" -#include "lpe-tool-context.h" -#include "shape-editor.h" -#include "selection-chemistry.h" -#include "selection.h" -#include "xml/repr.h" -#include "preferences.h" -#include "sp-metrics.h" -#include "sp-path.h" -#include "sp-text.h" -#include "sp-shape.h" -#include "libnr/nr-matrix-ops.h" -#include "svg/svg.h" -#include "verbs.h" -#include <2geom/bezier-utils.h> -#include <vector> -#include <algorithm> -#include <cstring> -#include <cmath> -#include "live_effects/lpeobject.h" -#include "live_effects/lpeobject-reference.h" -#include "live_effects/effect.h" -#include "live_effects/parameter/parameter.h" -#include "live_effects/parameter/path.h" -#include "util/mathfns.h" -#include "display/snap-indicator.h" -#include "snapped-point.h" - -namespace Geom { class Matrix; } - -/// \todo -/// evil evil evil. FIXME: conflict of two different Path classes! -/// There is a conflict in the namespace between two classes named Path. -/// #include "sp-flowtext.h" -/// #include "sp-flowregion.h" - -#define SP_TYPE_FLOWREGION (sp_flowregion_get_type ()) -#define SP_IS_FLOWREGION(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SP_TYPE_FLOWREGION)) -GType sp_flowregion_get_type (void); -#define SP_TYPE_FLOWTEXT (sp_flowtext_get_type ()) -#define SP_IS_FLOWTEXT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SP_TYPE_FLOWTEXT)) -GType sp_flowtext_get_type (void); -// end evil workaround - -#include "helper/stlport.h" - - -/// \todo fixme: Implement these via preferences */ - -#define NODE_FILL 0xbfbfbf00 -#define NODE_STROKE 0x000000ff -#define NODE_FILL_HI 0xff000000 -#define NODE_STROKE_HI 0x000000ff -#define NODE_FILL_SEL 0x0000ffff -#define NODE_STROKE_SEL 0x000000ff -#define NODE_FILL_SEL_HI 0xff000000 -#define NODE_STROKE_SEL_HI 0x000000ff -#define KNOT_FILL 0xffffffff -#define KNOT_STROKE 0x000000ff -#define KNOT_FILL_HI 0xff000000 -#define KNOT_STROKE_HI 0x000000ff - -static GMemChunk *nodechunk = NULL; - -/* Creation from object */ - -static void subpaths_from_pathvector(Inkscape::NodePath::Path *np, Geom::PathVector const & pathv, Inkscape::NodePath::NodeType const *t); -static Inkscape::NodePath::NodeType * parse_nodetypes(gchar const *types, guint length); - -/* Object updating */ - -static void stamp_repr(Inkscape::NodePath::Path *np); -static SPCurve *create_curve(Inkscape::NodePath::Path *np); -static gchar *create_typestr(Inkscape::NodePath::Path *np); - -static void sp_node_update_handles(Inkscape::NodePath::Node *node, bool fire_move_signals = true); - -static void sp_nodepath_node_select(Inkscape::NodePath::Node *node, gboolean incremental, gboolean override); - -static void sp_node_set_selected(Inkscape::NodePath::Node *node, gboolean selected); - -static Inkscape::NodePath::Node *sp_nodepath_set_node_type(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeType type); - -/* Adjust handle placement, if the node or the other handle is moved */ -static void sp_node_adjust_handle(Inkscape::NodePath::Node *node, gint which_adjust); -static void sp_node_adjust_handles(Inkscape::NodePath::Node *node); -static void sp_node_adjust_handles_auto(Inkscape::NodePath::Node *node); - -/* Node event callbacks */ -static void node_clicked(SPKnot *knot, guint state, gpointer data); -static void node_grabbed(SPKnot *knot, guint state, gpointer data); -static void node_ungrabbed(SPKnot *knot, guint state, gpointer data); -static gboolean node_request(SPKnot *knot, Geom::Point const &p, guint state, gpointer data); - -/* Handle event callbacks */ -static void node_handle_clicked(SPKnot *knot, guint state, gpointer data); -static void node_handle_grabbed(SPKnot *knot, guint state, gpointer data); -static void node_handle_ungrabbed(SPKnot *knot, guint state, gpointer data); -static gboolean node_handle_request(SPKnot *knot, Geom::Point &p, guint state, gpointer data); -static void node_handle_moved(SPKnot *knot, Geom::Point const &p, guint state, gpointer data); -static gboolean node_handle_event(SPKnot *knot, GdkEvent *event, Inkscape::NodePath::Node *n); - -/* Constructors and destructors */ - -static Inkscape::NodePath::SubPath *sp_nodepath_subpath_new(Inkscape::NodePath::Path *nodepath); -static void sp_nodepath_subpath_destroy(Inkscape::NodePath::SubPath *subpath); -static void sp_nodepath_subpath_close(Inkscape::NodePath::SubPath *sp); -static void sp_nodepath_subpath_open(Inkscape::NodePath::SubPath *sp,Inkscape::NodePath::Node *n); -static Inkscape::NodePath::Node * sp_nodepath_node_new(Inkscape::NodePath::SubPath *sp,Inkscape::NodePath::Node *next,Inkscape::NodePath::NodeType type, NRPathcode code, - Geom::Point *ppos, Geom::Point *pos, Geom::Point *npos); -static void sp_nodepath_node_destroy(Inkscape::NodePath::Node *node); - -/* Helpers */ - -static Inkscape::NodePath::NodeSide *sp_node_get_side(Inkscape::NodePath::Node *node, gint which); -static Inkscape::NodePath::NodeSide *sp_node_opposite_side(Inkscape::NodePath::Node *node,Inkscape::NodePath::NodeSide *me); -static NRPathcode sp_node_path_code_from_side(Inkscape::NodePath::Node *node,Inkscape::NodePath::NodeSide *me); - -static SPCurve* sp_nodepath_object_get_curve(SPObject *object, const gchar *key); -static void sp_nodepath_set_curve (Inkscape::NodePath::Path *np, SPCurve *curve); - -// active_node indicates mouseover node -Inkscape::NodePath::Node * Inkscape::NodePath::Path::active_node = NULL; - -static SPCanvasItem * -sp_nodepath_make_helper_item(Inkscape::NodePath::Path *np, /*SPDesktop *desktop, */const SPCurve *curve, bool show = false, guint32 color = 0xff0000ff) { - SPCurve *helper_curve = curve->copy(); - helper_curve->transform(np->i2d); - SPCanvasItem *helper_path = sp_canvas_bpath_new(sp_desktop_controls(np->desktop), helper_curve); - sp_canvas_bpath_set_stroke(SP_CANVAS_BPATH(helper_path), color, 1.0, SP_STROKE_LINEJOIN_MITER, SP_STROKE_LINECAP_BUTT); - sp_canvas_bpath_set_fill(SP_CANVAS_BPATH(helper_path), 0, SP_WIND_RULE_NONZERO); - sp_canvas_item_move_to_z(helper_path, 0); - if (show) { - sp_canvas_item_show(helper_path); - } - helper_curve->unref(); - return helper_path; -} - -static void -sp_nodepath_create_helperpaths(Inkscape::NodePath::Path *np) { - //std::map<Inkscape::LivePathEffect::Effect *, std::vector<SPCanvasItem *> > helper_path_vec; - if (!SP_IS_LPE_ITEM(np->item)) { - g_print ("Only LPEItems can have helperpaths!\n"); - return; - } - - SPLPEItem *lpeitem = SP_LPE_ITEM(np->item); - PathEffectList lpelist = sp_lpe_item_get_effect_list(lpeitem); - for (PathEffectList::iterator i = lpelist.begin(); i != lpelist.end(); ++i) { - Inkscape::LivePathEffect::LPEObjectReference *lperef = (*i); - Inkscape::LivePathEffect::Effect *lpe = lperef->lpeobject->get_lpe(); - if (lpe) { - // create new canvas items from the effect's helper paths - std::vector<Geom::PathVector> hpaths = lpe->getHelperPaths(lpeitem); - for (std::vector<Geom::PathVector>::iterator j = hpaths.begin(); j != hpaths.end(); ++j) { - SPCurve *helper_curve = new SPCurve(*j); - SPCanvasItem * canvasitem = sp_nodepath_make_helper_item(np, helper_curve, true, 0x509050dd); - np->helper_path_vec[lpe].push_back(canvasitem); - helper_curve->unref(); - } - } - } -} - -static void -sp_nodepath_destroy_helperpaths(Inkscape::NodePath::Path *np) { - for (HelperPathList::iterator i = np->helper_path_vec.begin(); i != np->helper_path_vec.end(); ++i) { - for (std::vector<SPCanvasItem *>::iterator j = (*i).second.begin(); j != (*i).second.end(); ++j) { - GtkObject *temp = *j; - *j = NULL; - gtk_object_destroy(temp); - } - } - np->helper_path_vec.clear(); -} - -/** updates canvas items from the effect's helper paths */ -void -sp_nodepath_update_helperpaths(Inkscape::NodePath::Path *np) { - //std::map<Inkscape::LivePathEffect::Effect *, std::vector<SPCanvasItem *> > helper_path_vec; - if (!SP_IS_LPE_ITEM(np->item)) { - g_print ("Only LPEItems can have helperpaths!\n"); - return; - } - - SPLPEItem *lpeitem = SP_LPE_ITEM(np->item); - PathEffectList lpelist = sp_lpe_item_get_effect_list(lpeitem); - - /* The number or type or LPEs may have changed, so we need to clear and recreate our - * helper_path_vec to make sure it is in sync */ - sp_nodepath_destroy_helperpaths(np); - sp_nodepath_create_helperpaths(np); - - for (PathEffectList::iterator i = lpelist.begin(); i != lpelist.end(); ++i) { - Inkscape::LivePathEffect::Effect *lpe = (*i)->lpeobject->get_lpe(); - if (lpe) { - std::vector<Geom::PathVector> hpaths = lpe->getHelperPaths(lpeitem); - for (unsigned int j = 0; j < hpaths.size(); ++j) { - SPCurve *curve = new SPCurve(hpaths[j]); - curve->transform(np->i2d); - sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH((np->helper_path_vec[lpe])[j]), curve); - curve = curve->unref(); - } - } - } -} - -/** - * \brief Creates new nodepath from item - * - * If repr_key_in is not NULL, object *has* to be a LivePathEffectObject ! - * - * \todo create proper constructor for nodepath::path, this method returns null a constructor cannot so this cannot be simply converted to constructor. - */ -Inkscape::NodePath::Path *sp_nodepath_new(SPDesktop *desktop, SPObject *object, bool show_handles, const char * repr_key_in, SPItem *item) -{ - if (repr_key_in) { - g_assert(IS_LIVEPATHEFFECT(object)); - } - - Inkscape::XML::Node *repr = object->repr; - - /** \todo - * FIXME: remove this. We don't want to edit paths inside flowtext. - * Instead we will build our flowtext with cloned paths, so that the - * real paths are outside the flowtext and thus editable as usual. - */ - if (SP_IS_FLOWTEXT(object)) { - for (SPObject *child = sp_object_first_child(object) ; child != NULL; child = SP_OBJECT_NEXT(child) ) { - if SP_IS_FLOWREGION(child) { - SPObject *grandchild = sp_object_first_child(SP_OBJECT(child)); - if (grandchild && SP_IS_PATH(grandchild)) { - object = SP_ITEM(grandchild); - break; - } - } - } - } - - SPCurve *curve = sp_nodepath_object_get_curve(object, repr_key_in); - - if (curve == NULL) { - return NULL; - } - - if (curve->get_segment_count() < 1) { - curve->unref(); - return NULL; // prevent crash for one-node paths - } - - //Create new nodepath - Inkscape::NodePath::Path *np = new Inkscape::NodePath::Path(); - if (!np) { - curve->unref(); - return NULL; - } - - Inkscape::Preferences *prefs = Inkscape::Preferences::get(); - - // Set defaults - np->desktop = desktop; - np->object = object; - np->subpaths = NULL; - np->selected = NULL; - np->shape_editor = NULL; //Let the shapeeditor that makes this set it - np->local_change = 0; - np->show_handles = show_handles; - np->helper_path = NULL; - np->helperpath_rgba = prefs->getInt("/tools/nodes/highlight_color", 0xff0000ff); - np->helperpath_width = 1.0; - np->curve = curve->copy(); - np->show_helperpath = prefs->getBool("/tools/nodes/show_helperpath"); - if (SP_IS_LPE_ITEM(object)) { - Inkscape::LivePathEffect::Effect *lpe = sp_lpe_item_get_current_lpe(SP_LPE_ITEM(object)); - if (lpe && lpe->isVisible() && lpe->showOrigPath()) { - np->show_helperpath = true; - } - } - np->straight_path = false; - if (IS_LIVEPATHEFFECT(object) && item) { - np->item = item; - } else { - np->item = SP_ITEM(object); - } - - np->drag_origin_mouse = Geom::Point(NR_HUGE, NR_HUGE); - - // we need to update item's transform from the repr here, - // because they may be out of sync when we respond - // to a change in repr by regenerating nodepath --bb - sp_object_read_attr(SP_OBJECT(np->item), "transform"); - - np->i2d = sp_item_i2d_affine(np->item); - np->d2i = np->i2d.inverse(); - - np->repr = repr; - if (repr_key_in) { // apparently the object is an LPEObject - np->repr_key = g_strdup(repr_key_in); - np->repr_nodetypes_key = g_strconcat(np->repr_key, "-nodetypes", NULL); - Inkscape::LivePathEffect::Effect * lpe = LIVEPATHEFFECT(object)->get_lpe(); - if (!lpe) { - g_error("sp_nodepath_new: lpeobject without real lpe passed as argument!"); - delete np; - } - Inkscape::LivePathEffect::Parameter *lpeparam = lpe->getParameter(repr_key_in); - if (lpeparam) { - lpeparam->param_setup_nodepath(np); - } - } else { - np->repr_nodetypes_key = g_strdup("sodipodi:nodetypes"); - if ( sp_lpe_item_has_path_effect_recursive(SP_LPE_ITEM(np->object)) ) { - np->repr_key = g_strdup("inkscape:original-d"); - - Inkscape::LivePathEffect::Effect* lpe = sp_lpe_item_get_current_lpe(SP_LPE_ITEM(np->object)); - if (lpe) { - lpe->setup_nodepath(np); - } - } else { - np->repr_key = g_strdup("d"); - } - } - - /* Calculate length of the nodetype string. The closing/starting point for closed paths is counted twice. - * So for example a closed rectangle has a nodetypestring of length 5. - * To get the correct count, one can count all segments in the paths, and then add the total number of (non-empty) paths. */ - Geom::PathVector pathv_sanitized = pathv_to_linear_and_cubic_beziers(np->curve->get_pathvector()); - np->curve->set_pathvector(pathv_sanitized); - guint length = np->curve->get_segment_count(); - for (Geom::PathVector::const_iterator pit = pathv_sanitized.begin(); pit != pathv_sanitized.end(); ++pit) { - length += pit->empty() ? 0 : 1; - } - - gchar const *nodetypes = np->repr->attribute(np->repr_nodetypes_key); - Inkscape::NodePath::NodeType *typestr = parse_nodetypes(nodetypes, length); - - // create the subpath(s) from the bpath - subpaths_from_pathvector(np, pathv_sanitized, typestr); - - // reverse the list, because sp_nodepath_subpath_new() used g_list_prepend instead of append (for speed) - np->subpaths = g_list_reverse(np->subpaths); - - delete[] typestr; - curve->unref(); - - // Draw helper curve - if (np->show_helperpath) { - np->helper_path = sp_nodepath_make_helper_item(np, /*desktop, */np->curve, true, np->helperpath_rgba); - } - - sp_nodepath_create_helperpaths(np); - - return np; -} - -/** - * Destroys nodepath's subpaths, then itself, also tell parent ShapeEditor about it. - */ -Inkscape::NodePath::Path::~Path() { - while (this->subpaths) { - sp_nodepath_subpath_destroy((Inkscape::NodePath::SubPath *) this->subpaths->data); - } - - //Inform the ShapeEditor that made me, if any, that I am gone. - if (this->shape_editor) - this->shape_editor->nodepath_destroyed(); - - g_assert(!this->selected); - - if (this->helper_path) { - GtkObject *temp = this->helper_path; - this->helper_path = NULL; - gtk_object_destroy(temp); - } - if (this->curve) { - this->curve->unref(); - this->curve = NULL; - } - - if (this->repr_key) { - g_free(this->repr_key); - this->repr_key = NULL; - } - if (this->repr_nodetypes_key) { - g_free(this->repr_nodetypes_key); - this->repr_nodetypes_key = NULL; - } - - sp_nodepath_destroy_helperpaths(this); - - this->desktop = NULL; -} - -/** - * Return the node count of a given NodeSubPath. - */ -static gint sp_nodepath_subpath_get_node_count(Inkscape::NodePath::SubPath *subpath) -{ - int nodeCount = 0; - - if (subpath) { - nodeCount = g_list_length(subpath->nodes); - } - - return nodeCount; -} - -/** - * Return the node count of a given NodePath. - */ -static gint sp_nodepath_get_node_count(Inkscape::NodePath::Path *np) -{ - gint nodeCount = 0; - if (np) { - for (GList *item = np->subpaths ; item ; item=item->next) { - Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *)item->data; - nodeCount += g_list_length(subpath->nodes); - } - } - return nodeCount; -} - -/** - * Return the subpath count of a given NodePath. - */ -static gint sp_nodepath_get_subpath_count(Inkscape::NodePath::Path *np) -{ - gint nodeCount = 0; - if (np) { - nodeCount = g_list_length(np->subpaths); - } - return nodeCount; -} - -/** - * Return the selected node count of a given NodePath. - */ -static gint sp_nodepath_selection_get_node_count(Inkscape::NodePath::Path *np) -{ - gint nodeCount = 0; - if (np) { - nodeCount = g_list_length(np->selected); - } - return nodeCount; -} - -/** - * Return the number of subpaths where nodes are selected in a given NodePath. - */ -static gint sp_nodepath_selection_get_subpath_count(Inkscape::NodePath::Path *np) -{ - gint nodeCount = 0; - if (np && np->selected) { - if (!np->selected->next) { - nodeCount = 1; - } else { - for (GList *spl = np->subpaths; spl != NULL; spl = spl->next) { - Inkscape::NodePath::SubPath *subpath = static_cast<Inkscape::NodePath::SubPath *>(spl->data); - for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) { - Inkscape::NodePath::Node *node = static_cast<Inkscape::NodePath::Node *>(nl->data); - if (node->selected) { - nodeCount++; - break; - } - } - } - } - } - return nodeCount; -} - -/** - * Clean up a nodepath after editing. - * - * Currently we are deleting trivial subpaths. - */ -static void sp_nodepath_cleanup(Inkscape::NodePath::Path *nodepath) -{ - GList *badSubPaths = NULL; - - //Check all closed subpaths to be >=1 nodes, all open subpaths to be >= 2 nodes - for (GList *l = nodepath->subpaths; l ; l=l->next) { - Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *)l->data; - if ((sp_nodepath_subpath_get_node_count(sp)<2 && !sp->closed) || (sp_nodepath_subpath_get_node_count(sp)<1 && sp->closed)) - badSubPaths = g_list_append(badSubPaths, sp); - } - - //Delete them. This second step is because sp_nodepath_subpath_destroy() - //also removes the subpath from nodepath->subpaths - for (GList *l = badSubPaths; l ; l=l->next) { - Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *)l->data; - sp_nodepath_subpath_destroy(sp); - } - - g_list_free(badSubPaths); -} - -/** - * Create new nodepaths from pathvector, make it subpaths of np. - * \param t The node type array. - */ -static void subpaths_from_pathvector(Inkscape::NodePath::Path *np, Geom::PathVector const & pathv, Inkscape::NodePath::NodeType const *t) -{ - guint i = 0; // index into node type array - for (Geom::PathVector::const_iterator pit = pathv.begin(); pit != pathv.end(); ++pit) { - if (pit->empty()) - continue; // don't add single knot paths - - Inkscape::NodePath::SubPath *sp = sp_nodepath_subpath_new(np); - - Geom::Point ppos = pit->initialPoint() * np->i2d; - NRPathcode pcode = NR_MOVETO; - - /* Johan: Note that this is pretty arcane code. I am pretty sure it is working correctly, be very certain to change it! (better to just rewrite this whole method)*/ - for (Geom::Path::const_iterator cit = pit->begin(); cit != pit->end_closed(); ++cit) { - if( dynamic_cast<Geom::LineSegment const*>(&*cit) || - dynamic_cast<Geom::HLineSegment const*>(&*cit) || - dynamic_cast<Geom::VLineSegment const*>(&*cit) ) - { - Geom::Point pos = cit->initialPoint() * (Geom::Matrix)np->i2d; - sp_nodepath_node_new(sp, NULL, t[i++], pcode, &ppos, &pos, &pos); - - ppos = cit->finalPoint() * (Geom::Matrix)np->i2d; - pcode = NR_LINETO; - } - else if(Geom::CubicBezier const *cubic_bezier = dynamic_cast<Geom::CubicBezier const*>(&*cit)) { - std::vector<Geom::Point> points = cubic_bezier->points(); - Geom::Point pos = points[0] * (Geom::Matrix)np->i2d; - Geom::Point npos = points[1] * (Geom::Matrix)np->i2d; - sp_nodepath_node_new(sp, NULL, t[i++], pcode, &ppos, &pos, &npos); - - ppos = points[2] * (Geom::Matrix)np->i2d; - pcode = NR_CURVETO; - } - } - - if (pit->closed()) { - // Add last knot (because sp_nodepath_subpath_close kills the last knot) - /* Remember that last closing segment is always a lineto, but its length can be zero if the path is visually closed already - * If the length is zero, don't add it to the nodepath. */ - Geom::Curve const &closing_seg = pit->back_closed(); - // Don't use !closing_seg.isDegenerate() as it is too precise, and does not account for floating point rounding probs (LP bug #257289) - if ( ! are_near(closing_seg.initialPoint(), closing_seg.finalPoint()) ) { - Geom::Point pos = closing_seg.finalPoint() * (Geom::Matrix)np->i2d; - sp_nodepath_node_new(sp, NULL, t[i++], NR_LINETO, &pos, &pos, &pos); - } - - sp_nodepath_subpath_close(sp); - } - } -} - -/** - * Convert from sodipodi:nodetypes to new style type array. - */ -static -Inkscape::NodePath::NodeType * parse_nodetypes(gchar const *types, guint length) -{ - Inkscape::NodePath::NodeType *typestr = new Inkscape::NodePath::NodeType[length + 1]; - - guint pos = 0; - - if (types) { - for (guint i = 0; types[i] && ( i < length ); i++) { - while ((types[i] > '\0') && (types[i] <= ' ')) i++; - if (types[i] != '\0') { - switch (types[i]) { - case 's': - typestr[pos++] =Inkscape::NodePath::NODE_SMOOTH; - break; - case 'a': - typestr[pos++] =Inkscape::NodePath::NODE_AUTO; - break; - case 'z': - typestr[pos++] =Inkscape::NodePath::NODE_SYMM; - break; - case 'c': - typestr[pos++] =Inkscape::NodePath::NODE_CUSP; - break; - default: - typestr[pos++] =Inkscape::NodePath::NODE_NONE; - break; - } - } - } - } - - while (pos < length) { - typestr[pos++] = Inkscape::NodePath::NODE_NONE; - } - - return typestr; -} - -/** - * Make curve out of nodepath, write it into that nodepath's SPShape item so that display is - * updated but repr is not (for speed). Used during curve and node drag. - */ -static void update_object(Inkscape::NodePath::Path *np) -{ - g_assert(np); - - np->curve->unref(); - np->curve = create_curve(np); - - sp_nodepath_set_curve(np, np->curve); - - if (np->show_helperpath) { - SPCurve * helper_curve = np->curve->copy(); - helper_curve->transform(np->i2d); - sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(np->helper_path), helper_curve); - helper_curve->unref(); - } - - // updating helperpaths of LPEItems is now done in sp_lpe_item_update(); - //sp_nodepath_update_helperpaths(np); - - // now that nodepath and knotholder can be enabled simultaneously, we must update the knotholder, too - // TODO: this should be done from ShapeEditor!! nodepath should be oblivious of knotholder! - np->shape_editor->update_knotholder(); -} - -/** - * Update XML path node with data from path object. - */ -static void update_repr_internal(Inkscape::NodePath::Path *np) -{ - g_assert(np); - - Inkscape::XML::Node *repr = np->object->repr; - - np->curve->unref(); - np->curve = create_curve(np); - - gchar *typestr = create_typestr(np); - gchar *svgpath = sp_svg_write_path(np->curve->get_pathvector()); - - // determine if path has an effect applied and write to correct "d" attribute. - if (repr->attribute(np->repr_key) == NULL || strcmp(svgpath, repr->attribute(np->repr_key))) { // d changed - np->local_change++; - repr->setAttribute(np->repr_key, svgpath); - } - - if (repr->attribute(np->repr_nodetypes_key) == NULL || strcmp(typestr, repr->attribute(np->repr_nodetypes_key))) { // nodetypes changed - np->local_change++; - repr->setAttribute(np->repr_nodetypes_key, typestr); - } - - g_free(svgpath); - g_free(typestr); - - if (np->show_helperpath) { - SPCurve * helper_curve = np->curve->copy(); - helper_curve->transform(np->i2d); - sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(np->helper_path), helper_curve); - helper_curve->unref(); - } - - // TODO: do we need this call here? after all, update_object() should have been called just before - //sp_nodepath_update_helperpaths(np); -} - -/** - * Update XML path node with data from path object, commit changes forever. - */ -void sp_nodepath_update_repr(Inkscape::NodePath::Path *np, const gchar *annotation) -{ - //fixme: np can be NULL, so check before proceeding - g_return_if_fail(np != NULL); - - update_repr_internal(np); - sp_canvas_end_forced_full_redraws(np->desktop->canvas); - - sp_document_done(sp_desktop_document(np->desktop), SP_VERB_CONTEXT_NODE, - annotation); -} - -/** - * Update XML path node with data from path object, commit changes with undo. - */ -static void sp_nodepath_update_repr_keyed(Inkscape::NodePath::Path *np, gchar const *key, const gchar *annotation) -{ - update_repr_internal(np); - sp_document_maybe_done(sp_desktop_document(np->desktop), key, SP_VERB_CONTEXT_NODE, - annotation); -} - -/** - * Make duplicate of path, replace corresponding XML node in tree, commit. - */ -static void stamp_repr(Inkscape::NodePath::Path *np) -{ - g_assert(np); - - Inkscape::XML::Node *old_repr = np->object->repr; - Inkscape::XML::Node *new_repr = old_repr->duplicate(old_repr->document()); - - // remember the position of the item - gint pos = old_repr->position(); - // remember parent - Inkscape::XML::Node *parent = sp_repr_parent(old_repr); - - SPCurve *curve = create_curve(np); - gchar *typestr = create_typestr(np); - - gchar *svgpath = sp_svg_write_path(curve->get_pathvector()); - - new_repr->setAttribute(np->repr_key, svgpath); - new_repr->setAttribute(np->repr_nodetypes_key, typestr); - - // add the new repr to the parent - parent->appendChild(new_repr); - // move to the saved position - new_repr->setPosition(pos > 0 ? pos : 0); - - sp_document_done(sp_desktop_document(np->desktop), SP_VERB_CONTEXT_NODE, - _("Stamp")); - - Inkscape::GC::release(new_repr); - g_free(svgpath); - g_free(typestr); - curve->unref(); -} - -/** - * Create curve from path. - */ -static SPCurve *create_curve(Inkscape::NodePath::Path *np) -{ - SPCurve *curve = new SPCurve(); - - for (GList *spl = np->subpaths; spl != NULL; spl = spl->next) { - Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *) spl->data; - curve->moveto(sp->first->pos * np->d2i); - Inkscape::NodePath::Node *n = sp->first->n.other; - while (n) { - Geom::Point const end_pt = n->pos * np->d2i; - if (!IS_FINITE(n->pos[0]) || !IS_FINITE(n->pos[1])){ - g_message("niet finite"); - } - switch (n->code) { - case NR_LINETO: - curve->lineto(end_pt); - break; - case NR_CURVETO: - curve->curveto(n->p.other->n.pos * np->d2i, - n->p.pos * np->d2i, - end_pt); - break; - default: - g_assert_not_reached(); - break; - } - if (n != sp->last) { - n = n->n.other; - } else { - n = NULL; - } - } - if (sp->closed) { - curve->closepath(); - } - } - - return curve; -} - -/** - * Convert path type string to sodipodi:nodetypes style. - */ -static gchar *create_typestr(Inkscape::NodePath::Path *np) -{ - gchar *typestr = g_new(gchar, 32); - gint len = 32; - gint pos = 0; - - for (GList *spl = np->subpaths; spl != NULL; spl = spl->next) { - Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *) spl->data; - - if (pos >= len) { - typestr = g_renew(gchar, typestr, len + 32); - len += 32; - } - - typestr[pos++] = 'c'; - - Inkscape::NodePath::Node *n; - n = sp->first->n.other; - while (n) { - gchar code; - - switch (n->type) { - case Inkscape::NodePath::NODE_CUSP: - code = 'c'; - break; - case Inkscape::NodePath::NODE_SMOOTH: - code = 's'; - break; - case Inkscape::NodePath::NODE_AUTO: - code = 'a'; - break; - case Inkscape::NodePath::NODE_SYMM: - code = 'z'; - break; - default: - g_assert_not_reached(); - code = '\0'; - break; - } - - if (pos >= len) { - typestr = g_renew(gchar, typestr, len + 32); - len += 32; - } - - typestr[pos++] = code; - - if (n != sp->last) { - n = n->n.other; - } else { - n = NULL; - } - } - } - - if (pos >= len) { - typestr = g_renew(gchar, typestr, len + 1); - len += 1; - } - - typestr[pos++] = '\0'; - - return typestr; -} - -// Returns different message contexts depending on the current context. This function should only -// be called when ec is either a SPNodeContext or SPLPEToolContext, thus we return NULL in all -// other cases. -static Inkscape::MessageContext * -get_message_context(SPEventContext *ec) -{ - Inkscape::MessageContext *mc = 0; - - if (SP_IS_NODE_CONTEXT(ec)) { - mc = SP_NODE_CONTEXT(ec)->_node_message_context; - } else if (SP_IS_LPETOOL_CONTEXT(ec)) { - mc = SP_LPETOOL_CONTEXT(ec)->_lpetool_message_context; - } else { - g_warning ("Nodepath should only be present in Node tool or Geometric tool."); - } - - return mc; -} - -/** - \brief Fills node and handle positions for three nodes, splitting line - marked by end at distance t. - */ -static void sp_nodepath_line_midpoint(Inkscape::NodePath::Node *new_path,Inkscape::NodePath::Node *end, gdouble t) -{ - g_assert(new_path != NULL); - g_assert(end != NULL); - - g_assert(end->p.other == new_path); - Inkscape::NodePath::Node *start = new_path->p.other; - g_assert(start); - - if (end->code == NR_LINETO) { - new_path->type =Inkscape::NodePath::NODE_CUSP; - new_path->code = NR_LINETO; - new_path->pos = new_path->n.pos = new_path->p.pos = (t * start->pos + (1 - t) * end->pos); - } else { - new_path->type =Inkscape::NodePath::NODE_SMOOTH; - new_path->code = NR_CURVETO; - gdouble s = 1 - t; - for (int dim = 0; dim < 2; dim++) { - Geom::Coord const f000 = start->pos[dim]; - Geom::Coord const f001 = start->n.pos[dim]; - Geom::Coord const f011 = end->p.pos[dim]; - Geom::Coord const f111 = end->pos[dim]; - Geom::Coord const f00t = s * f000 + t * f001; - Geom::Coord const f01t = s * f001 + t * f011; - Geom::Coord const f11t = s * f011 + t * f111; - Geom::Coord const f0tt = s * f00t + t * f01t; - Geom::Coord const f1tt = s * f01t + t * f11t; - Geom::Coord const fttt = s * f0tt + t * f1tt; - start->n.pos[dim] = f00t; - new_path->p.pos[dim] = f0tt; - new_path->pos[dim] = fttt; - new_path->n.pos[dim] = f1tt; - end->p.pos[dim] = f11t; - } - } -} - -/** - * Adds new node on direct line between two nodes, activates handles of all - * three nodes. - */ -static Inkscape::NodePath::Node *sp_nodepath_line_add_node(Inkscape::NodePath::Node *end, gdouble t) -{ - g_assert(end); - g_assert(end->subpath); - g_assert(g_list_find(end->subpath->nodes, end)); - - Inkscape::NodePath::Node *start = end->p.other; - g_assert( start->n.other == end ); - Inkscape::NodePath::Node *newnode = sp_nodepath_node_new(end->subpath, - end, - (NRPathcode)end->code == NR_LINETO? - Inkscape::NodePath::NODE_CUSP : Inkscape::NodePath::NODE_SMOOTH, - (NRPathcode)end->code, - &start->pos, &start->pos, &start->n.pos); - sp_nodepath_line_midpoint(newnode, end, t); - - sp_node_adjust_handles(start); - sp_node_update_handles(start); - sp_node_update_handles(newnode); - sp_node_adjust_handles(end); - sp_node_update_handles(end); - - return newnode; -} - -/** -\brief Break the path at the node: duplicate the argument node, start a new subpath with the duplicate, and copy all nodes after the argument node to it -*/ -static Inkscape::NodePath::Node *sp_nodepath_node_break(Inkscape::NodePath::Node *node) -{ - g_assert(node); - g_assert(node->subpath); - g_assert(g_list_find(node->subpath->nodes, node)); - - Inkscape::NodePath::Node* result = 0; - Inkscape::NodePath::SubPath *sp = node->subpath; - Inkscape::NodePath::Path *np = sp->nodepath; - - if (sp->closed) { - sp_nodepath_subpath_open(sp, node); - result = sp->first; - } else if ( (node == sp->first) || (node == sp->last ) ){ - // no break for end nodes - result = 0; - } else { - // create a new subpath - Inkscape::NodePath::SubPath *newsubpath = sp_nodepath_subpath_new(np); - - // duplicate the break node as start of the new subpath - Inkscape::NodePath::Node *newnode = sp_nodepath_node_new(newsubpath, NULL, - static_cast<Inkscape::NodePath::NodeType>(node->type), - NR_MOVETO, &node->pos, &node->pos, &node->n.pos); - - // attach rest of curve to new node - g_assert(node->n.other); - newnode->n.other = node->n.other; node->n.other = NULL; - newnode->n.other->p.other = newnode; - newsubpath->last = sp->last; - sp->last = node; - node = newnode; - while (node->n.other) { - node = node->n.other; - node->subpath = newsubpath; - sp->nodes = g_list_remove(sp->nodes, node); - newsubpath->nodes = g_list_prepend(newsubpath->nodes, node); - } - - - result = newnode; - } - return result; -} - -/** - * Duplicate node and connect to neighbours. - */ -static Inkscape::NodePath::Node *sp_nodepath_node_duplicate(Inkscape::NodePath::Node *node) -{ - g_assert(node); - g_assert(node->subpath); - g_assert(g_list_find(node->subpath->nodes, node)); - - Inkscape::NodePath::SubPath *sp = node->subpath; - - NRPathcode code = (NRPathcode) node->code; - if (code == NR_MOVETO) { // if node is the endnode, - node->code = NR_LINETO; // new one is inserted before it, so change that to line - } - - Inkscape::NodePath::Node *newnode = sp_nodepath_node_new(sp, node, (Inkscape::NodePath::NodeType)node->type, code, &node->p.pos, &node->pos, &node->n.pos); - - if (!node->n.other || !node->p.other) { // if node is an endnode, select it - return node; - } else { - return newnode; // otherwise select the newly created node - } -} - -static void sp_node_handle_mirror_n_to_p(Inkscape::NodePath::Node *node) -{ - node->p.pos = (node->pos + (node->pos - node->n.pos)); -} - -static void sp_node_handle_mirror_p_to_n(Inkscape::NodePath::Node *node) -{ - node->n.pos = (node->pos + (node->pos - node->p.pos)); -} - -/** - * Change line type at node, with side effects on neighbours. - */ -static void sp_nodepath_set_line_type(Inkscape::NodePath::Node *end, NRPathcode code) -{ - g_assert(end); - g_assert(end->subpath); - g_assert(end->p.other); - - if (end->code != static_cast<guint>(code) ) { - Inkscape::NodePath::Node *start = end->p.other; - - end->code = code; - - if (code == NR_LINETO) { - if (start->code == NR_LINETO) { - sp_nodepath_set_node_type(start, Inkscape::NodePath::NODE_CUSP); - } - if (end->n.other) { - if (end->n.other->code == NR_LINETO) { - sp_nodepath_set_node_type(end, Inkscape::NodePath::NODE_CUSP); - } - } - - if (start->type == Inkscape::NodePath::NODE_AUTO) - start->type = Inkscape::NodePath::NODE_SMOOTH; - if (end->type == Inkscape::NodePath::NODE_AUTO) - end->type = Inkscape::NodePath::NODE_SMOOTH; - - start->n.pos = start->pos; - end->p.pos = end->pos; - - sp_node_adjust_handle(start, -1); - sp_node_adjust_handle(end, 1); - - } else { - Geom::Point delta = end->pos - start->pos; - start->n.pos = start->pos + delta / 3; - end->p.pos = end->pos - delta / 3; - sp_node_adjust_handle(start, 1); - sp_node_adjust_handle(end, -1); - } - - sp_node_update_handles(start); - sp_node_update_handles(end); - } -} - -static void -sp_nodepath_update_node_knot(Inkscape::NodePath::Node *node) -{ - if (node->type == Inkscape::NodePath::NODE_CUSP) { - node->knot->setShape (SP_KNOT_SHAPE_DIAMOND); - node->knot->setSize (node->selected? 11 : 9); - sp_knot_update_ctrl(node->knot); - } else if (node->type == Inkscape::NodePath::NODE_AUTO) { - node->knot->setShape (SP_KNOT_SHAPE_CIRCLE); - node->knot->setSize (node->selected? 11 : 9); - sp_knot_update_ctrl(node->knot); - } else { - node->knot->setShape (SP_KNOT_SHAPE_SQUARE); - node->knot->setSize (node->selected? 9 : 7); - sp_knot_update_ctrl(node->knot); - } -} - - -/** - * Change node type, and its handles accordingly. - */ -static Inkscape::NodePath::Node *sp_nodepath_set_node_type(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeType type) -{ - g_assert(node); - g_assert(node->subpath); - - if ((node->p.other != NULL) && (node->n.other != NULL)) { - if ((node->code == NR_LINETO) && (node->n.other->code == NR_LINETO)) { - type =Inkscape::NodePath::NODE_CUSP; - } - } - - node->type = type; - - sp_nodepath_update_node_knot(node); - - // if one of handles is mouseovered, preserve its position - if (node->p.knot && SP_KNOT_IS_MOUSEOVER(node->p.knot)) { - sp_node_adjust_handle(node, 1); - } else if (node->n.knot && SP_KNOT_IS_MOUSEOVER(node->n.knot)) { - sp_node_adjust_handle(node, -1); - } else { - sp_node_adjust_handles(node); - } - - sp_node_update_handles(node); - - sp_nodepath_update_statusbar(node->subpath->nodepath); - - return node; -} - -bool -sp_node_side_is_line (Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeSide *side) -{ -// TODO clean up multiple returns - Inkscape::NodePath::Node *othernode = side->other; - if (!othernode) - return false; - NRPathcode const code = sp_node_path_code_from_side(node, side); - if (code == NR_LINETO) - return true; - Inkscape::NodePath::NodeSide *other_to_me = NULL; - if (&node->p == side) { - other_to_me = &othernode->n; - } else if (&node->n == side) { - other_to_me = &othernode->p; - } - if (!other_to_me) - return false; - bool is_line = - (Geom::L2(othernode->pos - other_to_me->pos) < 1e-6 && - Geom::L2(node->pos - side->pos) < 1e-6); - return is_line; -} - -/** - * Same as sp_nodepath_set_node_type(), but also converts, if necessary, adjacent segments from - * lines to curves. If adjacent to one line segment, pulls out or rotates opposite handle to align - * with that segment, procucing half-smooth node. If already half-smooth, pull out the second handle too. - * If already cusp and set to cusp, retracts handles. -*/ -void sp_nodepath_convert_node_type(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeType type) -{ - if (type == Inkscape::NodePath::NODE_AUTO) { - if (node->p.other != NULL) - node->code = NR_CURVETO; - if (node->n.other != NULL) - node->n.other->code = NR_CURVETO; - } - - if (type == Inkscape::NodePath::NODE_SYMM || type == Inkscape::NodePath::NODE_SMOOTH) { - -/* - Here's the algorithm of converting node to smooth (Shift+S or toolbar button), in pseudocode: - - if (two_handles) { - // do nothing, adjust_handles called via set_node_type will line them up - } else if (one_handle) { - if (opposite_to_handle_is_line) { - if (lined_up) { - // already half-smooth; pull opposite handle too making it fully smooth - } else { - // do nothing, adjust_handles will line the handle up, producing a half-smooth node - } - } else { - // pull opposite handle in line with the existing one - } - } else if (no_handles) { - if (both_segments_are_lines - OR both_segments_are_curves - OR one_is_line_but_the_curveside_node_is_selected_and_has_two_handles) { - //pull both handles - } else { - // pull the handle opposite to line segment, making node half-smooth - } - } -*/ - bool p_has_handle = (Geom::L2(node->pos - node->p.pos) > 1e-6); - bool n_has_handle = (Geom::L2(node->pos - node->n.pos) > 1e-6); - bool p_is_line = sp_node_side_is_line(node, &node->p); - bool n_is_line = sp_node_side_is_line(node, &node->n); - -#define NODE_HAS_BOTH_HANDLES(node) ((Geom::L2(node->pos - node->n.pos) > 1e-6) && (Geom::L2(node->pos - node->p.pos) > 1e-6)) - - if (p_has_handle && n_has_handle) { - // do nothing, adjust_handles will line them up - } else if (p_has_handle || n_has_handle) { - if (p_has_handle && n_is_line) { - Radial line (node->n.other->pos - node->pos); - Radial handle (node->pos - node->p.pos); - if (fabs(line.a - handle.a) < 1e-3) { // lined up - // already half-smooth; pull opposite handle too making it fully smooth - node->n.other->code = NR_CURVETO; - node->n.pos = node->pos + (node->n.other->pos - node->pos) / 3; - } else { - // do nothing, adjust_handles will line the handle up, producing a half-smooth node - } - } else if (n_has_handle && p_is_line) { - Radial line (node->p.other->pos - node->pos); - Radial handle (node->pos - node->n.pos); - if (fabs(line.a - handle.a) < 1e-3) { // lined up - // already half-smooth; pull opposite handle too making it fully smooth - node->code = NR_CURVETO; - node->p.pos = node->pos + (node->p.other->pos - node->pos) / 3; - } else { - // do nothing, adjust_handles will line the handle up, producing a half-smooth node - } - } else if (p_has_handle && node->n.other) { - // pull n handle - node->n.other->code = NR_CURVETO; - double len = (type == Inkscape::NodePath::NODE_SYMM)? - Geom::L2(node->p.pos - node->pos) : - Geom::L2(node->n.other->pos - node->pos) / 3; - node->n.pos = node->pos - (len / Geom::L2(node->p.pos - node->pos)) * (node->p.pos - node->pos); - } else if (n_has_handle && node->p.other) { - // pull p handle - node->code = NR_CURVETO; - double len = (type == Inkscape::NodePath::NODE_SYMM)? - Geom::L2(node->n.pos - node->pos) : - Geom::L2(node->p.other->pos - node->pos) / 3; - node->p.pos = node->pos - (len / Geom::L2(node->n.pos - node->pos)) * (node->n.pos - node->pos); - } - } else if (!p_has_handle && !n_has_handle) { - if ((p_is_line && n_is_line) || (!p_is_line && node->p.other && !n_is_line && node->n.other) || - (n_is_line && node->p.other && node->p.other->selected && NODE_HAS_BOTH_HANDLES(node->p.other)) || - (p_is_line && node->n.other && node->n.other->selected && NODE_HAS_BOTH_HANDLES(node->n.other)) - ) { - // no handles, but: both segments are either lines or curves; or: one is line and the - // node at the other side is selected (so it was just smoothed too!) and now has both - // handles: then pull both handles here - - // convert both to curves: - node->code = NR_CURVETO; - node->n.other->code = NR_CURVETO; - - sp_node_adjust_handles_auto(node); - } else { - // pull the handle opposite to line segment, making it half-smooth - if (p_is_line && node->n.other) { - if (type != Inkscape::NodePath::NODE_SYMM) { - // pull n handle - node->n.other->code = NR_CURVETO; - double len = Geom::L2(node->n.other->pos - node->pos) / 3; - node->n.pos = node->pos + (len / Geom::L2(node->p.other->pos - node->pos)) * (node->p.other->pos - node->pos); - } - } else if (n_is_line && node->p.other) { - if (type != Inkscape::NodePath::NODE_SYMM) { - // pull p handle - node->code = NR_CURVETO; - double len = Geom::L2(node->p.other->pos - node->pos) / 3; - node->p.pos = node->pos + (len / Geom::L2(node->n.other->pos - node->pos)) * (node->n.other->pos - node->pos); - } - } - } - } - } else if (type == Inkscape::NodePath::NODE_CUSP && node->type == Inkscape::NodePath::NODE_CUSP) { - // cusping a cusp: retract nodes - node->p.pos = node->pos; - node->n.pos = node->pos; - } - - sp_nodepath_set_node_type (node, type); -} - -/** - * Move node to point, and adjust its and neighbouring handles. - */ -void sp_node_moveto(Inkscape::NodePath::Node *node, Geom::Point p) -{ - if (node->type == Inkscape::NodePath::NODE_AUTO) { - node->pos = p; - sp_node_adjust_handles_auto(node); - } else { - Geom::Point delta = p - node->pos; - node->pos = p; - - node->p.pos += delta; - node->n.pos += delta; - } - - Inkscape::NodePath::Node *node_p = NULL; - Inkscape::NodePath::Node *node_n = NULL; - - if (node->p.other) { - if (sp_node_side_is_line(node, &node->p)) { - sp_node_adjust_handle(node, 1); - sp_node_adjust_handle(node->p.other, -1); - node_p = node->p.other; - } - if (!node->p.other->selected && node->p.other->type == Inkscape::NodePath::NODE_AUTO) { - sp_node_adjust_handles_auto(node->p.other); - node_p = node->p.other; - } - } - if (node->n.other) { - if (sp_node_side_is_line(node, &node->n)) { - sp_node_adjust_handle(node, -1); - sp_node_adjust_handle(node->n.other, 1); - node_n = node->n.other; - } - if (!node->n.other->selected && node->n.other->type == Inkscape::NodePath::NODE_AUTO) { - sp_node_adjust_handles_auto(node->n.other); - node_n = node->n.other; - } - } - - // this function is only called from batch movers that will update display at the end - // themselves, so here we just move all the knots without emitting move signals, for speed - sp_node_update_handles(node, false); - if (node_n) { - sp_node_update_handles(node_n, false); - } - if (node_p) { - sp_node_update_handles(node_p, false); - } -} - -/** - * Call sp_node_moveto() for node selection and handle possible snapping. - */ -static void sp_nodepath_selected_nodes_move(Inkscape::NodePath::Path *nodepath, Geom::Coord dx, Geom::Coord dy, - bool const snap, bool constrained = false, - Inkscape::Snapper::ConstraintLine const &constraint = Geom::Point()) -{ - Geom::Point delta(dx, dy); - Geom::Point best_pt = delta; - Inkscape::SnappedPoint best; - - if (snap) { - /* When dragging a (selected) node, it should only snap to other nodes (i.e. unselected nodes), and - * not to itself. The snapper however can not tell which nodes are selected and which are not, so we - * must provide that information. */ - - // Build a list of the unselected nodes to which the snapper should snap - std::vector<Inkscape::SnapCandidatePoint> unselected_nodes; - for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) { - Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data; - for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) { - Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data; - if (!node->selected) { - unselected_nodes.push_back(Inkscape::SnapCandidatePoint(node->pos, Inkscape::SNAPSOURCE_UNDEFINED, node->type == Inkscape::NodePath::NODE_SMOOTH ? Inkscape::SNAPTARGET_NODE_SMOOTH : Inkscape::SNAPTARGET_NODE_CUSP)); - } - } - } - - SnapManager &m = nodepath->desktop->namedview->snap_manager; - - // When only the node closest to the mouse pointer is to be snapped - // then we will not even try to snap to other points and discard those immediately - Inkscape::Preferences *prefs = Inkscape::Preferences::get(); - bool closest_only = prefs->getBool("/options/snapclosestonly/value", false); - - Inkscape::NodePath::Node *closest_node = NULL; - Geom::Coord closest_dist = NR_HUGE; - - if (closest_only) { - for (GList *l = nodepath->selected; l != NULL; l = l->next) { - Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data; - Geom::Coord dist = Geom::L2(nodepath->drag_origin_mouse - n->origin); - if (dist < closest_dist) { - closest_node = n; - closest_dist = dist; - } - } - } - - // Iterate through all selected nodes - m.setup(nodepath->desktop, false, nodepath->item, &unselected_nodes); - for (GList *l = nodepath->selected; l != NULL; l = l->next) { - Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data; - if (!closest_only || n == closest_node) { //try to snap either all selected nodes or only the closest one - Inkscape::SnappedPoint s; - Inkscape::SnapSourceType source_type = (n->type == Inkscape::NodePath::NODE_SMOOTH ? Inkscape::SNAPSOURCE_NODE_SMOOTH : Inkscape::SNAPSOURCE_NODE_CUSP); - if (constrained) { - Inkscape::Snapper::ConstraintLine dedicated_constraint = constraint; - dedicated_constraint.setPoint(n->pos); - s = m.constrainedSnap(Inkscape::SnapPreferences::SNAPPOINT_NODE, Inkscape::SnapCandidatePoint(n->pos + delta, source_type), dedicated_constraint); - } else { - s = m.freeSnap(Inkscape::SnapPreferences::SNAPPOINT_NODE, Inkscape::SnapCandidatePoint(n->pos + delta, source_type)); - } - - if (s.getSnapped()) { - s.setPointerDistance(Geom::L2(nodepath->drag_origin_mouse - n->origin)); - if (!s.isOtherSnapBetter(best, true)) { - best = s; - best_pt = from_2geom(s.getPoint()) - n->pos; - } - } - } - } - - if (best.getSnapped()) { - nodepath->desktop->snapindicator->set_new_snaptarget(best); - } else { - nodepath->desktop->snapindicator->remove_snaptarget(); - } - } - - for (GList *l = nodepath->selected; l != NULL; l = l->next) { - Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data; - sp_node_moveto(n, n->pos + best_pt); - } - - // do not update repr here so that node dragging is acceptably fast - update_object(nodepath); -} - -/** -Function mapping x (in the range 0..1) to y (in the range 1..0) using a smooth half-bell-like -curve; the parameter alpha determines how blunt (alpha > 1) or sharp (alpha < 1) will be the curve -near x = 0. - */ -double -sculpt_profile (double x, double alpha, guint profile) -{ - double result = 1; - - if (x >= 1) { - result = 0; - } else if (x <= 0) { - result = 1; - } else { - switch (profile) { - case SCULPT_PROFILE_LINEAR: - result = 1 - x; - break; - case SCULPT_PROFILE_BELL: - result = (0.5 * cos (M_PI * (pow(x, alpha))) + 0.5); - break; - case SCULPT_PROFILE_ELLIPTIC: - result = sqrt(1 - x*x); - break; - default: - g_assert_not_reached(); - } - } - - return result; -} - -double -bezier_length (Geom::Point a, Geom::Point ah, Geom::Point bh, Geom::Point b) -{ - // extremely primitive for now, don't have time to look for the real one - double lower = Geom::L2(b - a); - double upper = Geom::L2(ah - a) + Geom::L2(bh - ah) + Geom::L2(bh - b); - return (lower + upper)/2; -} - -void -sp_nodepath_move_node_and_handles (Inkscape::NodePath::Node *n, Geom::Point delta, Geom::Point delta_n, Geom::Point delta_p) -{ - n->pos = n->origin + delta; - n->n.pos = n->n.origin + delta_n; - n->p.pos = n->p.origin + delta_p; - sp_node_adjust_handles(n); - sp_node_update_handles(n, false); -} - -/** - * Displace selected nodes and their handles by fractions of delta (from their origins), depending - * on how far they are from the dragged node n. - */ -static void -sp_nodepath_selected_nodes_sculpt(Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *n, Geom::Point delta) -{ - g_assert (n); - g_assert (nodepath); - g_assert (n->subpath->nodepath == nodepath); - - double pressure = n->knot->pressure; - if (pressure == 0) - pressure = 0.5; // default - pressure = CLAMP (pressure, 0.2, 0.8); - - // map pressure to alpha = 1/5 ... 5 - double alpha = 1 - 2 * fabs(pressure - 0.5); - if (pressure > 0.5) - alpha = 1/alpha; - - Inkscape::Preferences *prefs = Inkscape::Preferences::get(); - guint profile = prefs->getInt("/tools/nodes/sculpting_profile", SCULPT_PROFILE_BELL); - - if (sp_nodepath_selection_get_subpath_count(nodepath) <= 1) { - // Only one subpath has selected nodes: - // use linear mode, where the distance from n to node being dragged is calculated along the path - - double n_sel_range = 0, p_sel_range = 0; - guint n_nodes = 0, p_nodes = 0; - guint n_sel_nodes = 0, p_sel_nodes = 0; - - // First pass: calculate ranges (TODO: we could cache them, as they don't change while dragging) - { - double n_range = 0, p_range = 0; - bool n_going = true, p_going = true; - Inkscape::NodePath::Node *n_node = n; - Inkscape::NodePath::Node *p_node = n; - do { - // Do one step in both directions from n, until reaching the end of subpath or bumping into each other - if (n_node && n_going) - n_node = n_node->n.other; - if (n_node == NULL) { - n_going = false; - } else { - n_nodes ++; - n_range += bezier_length (n_node->p.other->origin, n_node->p.other->n.origin, n_node->p.origin, n_node->origin); - if (n_node->selected) { - n_sel_nodes ++; - n_sel_range = n_range; - } - if (n_node == p_node) { - n_going = false; - p_going = false; - } - } - if (p_node && p_going) - p_node = p_node->p.other; - if (p_node == NULL) { - p_going = false; - } else { - p_nodes ++; - p_range += bezier_length (p_node->n.other->origin, p_node->n.other->p.origin, p_node->n.origin, p_node->origin); - if (p_node->selected) { - p_sel_nodes ++; - p_sel_range = p_range; - } - if (p_node == n_node) { - n_going = false; - p_going = false; - } - } - } while (n_going || p_going); - } - - // Second pass: actually move nodes in this subpath - sp_nodepath_move_node_and_handles (n, delta, delta, delta); - { - double n_range = 0, p_range = 0; - bool n_going = true, p_going = true; - Inkscape::NodePath::Node *n_node = n; - Inkscape::NodePath::Node *p_node = n; - do { - // Do one step in both directions from n, until reaching the end of subpath or bumping into each other - if (n_node && n_going) - n_node = n_node->n.other; - if (n_node == NULL) { - n_going = false; - } else { - n_range += bezier_length (n_node->p.other->origin, n_node->p.other->n.origin, n_node->p.origin, n_node->origin); - if (n_node->selected) { - sp_nodepath_move_node_and_handles (n_node, - sculpt_profile (n_range / n_sel_range, alpha, profile) * delta, - sculpt_profile ((n_range + Geom::L2(n_node->n.origin - n_node->origin)) / n_sel_range, alpha, profile) * delta, - sculpt_profile ((n_range - Geom::L2(n_node->p.origin - n_node->origin)) / n_sel_range, alpha, profile) * delta); - } - if (n_node == p_node) { - n_going = false; - p_going = false; - } - } - if (p_node && p_going) - p_node = p_node->p.other; - if (p_node == NULL) { - p_going = false; - } else { - p_range += bezier_length (p_node->n.other->origin, p_node->n.other->p.origin, p_node->n.origin, p_node->origin); - if (p_node->selected) { - sp_nodepath_move_node_and_handles (p_node, - sculpt_profile (p_range / p_sel_range, alpha, profile) * delta, - sculpt_profile ((p_range - Geom::L2(p_node->n.origin - p_node->origin)) / p_sel_range, alpha, profile) * delta, - sculpt_profile ((p_range + Geom::L2(p_node->p.origin - p_node->origin)) / p_sel_range, alpha, profile) * delta); - } - if (p_node == n_node) { - n_going = false; - p_going = false; - } - } - } while (n_going || p_going); - } - - } else { - // Multiple subpaths have selected nodes: - // use spatial mode, where the distance from n to node being dragged is measured directly as Geom::L2. - // TODO: correct these distances taking into account their angle relative to the bisector, so as to - // fix the pear-like shape when sculpting e.g. a ring - - // First pass: calculate range - gdouble direct_range = 0; - for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) { - Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data; - for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) { - Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data; - if (node->selected) { - direct_range = MAX(direct_range, Geom::L2(node->origin - n->origin)); - } - } - } - - // Second pass: actually move nodes - for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) { - Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data; - for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) { - Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data; - if (node->selected) { - if (direct_range > 1e-6) { - sp_nodepath_move_node_and_handles (node, - sculpt_profile (Geom::L2(node->origin - n->origin) / direct_range, alpha, profile) * delta, - sculpt_profile (Geom::L2(node->n.origin - n->origin) / direct_range, alpha, profile) * delta, - sculpt_profile (Geom::L2(node->p.origin - n->origin) / direct_range, alpha, profile) * delta); - } else { - sp_nodepath_move_node_and_handles (node, delta, delta, delta); - } - - } - } - } - } - - // do not update repr here so that node dragging is acceptably fast - update_object(nodepath); -} - - -/** - * Move node selection to point, adjust its and neighbouring handles, - * handle possible snapping, and commit the change with possible undo. - */ -void -sp_node_selected_move(Inkscape::NodePath::Path *nodepath, gdouble dx, gdouble dy) -{ - if (!nodepath) return; - - sp_nodepath_selected_nodes_move(nodepath, dx, dy, false); - - if (dx == 0) { - sp_nodepath_update_repr_keyed(nodepath, "node:move:vertical", _("Move nodes vertically")); - } else if (dy == 0) { - sp_nodepath_update_repr_keyed(nodepath, "node:move:horizontal", _("Move nodes horizontally")); - } else { - sp_nodepath_update_repr(nodepath, _("Move nodes")); - } -} - -/** - * Move node selection off screen and commit the change. - */ -void -sp_node_selected_move_screen(SPDesktop *desktop, Inkscape::NodePath::Path *nodepath, gdouble dx, gdouble dy) -{ - // borrowed from sp_selection_move_screen in selection-chemistry.c - // we find out the current zoom factor and divide deltas by it - - gdouble zoom = desktop->current_zoom(); - gdouble zdx = dx / zoom; - gdouble zdy = dy / zoom; - - if (!nodepath) return; - - sp_nodepath_selected_nodes_move(nodepath, zdx, zdy, false); - - if (dx == 0) { - sp_nodepath_update_repr_keyed(nodepath, "node:move:vertical", _("Move nodes vertically")); - } else if (dy == 0) { - sp_nodepath_update_repr_keyed(nodepath, "node:move:horizontal", _("Move nodes horizontally")); - } else { - sp_nodepath_update_repr(nodepath, _("Move nodes")); - } -} - -/** - * Move selected nodes to the absolute position given - */ -void sp_node_selected_move_absolute(Inkscape::NodePath::Path *nodepath, Geom::Coord val, Geom::Dim2 axis) -{ - for (GList *l = nodepath->selected; l != NULL; l = l->next) { - Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data; - Geom::Point npos(axis == Geom::X ? val : n->pos[Geom::X], axis == Geom::Y ? val : n->pos[Geom::Y]); - sp_node_moveto(n, npos); - } - - sp_nodepath_update_repr(nodepath, _("Move nodes")); -} - -/** - * If the coordinates of all selected nodes coincide, return the common coordinate; otherwise return Geom::Nothing - */ -boost::optional<Geom::Coord> sp_node_selected_common_coord (Inkscape::NodePath::Path *nodepath, Geom::Dim2 axis) -{ - boost::optional<Geom::Coord> no_coord; - g_return_val_if_fail(nodepath->selected, no_coord); - - // determine coordinate of first selected node - GList *nsel = nodepath->selected; - Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nsel->data; - Geom::Coord coord = n->pos[axis]; - bool coincide = true; - - // compare it to the coordinates of all the other selected nodes - for (GList *l = nsel->next; l != NULL; l = l->next) { - n = (Inkscape::NodePath::Node *) l->data; - if (n->pos[axis] != coord) { - coincide = false; - } - } - if (coincide) { - return coord; - } else { - Geom::Rect bbox = sp_node_selected_bbox(nodepath); - // currently we return the coordinate of the bounding box midpoint because I don't know how - // to erase the spin button entry field :), but maybe this can be useful behaviour anyway - return bbox.midpoint()[axis]; - } -} - -/** If they don't yet exist, creates knot and line for the given side of the node */ -static void sp_node_ensure_knot_exists (SPDesktop *desktop, Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeSide *side) -{ - if (!side->knot) { - side->knot = sp_knot_new(desktop, _("<b>Node handle</b>: drag to shape the curve; with <b>Ctrl</b> to snap angle; with <b>Alt</b> to lock length; with <b>Shift</b> to rotate both handles")); - - side->knot->setShape (SP_KNOT_SHAPE_CIRCLE); - side->knot->setSize (7); - side->knot->setAnchor (GTK_ANCHOR_CENTER); - side->knot->setFill(KNOT_FILL, KNOT_FILL_HI, KNOT_FILL_HI); - side->knot->setStroke(KNOT_STROKE, KNOT_STROKE_HI, KNOT_STROKE_HI); - sp_knot_update_ctrl(side->knot); - - g_signal_connect(G_OBJECT(side->knot), "clicked", G_CALLBACK(node_handle_clicked), node); - g_signal_connect(G_OBJECT(side->knot), "grabbed", G_CALLBACK(node_handle_grabbed), node); - g_signal_connect(G_OBJECT(side->knot), "ungrabbed", G_CALLBACK(node_handle_ungrabbed), node); - g_signal_connect(G_OBJECT(side->knot), "request", G_CALLBACK(node_handle_request), node); - g_signal_connect(G_OBJECT(side->knot), "moved", G_CALLBACK(node_handle_moved), node); - g_signal_connect(G_OBJECT(side->knot), "event", G_CALLBACK(node_handle_event), node); - } - - if (!side->line) { - side->line = sp_canvas_item_new(sp_desktop_controls(desktop), - SP_TYPE_CTRLLINE, NULL); - } -} - -/** - * Ensure the given handle of the node is visible/invisible, update its screen position - */ -static void sp_node_update_handle(Inkscape::NodePath::Node *node, gint which, gboolean show_handle, bool fire_move_signals) -{ - g_assert(node != NULL); - - Inkscape::NodePath::NodeSide *side = sp_node_get_side(node, which); - NRPathcode code = sp_node_path_code_from_side(node, side); - - show_handle = show_handle && (code == NR_CURVETO) && (Geom::L2(side->pos - node->pos) > 1e-6); - - if (show_handle) { - if (!side->knot) { // No handle knot at all - sp_node_ensure_knot_exists(node->subpath->nodepath->desktop, node, side); - // Just created, so we shouldn't fire the node_moved callback - instead set the knot position directly - side->knot->pos = side->pos; - if (side->knot->item) - SP_CTRL(side->knot->item)->moveto(side->pos); - sp_ctrlline_set_coords(SP_CTRLLINE(side->line), node->pos, side->pos); - sp_knot_show(side->knot); - } else { - if (side->knot->pos != to_2geom(side->pos)) { // only if it's really moved - if (fire_move_signals) { - sp_knot_set_position(side->knot, side->pos, 0); // this will set coords of the line as well - } else { - sp_knot_moveto(side->knot, side->pos); - sp_ctrlline_set_coords(SP_CTRLLINE(side->line), node->pos, side->pos); - } - } - if (!SP_KNOT_IS_VISIBLE(side->knot)) { - sp_knot_show(side->knot); - } - } - sp_canvas_item_show(side->line); - } else { - if (side->knot) { - if (SP_KNOT_IS_VISIBLE(side->knot)) { - sp_knot_hide(side->knot); - } - } - if (side->line) { - sp_canvas_item_hide(side->line); - } - } -} - -/** - * Ensure the node itself is visible, its handles and those of the neighbours of the node are - * visible if selected, update their screen positions. If fire_move_signals, move the node and its - * handles so that the corresponding signals are fired, callbacks are activated, and curve is - * updated; otherwise, just move the knots silently (used in batch moves). - */ -static void sp_node_update_handles(Inkscape::NodePath::Node *node, bool fire_move_signals) -{ - g_assert(node != NULL); - - if (!SP_KNOT_IS_VISIBLE(node->knot)) { - sp_knot_show(node->knot); - } - - if (node->knot->pos != to_2geom(node->pos)) { // visible knot is in a different position, need to update - if (fire_move_signals) - sp_knot_set_position(node->knot, node->pos, 0); - else - sp_knot_moveto(node->knot, node->pos); - } - - gboolean show_handles = node->selected; - if (node->p.other != NULL) { - if (node->p.other->selected) show_handles = TRUE; - } - if (node->n.other != NULL) { - if (node->n.other->selected) show_handles = TRUE; - } - - if (node->subpath->nodepath->show_handles == false) - show_handles = FALSE; - - sp_node_update_handle(node, -1, show_handles, fire_move_signals); - sp_node_update_handle(node, 1, show_handles, fire_move_signals); -} - -/** - * Call sp_node_update_handles() for all nodes on subpath. - */ -static void sp_nodepath_subpath_update_handles(Inkscape::NodePath::SubPath *subpath) -{ - g_assert(subpath != NULL); - - for (GList *l = subpath->nodes; l != NULL; l = l->next) { - sp_node_update_handles((Inkscape::NodePath::Node *) l->data); - } -} - -/** - * Call sp_nodepath_subpath_update_handles() for all subpaths of nodepath. - */ -static void sp_nodepath_update_handles(Inkscape::NodePath::Path *nodepath) -{ - g_assert(nodepath != NULL); - - for (GList *l = nodepath->subpaths; l != NULL; l = l->next) { - sp_nodepath_subpath_update_handles((Inkscape::NodePath::SubPath *) l->data); - } -} - -void -sp_nodepath_show_handles(Inkscape::NodePath::Path *nodepath, bool show) -{ - if (nodepath) { - nodepath->show_handles = show; - sp_nodepath_update_handles(nodepath); - } -} - -/** - * Adds all selected nodes in nodepath to list. - */ -void Inkscape::NodePath::Path::selection(std::list<Node *> &l) -{ - StlConv<Node *>::list(l, selected); -/// \todo this adds a copying, rework when the selection becomes a stl list -} - -/** - * Align selected nodes on the specified axis. - */ -void sp_nodepath_selected_align(Inkscape::NodePath::Path *nodepath, Geom::Dim2 axis) -{ - if ( !nodepath || !nodepath->selected ) { // no nodepath, or no nodes selected - return; - } - - if ( !nodepath->selected->next ) { // only one node selected - return; - } - Inkscape::NodePath::Node *pNode = reinterpret_cast<Inkscape::NodePath::Node *>(nodepath->selected->data); - Geom::Point dest(pNode->pos); - for (GList *l = nodepath->selected; l != NULL; l = l->next) { - pNode = reinterpret_cast<Inkscape::NodePath::Node *>(l->data); - if (pNode) { - dest[axis] = pNode->pos[axis]; - sp_node_moveto(pNode, dest); - } - } - - sp_nodepath_update_repr(nodepath, _("Align nodes")); -} - -/// Helper struct. -struct NodeSort -{ - Inkscape::NodePath::Node *_node; - Geom::Coord _coord; - /// \todo use vectorof pointers instead of calling copy ctor - NodeSort(Inkscape::NodePath::Node *node, Geom::Dim2 axis) : - _node(node), _coord(node->pos[axis]) - {} - -}; - -static bool operator<(NodeSort const &a, NodeSort const &b) -{ - return (a._coord < b._coord); -} - -/** - * Distribute selected nodes on the specified axis. - */ -void sp_nodepath_selected_distribute(Inkscape::NodePath::Path *nodepath, Geom::Dim2 axis) -{ - if ( !nodepath || !nodepath->selected ) { // no nodepath, or no nodes selected - return; - } - - if ( ! (nodepath->selected->next && nodepath->selected->next->next) ) { // less than 3 nodes selected - return; - } - - Inkscape::NodePath::Node *pNode = reinterpret_cast<Inkscape::NodePath::Node *>(nodepath->selected->data); - std::vector<NodeSort> sorted; - for (GList *l = nodepath->selected; l != NULL; l = l->next) { - pNode = reinterpret_cast<Inkscape::NodePath::Node *>(l->data); - if (pNode) { - NodeSort n(pNode, axis); - sorted.push_back(n); - //dest[axis] = pNode->pos[axis]; - //sp_node_moveto(pNode, dest); - } - } - std::sort(sorted.begin(), sorted.end()); - unsigned int len = sorted.size(); - //overall bboxes span - float dist = (sorted.back()._coord - - sorted.front()._coord); - //new distance between each bbox - float step = (dist) / (len - 1); - float pos = sorted.front()._coord; - for ( std::vector<NodeSort> ::iterator it(sorted.begin()); - it < sorted.end(); - it ++ ) - { - Geom::Point dest((*it)._node->pos); - dest[axis] = pos; - sp_node_moveto((*it)._node, dest); - pos += step; - } - - sp_nodepath_update_repr(nodepath, _("Distribute nodes")); -} - - -/** - * Call sp_nodepath_line_add_node() for all selected segments. - */ -void -sp_node_selected_add_node(Inkscape::NodePath::Path *nodepath) -{ - if (!nodepath) { - return; - } - - GList *nl = NULL; - - int n_added = 0; - - for (GList *l = nodepath->selected; l != NULL; l = l->next) { - Inkscape::NodePath::Node *t = (Inkscape::NodePath::Node *) l->data; - g_assert(t->selected); - if (t->p.other && t->p.other->selected) { - nl = g_list_prepend(nl, t); - } - } - - while (nl) { - Inkscape::NodePath::Node *t = (Inkscape::NodePath::Node *) nl->data; - Inkscape::NodePath::Node *n = sp_nodepath_line_add_node(t, 0.5); - sp_nodepath_node_select(n, TRUE, FALSE); - n_added ++; - nl = g_list_remove(nl, t); - } - - /** \todo fixme: adjust ? */ - sp_nodepath_update_handles(nodepath); - - if (n_added > 1) { - sp_nodepath_update_repr(nodepath, _("Add nodes")); - } else if (n_added > 0) { - sp_nodepath_update_repr(nodepath, _("Add node")); - } - - sp_nodepath_update_statusbar(nodepath); -} - -/** - * Select segment nearest to point - */ -void -sp_nodepath_select_segment_near_point(Inkscape::NodePath::Path *nodepath, Geom::Point p, bool toggle) -{ - if (!nodepath) { - return; - } - - SPCurve *curve = create_curve(nodepath); // perhaps we can use nodepath->curve here instead? - Geom::PathVector const &pathv = curve->get_pathvector(); - boost::optional<Geom::PathVectorPosition> pvpos = Geom::nearestPoint(pathv, p); - if (!pvpos) { - g_print ("Possible error?\n"); - return; - } - - // calculate index for nodepath's representation. - unsigned int segment_index = floor(pvpos->t) + 1; - for (unsigned int i = 0; i < pvpos->path_nr; ++i) { - segment_index += pathv[i].size() + 1; - if (pathv[i].closed()) { - segment_index += 1; - } - } - - curve->unref(); - - //find segment to segment - Inkscape::NodePath::Node *e = sp_nodepath_get_node_by_index(nodepath, segment_index); - - //fixme: this can return NULL, so check before proceeding. - g_return_if_fail(e != NULL); - - gboolean force = FALSE; - if (!(e->selected && (!e->p.other || e->p.other->selected))) { - force = TRUE; - } - sp_nodepath_node_select(e, (gboolean) toggle, force); - if (e->p.other) - sp_nodepath_node_select(e->p.other, TRUE, force); - - sp_nodepath_update_handles(nodepath); - - sp_nodepath_update_statusbar(nodepath); -} - -/** - * Add a node nearest to point - */ -void -sp_nodepath_add_node_near_point(Inkscape::NodePath::Path *nodepath, Geom::Point p) -{ - if (!nodepath) { - return; - } - - SPCurve *curve = create_curve(nodepath); // perhaps we can use nodepath->curve here instead? - Geom::PathVector const &pathv = curve->get_pathvector(); - boost::optional<Geom::PathVectorPosition> pvpos = Geom::nearestPoint(pathv, p); - if (!pvpos) { - g_print ("Possible error?\n"); - return; - } - - // calculate index for nodepath's representation. - double int_part; - double t = std::modf(pvpos->t, &int_part); - unsigned int segment_index = (unsigned int)int_part + 1; - for (unsigned int i = 0; i < pvpos->path_nr; ++i) { - segment_index += pathv[i].size() + 1; - if (pathv[i].closed()) { - segment_index += 1; - } - } - - curve->unref(); - - //find segment to split - Inkscape::NodePath::Node *e = sp_nodepath_get_node_by_index(nodepath, segment_index); - if (!e) { - return; - } - - //don't know why but t seems to flip for lines - if (sp_node_path_code_from_side(e, sp_node_get_side(e, -1)) == NR_LINETO) { - t = 1.0 - t; - } - - Inkscape::NodePath::Node *n = sp_nodepath_line_add_node(e, t); - sp_nodepath_node_select(n, FALSE, TRUE); - - /* fixme: adjust ? */ - sp_nodepath_update_handles(nodepath); - - sp_nodepath_update_repr(nodepath, _("Add node")); - - sp_nodepath_update_statusbar(nodepath); -} - -/* - * Adjusts a segment so that t moves by a certain delta for dragging - * converts lines to curves - * - * method and idea borrowed from Simon Budig <simon@gimp.org> and the GIMP - * cf. app/vectors/gimpbezierstroke.c, gimp_bezier_stroke_point_move_relative() - */ -void -sp_nodepath_curve_drag(Inkscape::NodePath::Path *nodepath, int node, double t, Geom::Point delta) -{ - Inkscape::NodePath::Node *e = sp_nodepath_get_node_by_index(nodepath, node); - - //fixme: e and e->p can be NULL, so check for those before proceeding - g_return_if_fail(e != NULL); - g_return_if_fail(&e->p != NULL); - - if (e->type == Inkscape::NodePath::NODE_AUTO) { - e->type = Inkscape::NodePath::NODE_SMOOTH; - sp_nodepath_update_node_knot (e); - } - if (e->p.other->type == Inkscape::NodePath::NODE_AUTO) { - e->p.other->type = Inkscape::NodePath::NODE_SMOOTH; - sp_nodepath_update_node_knot (e->p.other); - } - - /* feel good is an arbitrary parameter that distributes the delta between handles - * if t of the drag point is less than 1/6 distance form the endpoint only - * the corresponding hadle is adjusted. This matches the behavior in GIMP - */ - double feel_good; - if (t <= 1.0 / 6.0) - feel_good = 0; - else if (t <= 0.5) - feel_good = (pow((6 * t - 1) / 2.0, 3)) / 2; - else if (t <= 5.0 / 6.0) - feel_good = (1 - pow((6 * (1-t) - 1) / 2.0, 3)) / 2 + 0.5; - else - feel_good = 1; - - //if we're dragging a line convert it to a curve - if (sp_node_path_code_from_side(e, sp_node_get_side(e, -1))==NR_LINETO) { - sp_nodepath_set_line_type(e, NR_CURVETO); - } - - Geom::Point offsetcoord0 = ((1-feel_good)/(3*t*(1-t)*(1-t))) * delta; - Geom::Point offsetcoord1 = (feel_good/(3*t*t*(1-t))) * delta; - e->p.other->n.pos += offsetcoord0; - e->p.pos += offsetcoord1; - - // adjust handles of adjacent nodes where necessary - sp_node_adjust_handle(e,1); - sp_node_adjust_handle(e->p.other,-1); - - sp_nodepath_update_handles(e->subpath->nodepath); - - update_object(e->subpath->nodepath); - - sp_nodepath_update_statusbar(e->subpath->nodepath); -} - - -/** - * Call sp_nodepath_break() for all selected segments. - */ -void sp_node_selected_break(Inkscape::NodePath::Path *nodepath) -{ - if (!nodepath) return; - - GList *tempin = g_list_copy(nodepath->selected); - GList *temp = NULL; - for (GList *l = tempin; l != NULL; l = l->next) { - Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data; - Inkscape::NodePath::Node *nn = sp_nodepath_node_break(n); - if (nn == NULL) continue; // no break, no new node - temp = g_list_prepend(temp, nn); - } - g_list_free(tempin); - - if (temp) { - sp_nodepath_deselect(nodepath); - } - for (GList *l = temp; l != NULL; l = l->next) { - sp_nodepath_node_select((Inkscape::NodePath::Node *) l->data, TRUE, TRUE); - } - - sp_nodepath_update_handles(nodepath); - - sp_nodepath_update_repr(nodepath, _("Break path")); -} - -/** - * Duplicate the selected node(s). - */ -void sp_node_selected_duplicate(Inkscape::NodePath::Path *nodepath) -{ - if (!nodepath) { - return; - } - - GList *temp = NULL; - for (GList *l = nodepath->selected; l != NULL; l = l->next) { - Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data; - Inkscape::NodePath::Node *nn = sp_nodepath_node_duplicate(n); - if (nn == NULL) continue; // could not duplicate - temp = g_list_prepend(temp, nn); - } - - if (temp) { - sp_nodepath_deselect(nodepath); - } - for (GList *l = temp; l != NULL; l = l->next) { - sp_nodepath_node_select((Inkscape::NodePath::Node *) l->data, TRUE, TRUE); - } - - sp_nodepath_update_handles(nodepath); - - sp_nodepath_update_repr(nodepath, _("Duplicate node")); -} - -/** - * Internal function to join two nodes by merging them into one. - */ -static void do_node_selected_join(Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *a, Inkscape::NodePath::Node *b) -{ - /* a and b are endpoints */ - - // if one of the two nodes is mouseovered, fix its position - Geom::Point c; - if (a->knot && SP_KNOT_IS_MOUSEOVER(a->knot)) { - c = a->pos; - } else if (b->knot && SP_KNOT_IS_MOUSEOVER(b->knot)) { - c = b->pos; - } else { - // otherwise, move joined node to the midpoint - c = (a->pos + b->pos) / 2; - } - - if (a->subpath == b->subpath) { - Inkscape::NodePath::SubPath *sp = a->subpath; - sp_nodepath_subpath_close(sp); - sp_node_moveto (sp->first, c); - - sp_nodepath_update_handles(sp->nodepath); - sp_nodepath_update_repr(nodepath, _("Close subpath")); - return; - } - - /* a and b are separate subpaths */ - Inkscape::NodePath::SubPath *sa = a->subpath; - Inkscape::NodePath::SubPath *sb = b->subpath; - Geom::Point p; - Inkscape::NodePath::Node *n; - NRPathcode code; - if (a == sa->first) { - // we will now reverse sa, so that a is its last node, not first, and drop that node - p = sa->first->n.pos; - code = (NRPathcode)sa->first->n.other->code; - // create new subpath - Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(sa->nodepath); - // create a first moveto node on it - n = sa->last; - sp_nodepath_node_new(t, NULL,Inkscape::NodePath::NODE_CUSP, NR_MOVETO, &n->n.pos, &n->pos, &n->p.pos); - n = n->p.other; - if (n == sa->first) n = NULL; - while (n) { - // copy the rest of the nodes from sa to t, going backwards - sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos); - n = n->p.other; - if (n == sa->first) n = NULL; - } - // replace sa with t - sp_nodepath_subpath_destroy(sa); - sa = t; - } else if (a == sa->last) { - // a is already last, just drop it - p = sa->last->p.pos; - code = (NRPathcode)sa->last->code; - sp_nodepath_node_destroy(sa->last); - } else { - code = NR_END; - g_assert_not_reached(); - } - - if (b == sb->first) { - // copy all nodes from b to a, forward - sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &c, &sb->first->n.pos); - for (n = sb->first->n.other; n != NULL; n = n->n.other) { - sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->code, &n->p.pos, &n->pos, &n->n.pos); - } - } else if (b == sb->last) { - // copy all nodes from b to a, backward - sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &c, &sb->last->p.pos); - for (n = sb->last->p.other; n != NULL; n = n->p.other) { - sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos); - } - } else { - g_assert_not_reached(); - } - /* and now destroy sb */ - - sp_nodepath_subpath_destroy(sb); - - sp_nodepath_update_handles(sa->nodepath); - - sp_nodepath_update_repr(nodepath, _("Join nodes")); - - sp_nodepath_update_statusbar(nodepath); -} - -/** - * Internal function to join two nodes by adding a segment between them. - */ -static void do_node_selected_join_segment(Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *a, Inkscape::NodePath::Node *b) -{ - if (a->subpath == b->subpath) { - Inkscape::NodePath::SubPath *sp = a->subpath; - - /*similar to sp_nodepath_subpath_close(sp), without the node destruction*/ - sp->closed = TRUE; - - sp->first->p.other = sp->last; - sp->last->n.other = sp->first; - - sp_node_handle_mirror_p_to_n(sp->last); - sp_node_handle_mirror_n_to_p(sp->first); - - sp->first->code = sp->last->code; - sp->first = sp->last; - - sp_nodepath_update_handles(sp->nodepath); - - sp_nodepath_update_repr(nodepath, _("Close subpath by segment")); - - return; - } - - /* a and b are separate subpaths */ - Inkscape::NodePath::SubPath *sa = a->subpath; - Inkscape::NodePath::SubPath *sb = b->subpath; - - Inkscape::NodePath::Node *n; - Geom::Point p; - NRPathcode code; - if (a == sa->first) { - code = (NRPathcode) sa->first->n.other->code; - Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(sa->nodepath); - n = sa->last; - sp_nodepath_node_new(t, NULL,Inkscape::NodePath::NODE_CUSP, NR_MOVETO, &n->n.pos, &n->pos, &n->p.pos); - for (n = n->p.other; n != NULL; n = n->p.other) { - sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos); - } - sp_nodepath_subpath_destroy(sa); - sa = t; - } else if (a == sa->last) { - code = (NRPathcode)sa->last->code; - } else { - code = NR_END; - g_assert_not_reached(); - } - - if (b == sb->first) { - n = sb->first; - sp_node_handle_mirror_p_to_n(sa->last); - sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &n->p.pos, &n->pos, &n->n.pos); - sp_node_handle_mirror_n_to_p(sa->last); - for (n = n->n.other; n != NULL; n = n->n.other) { - sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->code, &n->p.pos, &n->pos, &n->n.pos); - } - } else if (b == sb->last) { - n = sb->last; - sp_node_handle_mirror_p_to_n(sa->last); - sp_nodepath_node_new(sa, NULL,Inkscape::NodePath::NODE_CUSP, code, &p, &n->pos, &n->p.pos); - sp_node_handle_mirror_n_to_p(sa->last); - for (n = n->p.other; n != NULL; n = n->p.other) { - sp_nodepath_node_new(sa, NULL, (Inkscape::NodePath::NodeType)n->type, (NRPathcode)n->n.other->code, &n->n.pos, &n->pos, &n->p.pos); - } - } else { - g_assert_not_reached(); - } - /* and now destroy sb */ - - sp_nodepath_subpath_destroy(sb); - - sp_nodepath_update_handles(sa->nodepath); - - sp_nodepath_update_repr(nodepath, _("Join nodes by segment")); -} - -enum NodeJoinType { NODE_JOIN_ENDPOINTS, NODE_JOIN_SEGMENT }; - -/** - * Internal function to handle joining two nodes. - */ -static void node_do_selected_join(Inkscape::NodePath::Path *nodepath, NodeJoinType mode) -{ - if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses - - if (g_list_length(nodepath->selected) != 2) { - nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected.")); - return; - } - - Inkscape::NodePath::Node *a = (Inkscape::NodePath::Node *) nodepath->selected->data; - Inkscape::NodePath::Node *b = (Inkscape::NodePath::Node *) nodepath->selected->next->data; - - g_assert(a != b); - if (!(a->p.other || a->n.other) || !(b->p.other || b->n.other)) { - // someone tried to join an orphan node (i.e. a single-node subpath). - // this is not worth an error message, just fail silently. - return; - } - - if (((a->subpath->closed) || (b->subpath->closed)) || (a->p.other && a->n.other) || (b->p.other && b->n.other)) { - nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("To join, you must have <b>two endnodes</b> selected.")); - return; - } - - switch(mode) { - case NODE_JOIN_ENDPOINTS: - do_node_selected_join(nodepath, a, b); - break; - case NODE_JOIN_SEGMENT: - do_node_selected_join_segment(nodepath, a, b); - break; - } -} - -/** - * Join two nodes by merging them into one. - */ -void sp_node_selected_join(Inkscape::NodePath::Path *nodepath) -{ - node_do_selected_join(nodepath, NODE_JOIN_ENDPOINTS); -} - -/** - * Join two nodes by adding a segment between them. - */ -void sp_node_selected_join_segment(Inkscape::NodePath::Path *nodepath) -{ - node_do_selected_join(nodepath, NODE_JOIN_SEGMENT); -} - -/** - * Delete one or more selected nodes and preserve the shape of the path as much as possible. - */ -void sp_node_delete_preserve(GList *nodes_to_delete) -{ - GSList *nodepaths = NULL; - - while (nodes_to_delete) { - Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node*) g_list_first(nodes_to_delete)->data; - Inkscape::NodePath::SubPath *sp = node->subpath; - Inkscape::NodePath::Path *nodepath = sp->nodepath; - Inkscape::NodePath::Node *sample_cursor = NULL; - Inkscape::NodePath::Node *sample_end = NULL; - Inkscape::NodePath::Node *delete_cursor = node; - bool just_delete = false; - - //find the start of this contiguous selection - //move left to the first node that is not selected - //or the start of the non-closed path - for (Inkscape::NodePath::Node *curr=node->p.other; curr && curr!=node && g_list_find(nodes_to_delete, curr); curr=curr->p.other) { - delete_cursor = curr; - } - - //just delete at the beginning of an open path - if (!delete_cursor->p.other) { - sample_cursor = delete_cursor; - just_delete = true; - } else { - sample_cursor = delete_cursor->p.other; - } - - //calculate points for each segment - int rate = 5; - float period = 1.0 / rate; - std::vector<Geom::Point> data; - if (!just_delete) { - data.push_back(sample_cursor->pos); - for (Inkscape::NodePath::Node *curr=sample_cursor; curr; curr=curr->n.other) { - //just delete at the end of an open path - if (!sp->closed && curr == sp->last) { - just_delete = true; - break; - } - - //sample points on the contiguous selected segment - Geom::Point *bez; - bez = new Geom::Point [4]; - bez[0] = curr->pos; - bez[1] = curr->n.pos; - bez[2] = curr->n.other->p.pos; - bez[3] = curr->n.other->pos; - for (int i=1; i<rate; i++) { - gdouble t = i * period; - Geom::Point p = bezier_pt(3, bez, t); - data.push_back(p); - } - data.push_back(curr->n.other->pos); - - sample_end = curr->n.other; - //break if we've come full circle or hit the end of the selection - if (!g_list_find(nodes_to_delete, curr->n.other) || curr->n.other==sample_cursor) { - break; - } - } - } - - if (!just_delete) { - //calculate the best fitting single segment and adjust the endpoints - Geom::Point *adata; - adata = new Geom::Point [data.size()]; - copy(data.begin(), data.end(), adata); - - Geom::Point *bez; - bez = new Geom::Point [4]; - //would decreasing error create a better fitting approximation? - gdouble error = 1.0; - gint ret; - ret = Geom::bezier_fit_cubic (bez, adata, data.size(), error); - - //if these nodes are smooth or symmetrical, the endpoints will be thrown out of sync. - //make sure these nodes are changed to cusp nodes so that, once the endpoints are moved, - //the resulting nodes behave as expected. - if (sample_cursor->type != Inkscape::NodePath::NODE_CUSP) - sp_nodepath_convert_node_type(sample_cursor, Inkscape::NodePath::NODE_CUSP); - if (sample_end->type != Inkscape::NodePath::NODE_CUSP) - sp_nodepath_convert_node_type(sample_end, Inkscape::NodePath::NODE_CUSP); - - //adjust endpoints - sample_cursor->n.pos = bez[1]; - sample_end->p.pos = bez[2]; - } - - //destroy this contiguous selection - while (delete_cursor && g_list_find(nodes_to_delete, delete_cursor)) { - Inkscape::NodePath::Node *temp = delete_cursor; - if (delete_cursor->n.other == delete_cursor) { - // delete_cursor->n points to itself, which means this is the last node on a closed subpath - delete_cursor = NULL; - } else { - delete_cursor = delete_cursor->n.other; - } - nodes_to_delete = g_list_remove(nodes_to_delete, temp); - sp_nodepath_node_destroy(temp); - } - - sp_nodepath_update_handles(nodepath); - - if (!g_slist_find(nodepaths, nodepath)) - nodepaths = g_slist_prepend (nodepaths, nodepath); - } - - for (GSList *i = nodepaths; i; i = i->next) { - // FIXME: when/if we teach node tool to have more than one nodepath, deleting nodes from - // different nodepaths will give us one undo event per nodepath - Inkscape::NodePath::Path *nodepath = (Inkscape::NodePath::Path *) i->data; - - // if the entire nodepath is removed, delete the selected object. - if (nodepath->subpaths == NULL || - //FIXME: a closed path CAN legally have one node, it's only an open one which must be - //at least 2 - sp_nodepath_get_node_count(nodepath) < 2) { - SPDocument *document = sp_desktop_document (nodepath->desktop); - //FIXME: The following line will be wrong when we have mltiple nodepaths: we only want to - //delete this nodepath's object, not the entire selection! (though at this time, this - //does not matter) - sp_selection_delete(nodepath->desktop); - sp_document_done (document, SP_VERB_CONTEXT_NODE, - _("Delete nodes")); - } else { - sp_nodepath_update_repr(nodepath, _("Delete nodes preserving shape")); - sp_nodepath_update_statusbar(nodepath); - } - } - - g_slist_free (nodepaths); -} - -/** - * Delete one or more selected nodes. - */ -void sp_node_selected_delete(Inkscape::NodePath::Path *nodepath) -{ - if (!nodepath) return; - if (!nodepath->selected) return; - - /** \todo fixme: do it the right way */ - while (nodepath->selected) { - Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nodepath->selected->data; - sp_nodepath_node_destroy(node); - } - - - //clean up the nodepath (such as for trivial subpaths) - sp_nodepath_cleanup(nodepath); - - sp_nodepath_update_handles(nodepath); - - // if the entire nodepath is removed, delete the selected object. - if (nodepath->subpaths == NULL || - sp_nodepath_get_node_count(nodepath) < 2) { - SPDocument *document = sp_desktop_document (nodepath->desktop); - sp_selection_delete(nodepath->desktop); - sp_document_done (document, SP_VERB_CONTEXT_NODE, - _("Delete nodes")); - return; - } - - sp_nodepath_update_repr(nodepath, _("Delete nodes")); - - sp_nodepath_update_statusbar(nodepath); -} - -/** - * Delete one or more segments between two selected nodes. - * This is the code for 'split'. - */ -void -sp_node_selected_delete_segment(Inkscape::NodePath::Path *nodepath) -{ - Inkscape::NodePath::Node *start, *end; //Start , end nodes. not inclusive - Inkscape::NodePath::Node *curr, *next; //Iterators - - if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses - - if (g_list_length(nodepath->selected) != 2) { - nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, - _("Select <b>two non-endpoint nodes</b> on a path between which to delete segments.")); - return; - } - - //Selected nodes, not inclusive - Inkscape::NodePath::Node *a = (Inkscape::NodePath::Node *) nodepath->selected->data; - Inkscape::NodePath::Node *b = (Inkscape::NodePath::Node *) nodepath->selected->next->data; - - if ( ( a==b) || //same node - (a->subpath != b->subpath ) || //not the same path - (!a->p.other || !a->n.other) || //one of a's sides does not have a segment - (!b->p.other || !b->n.other) ) //one of b's sides does not have a segment - { - nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, - _("Select <b>two non-endpoint nodes</b> on a path between which to delete segments.")); - return; - } - - //########################################### - //# BEGIN EDITS - //########################################### - //################################## - //# CLOSED PATH - //################################## - if (a->subpath->closed) { - - - gboolean reversed = FALSE; - - //Since we can go in a circle, we need to find the shorter distance. - // a->b or b->a - start = end = NULL; - int distance = 0; - int minDistance = 0; - for (curr = a->n.other ; curr && curr!=a ; curr=curr->n.other) { - if (curr==b) { - //printf("a to b:%d\n", distance); - start = a;//go from a to b - end = b; - minDistance = distance; - //printf("A to B :\n"); - break; - } - distance++; - } - - //try again, the other direction - distance = 0; - for (curr = b->n.other ; curr && curr!=b ; curr=curr->n.other) { - if (curr==a) { - //printf("b to a:%d\n", distance); - if (distance < minDistance) { - start = b; //we go from b to a - end = a; - reversed = TRUE; - //printf("B to A\n"); - } - break; - } - distance++; - } - - - //Copy everything from 'end' to 'start' to a new subpath - Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(nodepath); - for (curr=end ; curr ; curr=curr->n.other) { - NRPathcode code = (NRPathcode) curr->code; - if (curr == end) - code = NR_MOVETO; - sp_nodepath_node_new(t, NULL, - (Inkscape::NodePath::NodeType)curr->type, code, - &curr->p.pos, &curr->pos, &curr->n.pos); - if (curr == start) - break; - } - sp_nodepath_subpath_destroy(a->subpath); - - - } - - - - //################################## - //# OPEN PATH - //################################## - else { - - //We need to get the direction of the list between A and B - //Can we walk from a to b? - start = end = NULL; - for (curr = a->n.other ; curr && curr!=a ; curr=curr->n.other) { - if (curr==b) { - start = a; //did it! we go from a to b - end = b; - //printf("A to B\n"); - break; - } - } - if (!start) {//didn't work? let's try the other direction - for (curr = b->n.other ; curr && curr!=b ; curr=curr->n.other) { - if (curr==a) { - start = b; //did it! we go from b to a - end = a; - //printf("B to A\n"); - break; - } - } - } - if (!start) { - nodepath->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, - _("Cannot find path between nodes.")); - return; - } - - - - //Copy everything after 'end' to a new subpath - Inkscape::NodePath::SubPath *t = sp_nodepath_subpath_new(nodepath); - for (curr=end ; curr ; curr=curr->n.other) { - NRPathcode code = (NRPathcode) curr->code; - if (curr == end) - code = NR_MOVETO; - sp_nodepath_node_new(t, NULL, (Inkscape::NodePath::NodeType)curr->type, code, - &curr->p.pos, &curr->pos, &curr->n.pos); - } - - //Now let us do our deletion. Since the tail has been saved, go all the way to the end of the list - for (curr = start->n.other ; curr ; curr=next) { - next = curr->n.other; - sp_nodepath_node_destroy(curr); - } - - } - //########################################### - //# END EDITS - //########################################### - - //clean up the nodepath (such as for trivial subpaths) - sp_nodepath_cleanup(nodepath); - - sp_nodepath_update_handles(nodepath); - - sp_nodepath_update_repr(nodepath, _("Delete segment")); - - sp_nodepath_update_statusbar(nodepath); -} - -/** - * Call sp_nodepath_set_line() for all selected segments. - */ -void -sp_node_selected_set_line_type(Inkscape::NodePath::Path *nodepath, NRPathcode code) -{ - if (nodepath == NULL) return; - - for (GList *l = nodepath->selected; l != NULL; l = l->next) { - Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data; - g_assert(n->selected); - if (n->p.other && n->p.other->selected) { - sp_nodepath_set_line_type(n, code); - } - } - - sp_nodepath_update_repr(nodepath, _("Change segment type")); -} - -/** - * Call sp_nodepath_convert_node_type() for all selected nodes. - */ -void -sp_node_selected_set_type(Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::NodeType type) -{ - if (nodepath == NULL) return; - - if (nodepath->straight_path) return; // don't change type when it is a straight path! - - for (GList *l = nodepath->selected; l != NULL; l = l->next) { - sp_nodepath_convert_node_type((Inkscape::NodePath::Node *) l->data, type); - } - - sp_nodepath_update_repr(nodepath, _("Change node type")); -} - -/** - * Change select status of node, update its own and neighbour handles. - */ -static void sp_node_set_selected(Inkscape::NodePath::Node *node, gboolean selected) -{ - node->selected = selected; - - if (selected) { - node->knot->setSize ((node->type == Inkscape::NodePath::NODE_CUSP || node->type == Inkscape::NodePath::NODE_AUTO) ? 11 : 9); - node->knot->setFill(NODE_FILL_SEL, NODE_FILL_SEL_HI, NODE_FILL_SEL_HI); - node->knot->setStroke(NODE_STROKE_SEL, NODE_STROKE_SEL_HI, NODE_STROKE_SEL_HI); - sp_knot_update_ctrl(node->knot); - } else { - node->knot->setSize ((node->type == Inkscape::NodePath::NODE_CUSP || node->type == Inkscape::NodePath::NODE_AUTO) ? 9 : 7); - node->knot->setFill(NODE_FILL, NODE_FILL_HI, NODE_FILL_HI); - node->knot->setStroke(NODE_STROKE, NODE_STROKE_HI, NODE_STROKE_HI); - sp_knot_update_ctrl(node->knot); - } - - sp_node_update_handles(node); - if (node->n.other) sp_node_update_handles(node->n.other); - if (node->p.other) sp_node_update_handles(node->p.other); -} - -/** -\brief Select a node -\param node The node to select -\param incremental If true, add to selection, otherwise deselect others -\param override If true, always select this node, otherwise toggle selected status -*/ -static void sp_nodepath_node_select(Inkscape::NodePath::Node *node, gboolean incremental, gboolean override) -{ - Inkscape::NodePath::Path *nodepath = node->subpath->nodepath; - - if (incremental) { - if (override) { - if (!g_list_find(nodepath->selected, node)) { - nodepath->selected = g_list_prepend(nodepath->selected, node); - } - sp_node_set_selected(node, TRUE); - } else { // toggle - if (node->selected) { - g_assert(g_list_find(nodepath->selected, node)); - nodepath->selected = g_list_remove(nodepath->selected, node); - } else { - g_assert(!g_list_find(nodepath->selected, node)); - nodepath->selected = g_list_prepend(nodepath->selected, node); - } - sp_node_set_selected(node, !node->selected); - } - } else { - sp_nodepath_deselect(nodepath); - nodepath->selected = g_list_prepend(nodepath->selected, node); - sp_node_set_selected(node, TRUE); - } - - sp_nodepath_update_statusbar(nodepath); -} - - -/** -\brief Deselect all nodes in the nodepath -*/ -void -sp_nodepath_deselect(Inkscape::NodePath::Path *nodepath) -{ - if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses - - while (nodepath->selected) { - sp_node_set_selected((Inkscape::NodePath::Node *) nodepath->selected->data, FALSE); - nodepath->selected = g_list_remove(nodepath->selected, nodepath->selected->data); - } - sp_nodepath_update_statusbar(nodepath); -} - -/** -\brief Select or invert selection of all nodes in the nodepath -*/ -void -sp_nodepath_select_all(Inkscape::NodePath::Path *nodepath, bool invert) -{ - if (!nodepath) return; - - for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) { - Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data; - for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) { - Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data; - sp_nodepath_node_select(node, TRUE, invert? !node->selected : TRUE); - } - } -} - -/** - * If nothing selected, does the same as sp_nodepath_select_all(); - * otherwise selects/inverts all nodes in all subpaths that have selected nodes - * (i.e., similar to "select all in layer", with the "selected" subpaths - * being treated as "layers" in the path). - */ -void -sp_nodepath_select_all_from_subpath(Inkscape::NodePath::Path *nodepath, bool invert) -{ - if (!nodepath) return; - - if (g_list_length (nodepath->selected) == 0) { - sp_nodepath_select_all (nodepath, invert); - return; - } - - GList *copy = g_list_copy (nodepath->selected); // copy initial selection so that selecting in the loop does not affect us - GSList *subpaths = NULL; - - for (GList *l = copy; l != NULL; l = l->next) { - Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data; - Inkscape::NodePath::SubPath *subpath = n->subpath; - if (!g_slist_find (subpaths, subpath)) - subpaths = g_slist_prepend (subpaths, subpath); - } - - for (GSList *sp = subpaths; sp != NULL; sp = sp->next) { - Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) sp->data; - for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) { - Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data; - sp_nodepath_node_select(node, TRUE, invert? !g_list_find(copy, node) : TRUE); - } - } - - g_slist_free (subpaths); - g_list_free (copy); -} - -/** - * \brief Select the node after the last selected; if none is selected, - * select the first within path. - */ -void sp_nodepath_select_next(Inkscape::NodePath::Path *nodepath) -{ - if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses - - Inkscape::NodePath::Node *last = NULL; - if (nodepath->selected) { - for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) { - Inkscape::NodePath::SubPath *subpath, *subpath_next; - subpath = (Inkscape::NodePath::SubPath *) spl->data; - for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) { - Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data; - if (node->selected) { - if (node->n.other == (Inkscape::NodePath::Node *) subpath->last) { - if (node->n.other == (Inkscape::NodePath::Node *) subpath->first) { // closed subpath - if (spl->next) { // there's a next subpath - subpath_next = (Inkscape::NodePath::SubPath *) spl->next->data; - last = subpath_next->first; - } else if (spl->prev) { // there's a previous subpath - last = NULL; // to be set later to the first node of first subpath - } else { - last = node->n.other; - } - } else { - last = node->n.other; - } - } else { - if (node->n.other) { - last = node->n.other; - } else { - if (spl->next) { // there's a next subpath - subpath_next = (Inkscape::NodePath::SubPath *) spl->next->data; - last = subpath_next->first; - } else if (spl->prev) { // there's a previous subpath - last = NULL; // to be set later to the first node of first subpath - } else { - last = (Inkscape::NodePath::Node *) subpath->first; - } - } - } - } - } - } - sp_nodepath_deselect(nodepath); - } - - if (last) { // there's at least one more node after selected - sp_nodepath_node_select((Inkscape::NodePath::Node *) last, TRUE, TRUE); - } else { // no more nodes, select the first one in first subpath - Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) nodepath->subpaths->data; - sp_nodepath_node_select((Inkscape::NodePath::Node *) subpath->first, TRUE, TRUE); - } -} - -/** - * \brief Select the node before the first selected; if none is selected, - * select the last within path - */ -void sp_nodepath_select_prev(Inkscape::NodePath::Path *nodepath) -{ - if (!nodepath) return; // there's no nodepath when editing rects, stars, spirals or ellipses - - Inkscape::NodePath::Node *last = NULL; - if (nodepath->selected) { - for (GList *spl = g_list_last(nodepath->subpaths); spl != NULL; spl = spl->prev) { - Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data; - for (GList *nl = g_list_last(subpath->nodes); nl != NULL; nl = nl->prev) { - Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data; - if (node->selected) { - if (node->p.other == (Inkscape::NodePath::Node *) subpath->first) { - if (node->p.other == (Inkscape::NodePath::Node *) subpath->last) { // closed subpath - if (spl->prev) { // there's a prev subpath - Inkscape::NodePath::SubPath *subpath_prev = (Inkscape::NodePath::SubPath *) spl->prev->data; - last = subpath_prev->last; - } else if (spl->next) { // there's a next subpath - last = NULL; // to be set later to the last node of last subpath - } else { - last = node->p.other; - } - } else { - last = node->p.other; - } - } else { - if (node->p.other) { - last = node->p.other; - } else { - if (spl->prev) { // there's a prev subpath - Inkscape::NodePath::SubPath *subpath_prev = (Inkscape::NodePath::SubPath *) spl->prev->data; - last = subpath_prev->last; - } else if (spl->next) { // there's a next subpath - last = NULL; // to be set later to the last node of last subpath - } else { - last = (Inkscape::NodePath::Node *) subpath->last; - } - } - } - } - } - } - sp_nodepath_deselect(nodepath); - } - - if (last) { // there's at least one more node before selected - sp_nodepath_node_select((Inkscape::NodePath::Node *) last, TRUE, TRUE); - } else { // no more nodes, select the last one in last subpath - GList *spl = g_list_last(nodepath->subpaths); - Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data; - sp_nodepath_node_select((Inkscape::NodePath::Node *) subpath->last, TRUE, TRUE); - } -} - -/** - * \brief Select all nodes that are within the rectangle. - */ -void sp_nodepath_select_rect(Inkscape::NodePath::Path *nodepath, Geom::Rect const &b, gboolean incremental) -{ - if (!incremental) { - sp_nodepath_deselect(nodepath); - } - - for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) { - Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data; - for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) { - Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data; - - if (b.contains(node->pos)) { - sp_nodepath_node_select(node, TRUE, TRUE); - } - } - } -} - - -void -nodepath_grow_selection_linearly (Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *n, int grow) -{ - g_assert (n); - g_assert (nodepath); - g_assert (n->subpath->nodepath == nodepath); - - if (g_list_length (nodepath->selected) == 0) { - if (grow > 0) { - sp_nodepath_node_select(n, TRUE, TRUE); - } - return; - } - - if (g_list_length (nodepath->selected) == 1) { - if (grow < 0) { - sp_nodepath_deselect (nodepath); - return; - } - } - - double n_sel_range = 0, p_sel_range = 0; - Inkscape::NodePath::Node *farthest_n_node = n; - Inkscape::NodePath::Node *farthest_p_node = n; - - // Calculate ranges - { - double n_range = 0, p_range = 0; - bool n_going = true, p_going = true; - Inkscape::NodePath::Node *n_node = n; - Inkscape::NodePath::Node *p_node = n; - do { - // Do one step in both directions from n, until reaching the end of subpath or bumping into each other - if (n_node && n_going) - n_node = n_node->n.other; - if (n_node == NULL) { - n_going = false; - } else { - n_range += bezier_length (n_node->p.other->pos, n_node->p.other->n.pos, n_node->p.pos, n_node->pos); - if (n_node->selected) { - n_sel_range = n_range; - farthest_n_node = n_node; - } - if (n_node == p_node) { - n_going = false; - p_going = false; - } - } - if (p_node && p_going) - p_node = p_node->p.other; - if (p_node == NULL) { - p_going = false; - } else { - p_range += bezier_length (p_node->n.other->pos, p_node->n.other->p.pos, p_node->n.pos, p_node->pos); - if (p_node->selected) { - p_sel_range = p_range; - farthest_p_node = p_node; - } - if (p_node == n_node) { - n_going = false; - p_going = false; - } - } - } while (n_going || p_going); - } - - if (grow > 0) { - if (n_sel_range < p_sel_range && farthest_n_node && farthest_n_node->n.other && !(farthest_n_node->n.other->selected)) { - sp_nodepath_node_select(farthest_n_node->n.other, TRUE, TRUE); - } else if (farthest_p_node && farthest_p_node->p.other && !(farthest_p_node->p.other->selected)) { - sp_nodepath_node_select(farthest_p_node->p.other, TRUE, TRUE); - } - } else { - if (n_sel_range > p_sel_range && farthest_n_node && farthest_n_node->selected) { - sp_nodepath_node_select(farthest_n_node, TRUE, FALSE); - } else if (farthest_p_node && farthest_p_node->selected) { - sp_nodepath_node_select(farthest_p_node, TRUE, FALSE); - } - } -} - -void -nodepath_grow_selection_spatially (Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::Node *n, int grow) -{ - g_assert (n); - g_assert (nodepath); - g_assert (n->subpath->nodepath == nodepath); - - if (g_list_length (nodepath->selected) == 0) { - if (grow > 0) { - sp_nodepath_node_select(n, TRUE, TRUE); - } - return; - } - - if (g_list_length (nodepath->selected) == 1) { - if (grow < 0) { - sp_nodepath_deselect (nodepath); - return; - } - } - - Inkscape::NodePath::Node *farthest_selected = NULL; - double farthest_dist = 0; - - Inkscape::NodePath::Node *closest_unselected = NULL; - double closest_dist = NR_HUGE; - - for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) { - Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data; - for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) { - Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data; - if (node == n) - continue; - if (node->selected) { - if (Geom::L2(node->pos - n->pos) > farthest_dist) { - farthest_dist = Geom::L2(node->pos - n->pos); - farthest_selected = node; - } - } else { - if (Geom::L2(node->pos - n->pos) < closest_dist) { - closest_dist = Geom::L2(node->pos - n->pos); - closest_unselected = node; - } - } - } - } - - if (grow > 0) { - if (closest_unselected) { - sp_nodepath_node_select(closest_unselected, TRUE, TRUE); - } - } else { - if (farthest_selected) { - sp_nodepath_node_select(farthest_selected, TRUE, FALSE); - } - } -} - - -/** -\brief Saves all nodes' and handles' current positions in their origin members -*/ -void -sp_nodepath_remember_origins(Inkscape::NodePath::Path *nodepath) -{ - for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) { - Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data; - for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) { - Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nl->data; - n->origin = n->pos; - n->p.origin = n->p.pos; - n->n.origin = n->n.pos; - } - } -} - -/** -\brief Saves selected nodes in a nodepath into a list containing integer positions of all selected nodes -*/ -GList *save_nodepath_selection(Inkscape::NodePath::Path *nodepath) -{ - GList *r = NULL; - if (nodepath->selected) { - guint i = 0; - for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) { - Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data; - for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) { - Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data; - i++; - if (node->selected) { - r = g_list_append(r, GINT_TO_POINTER(i)); - } - } - } - } - return r; -} - -/** -\brief Restores selection by selecting nodes whose positions are in the list -*/ -void restore_nodepath_selection(Inkscape::NodePath::Path *nodepath, GList *r) -{ - sp_nodepath_deselect(nodepath); - - guint i = 0; - for (GList *spl = nodepath->subpaths; spl != NULL; spl = spl->next) { - Inkscape::NodePath::SubPath *subpath = (Inkscape::NodePath::SubPath *) spl->data; - for (GList *nl = subpath->nodes; nl != NULL; nl = nl->next) { - Inkscape::NodePath::Node *node = (Inkscape::NodePath::Node *) nl->data; - i++; - if (g_list_find(r, GINT_TO_POINTER(i))) { - sp_nodepath_node_select(node, TRUE, TRUE); - } - } - } -} - - -/** -\brief Adjusts handle according to node type and line code. -*/ -static void sp_node_adjust_handle(Inkscape::NodePath::Node *node, gint which_adjust) -{ - g_assert(node); - - // nothing to do for auto nodes (sp_node_adjust_handles() does the job) - if (node->type == Inkscape::NodePath::NODE_AUTO) - return; - - Inkscape::NodePath::NodeSide *me = sp_node_get_side(node, which_adjust); - Inkscape::NodePath::NodeSide *other = sp_node_opposite_side(node, me); - - // nothing to do if we are an end node - if (me->other == NULL) return; - if (other->other == NULL) return; - - // nothing to do if we are a cusp node - if (node->type == Inkscape::NodePath::NODE_CUSP) return; - - // nothing to do if it's a line from the specified side of the node (i.e. no handle to adjust) - NRPathcode mecode; - if (which_adjust == 1) { - mecode = (NRPathcode)me->other->code; - } else { - mecode = (NRPathcode)node->code; - } - if (mecode == NR_LINETO) return; - - if (sp_node_side_is_line(node, other)) { - // other is a line, and we are either smooth or symm - Inkscape::NodePath::Node *othernode = other->other; - double len = Geom::L2(me->pos - node->pos); - Geom::Point delta = node->pos - othernode->pos; - double linelen = Geom::L2(delta); - if (linelen < 1e-18) - return; - me->pos = node->pos + (len / linelen)*delta; - return; - } - - if (node->type == Inkscape::NodePath::NODE_SYMM) { - // symmetrize - me->pos = 2 * node->pos - other->pos; - return; - } else { - // smoothify - double len = Geom::L2(me->pos - node->pos); - Geom::Point delta = other->pos - node->pos; - double otherlen = Geom::L2(delta); - if (otherlen < 1e-18) return; - me->pos = node->pos - (len / otherlen) * delta; - } -} - -/** - \brief Adjusts both handles according to node type and line code - */ -static void sp_node_adjust_handles(Inkscape::NodePath::Node *node) -{ - g_assert(node); - - if (node->type == Inkscape::NodePath::NODE_CUSP) return; - - /* we are either smooth or symm */ - - if (node->p.other == NULL) return; - if (node->n.other == NULL) return; - - if (node->type == Inkscape::NodePath::NODE_AUTO) { - sp_node_adjust_handles_auto(node); - return; - } - - if (sp_node_side_is_line(node, &node->p)) { - sp_node_adjust_handle(node, 1); - return; - } - - if (sp_node_side_is_line(node, &node->n)) { - sp_node_adjust_handle(node, -1); - return; - } - - /* both are curves */ - Geom::Point const delta( node->n.pos - node->p.pos ); - - if (node->type == Inkscape::NodePath::NODE_SYMM) { - node->p.pos = node->pos - delta / 2; - node->n.pos = node->pos + delta / 2; - return; - } - - /* We are smooth */ - double plen = Geom::L2(node->p.pos - node->pos); - if (plen < 1e-18) return; - double nlen = Geom::L2(node->n.pos - node->pos); - if (nlen < 1e-18) return; - node->p.pos = node->pos - (plen / (plen + nlen)) * delta; - node->n.pos = node->pos + (nlen / (plen + nlen)) * delta; -} - -static void sp_node_adjust_handles_auto(Inkscape::NodePath::Node *node) -{ - if (node->p.other == NULL || node->n.other == NULL) { - node->p.pos = node->pos; - node->n.pos = node->pos; - return; - } - - Geom::Point leg_prev = to_2geom(node->p.other->pos - node->pos); - Geom::Point leg_next = to_2geom(node->n.other->pos - node->pos); - - double norm_leg_prev = Geom::L2(leg_prev); - double norm_leg_next = Geom::L2(leg_next); - - Geom::Point delta; - if (norm_leg_next > 0.0) { - delta = (norm_leg_prev / norm_leg_next) * leg_next - leg_prev; - delta.normalize(); - } - - node->p.pos = node->pos - norm_leg_prev / 3 * delta; - node->n.pos = node->pos + norm_leg_next / 3 * delta; -} - -/** - * Node event callback. - */ -static gboolean node_event(SPKnot */*knot*/, GdkEvent *event, Inkscape::NodePath::Node *n) -{ - gboolean ret = FALSE; - switch (event->type) { - case GDK_ENTER_NOTIFY: - Inkscape::NodePath::Path::active_node = n; - break; - case GDK_LEAVE_NOTIFY: - Inkscape::NodePath::Path::active_node = NULL; - break; - case GDK_SCROLL: - if ((event->scroll.state & GDK_CONTROL_MASK) && !(event->scroll.state & GDK_SHIFT_MASK)) { // linearly - switch (event->scroll.direction) { - case GDK_SCROLL_UP: - nodepath_grow_selection_linearly (n->subpath->nodepath, n, +1); - break; - case GDK_SCROLL_DOWN: - nodepath_grow_selection_linearly (n->subpath->nodepath, n, -1); - break; - default: - break; - } - ret = TRUE; - } else if (!(event->scroll.state & GDK_SHIFT_MASK)) { // spatially - switch (event->scroll.direction) { - case GDK_SCROLL_UP: - nodepath_grow_selection_spatially (n->subpath->nodepath, n, +1); - break; - case GDK_SCROLL_DOWN: - nodepath_grow_selection_spatially (n->subpath->nodepath, n, -1); - break; - default: - break; - } - ret = TRUE; - } - break; - case GDK_KEY_PRESS: - switch (get_group0_keyval (&event->key)) { - case GDK_space: - if (event->key.state & GDK_BUTTON1_MASK) { - Inkscape::NodePath::Path *nodepath = n->subpath->nodepath; - stamp_repr(nodepath); - ret = TRUE; - } - break; - case GDK_Page_Up: - if (event->key.state & GDK_CONTROL_MASK) { - nodepath_grow_selection_linearly (n->subpath->nodepath, n, +1); - } else { - nodepath_grow_selection_spatially (n->subpath->nodepath, n, +1); - } - break; - case GDK_Page_Down: - if (event->key.state & GDK_CONTROL_MASK) { - nodepath_grow_selection_linearly (n->subpath->nodepath, n, -1); - } else { - nodepath_grow_selection_spatially (n->subpath->nodepath, n, -1); - } - break; - default: - break; - } - break; - default: - break; - } - - return ret; -} - -/** - * Handle keypress on node; directly called. - */ -gboolean node_key(GdkEvent *event) -{ - Inkscape::NodePath::Path *np; - - // there is no way to verify nodes so set active_node to nil when deleting!! - if (Inkscape::NodePath::Path::active_node == NULL) return FALSE; - - if ((event->type == GDK_KEY_PRESS) && !(event->key.state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK))) { - gint ret = FALSE; - switch (get_group0_keyval (&event->key)) { - /// \todo FIXME: this does not seem to work, the keys are stolen by tool contexts! - case GDK_BackSpace: - np = Inkscape::NodePath::Path::active_node->subpath->nodepath; - sp_nodepath_node_destroy(Inkscape::NodePath::Path::active_node); - sp_nodepath_update_repr(np, _("Delete node")); - Inkscape::NodePath::Path::active_node = NULL; - ret = TRUE; - break; - case GDK_c: - sp_nodepath_set_node_type(Inkscape::NodePath::Path::active_node,Inkscape::NodePath::NODE_CUSP); - ret = TRUE; - break; - case GDK_s: - sp_nodepath_set_node_type(Inkscape::NodePath::Path::active_node,Inkscape::NodePath::NODE_SMOOTH); - ret = TRUE; - break; - case GDK_a: - sp_nodepath_set_node_type(Inkscape::NodePath::Path::active_node,Inkscape::NodePath::NODE_AUTO); - ret = TRUE; - break; - case GDK_y: - sp_nodepath_set_node_type(Inkscape::NodePath::Path::active_node,Inkscape::NodePath::NODE_SYMM); - ret = TRUE; - break; - case GDK_b: - sp_nodepath_node_break(Inkscape::NodePath::Path::active_node); - ret = TRUE; - break; - } - return ret; - } - return FALSE; -} - -/** - * Mouseclick on node callback. - */ -static void node_clicked(SPKnot */*knot*/, guint state, gpointer data) -{ - Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data; - - if (state & GDK_CONTROL_MASK) { - Inkscape::NodePath::Path *nodepath = n->subpath->nodepath; - - if (!(state & GDK_MOD1_MASK)) { // ctrl+click: toggle node type - if (n->type == Inkscape::NodePath::NODE_CUSP) { - sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_SMOOTH); - } else if (n->type == Inkscape::NodePath::NODE_SMOOTH) { - sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_SYMM); - } else if (n->type == Inkscape::NodePath::NODE_SYMM) { - sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_AUTO); - } else { - sp_nodepath_convert_node_type (n,Inkscape::NodePath::NODE_CUSP); - } - sp_nodepath_update_repr(nodepath, _("Change node type")); - sp_nodepath_update_statusbar(nodepath); - - } else { //ctrl+alt+click: delete node - GList *node_to_delete = NULL; - node_to_delete = g_list_append(node_to_delete, n); - sp_node_delete_preserve(node_to_delete); - } - - } else { - sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE); - } -} - -/** - * Mouse grabbed node callback. - */ -static void node_grabbed(SPKnot *knot, guint state, gpointer data) -{ - Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data; - - if (!n->selected) { - sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE); - } - - n->is_dragging = true; - // Reconstruct and store the location of the mouse pointer at the time when we started dragging (needed for snapping) - n->subpath->nodepath->drag_origin_mouse = knot->grabbed_rel_pos + knot->drag_origin; - - sp_canvas_force_full_redraw_after_interruptions(n->subpath->nodepath->desktop->canvas, 5); - - sp_nodepath_remember_origins (n->subpath->nodepath); -} - -/** - * Mouse ungrabbed node callback. - */ -static void node_ungrabbed(SPKnot */*knot*/, guint /*state*/, gpointer data) -{ - Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data; - - n->dragging_out = NULL; - n->is_dragging = false; - n->subpath->nodepath->drag_origin_mouse = Geom::Point(NR_HUGE, NR_HUGE); - sp_canvas_end_forced_full_redraws(n->subpath->nodepath->desktop->canvas); - - sp_nodepath_update_repr(n->subpath->nodepath, _("Move nodes")); -} - -/** - * The point on a line, given by its angle, closest to the given point. - * \param p A point. - * \param a Angle of the line; it is assumed to go through coordinate origin. - * \param closest Pointer to the point struct where the result is stored. - * \todo FIXME: use dot product perhaps? - */ -static void point_line_closest(Geom::Point *p, double a, Geom::Point *closest) -{ - if (a == HUGE_VAL) { // vertical - *closest = Geom::Point(0, (*p)[Geom::Y]); - } else { - (*closest)[Geom::X] = ( a * (*p)[Geom::Y] + (*p)[Geom::X]) / (a*a + 1); - (*closest)[Geom::Y] = a * (*closest)[Geom::X]; - } -} - -/** - * Distance from the point to a line given by its angle. - * \param p A point. - * \param a Angle of the line; it is assumed to go through coordinate origin. - */ -static double point_line_distance(Geom::Point *p, double a) -{ - Geom::Point c; - point_line_closest(p, a, &c); - return sqrt(((*p)[Geom::X] - c[Geom::X])*((*p)[Geom::X] - c[Geom::X]) + ((*p)[Geom::Y] - c[Geom::Y])*((*p)[Geom::Y] - c[Geom::Y])); -} - -/** - * Callback for node "request" signal. - * \todo fixme: This goes to "moved" event? (lauris) - */ -static gboolean -node_request(SPKnot */*knot*/, Geom::Point const &p, guint state, gpointer data) -{ - double yn, xn, yp, xp; - double an, ap, na, pa; - double d_an, d_ap, d_na, d_pa; - gboolean collinear = FALSE; - Geom::Point c; - Geom::Point pr; - - Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data; - - n->subpath->nodepath->desktop->snapindicator->remove_snaptarget(); - - // If either (Shift and some handle retracted), or (we're already dragging out a handle) - if ( (!n->subpath->nodepath->straight_path) && - ( ((state & GDK_SHIFT_MASK) && ((n->n.other && n->n.pos == n->pos) || (n->p.other && n->p.pos == n->pos))) - || n->dragging_out ) ) - { - Geom::Point mouse = p; - - if (!n->dragging_out) { - // This is the first drag-out event; find out which handle to drag out - double appr_n = (n->n.other ? Geom::L2(n->n.other->pos - n->pos) - Geom::L2(n->n.other->pos - p) : -HUGE_VAL); - double appr_p = (n->p.other ? Geom::L2(n->p.other->pos - n->pos) - Geom::L2(n->p.other->pos - p) : -HUGE_VAL); - - if (appr_p == -HUGE_VAL && appr_n == -HUGE_VAL) // orphan node? - return FALSE; - - Inkscape::NodePath::NodeSide *opposite; - if (appr_p > appr_n) { // closer to p - n->dragging_out = &n->p; - opposite = &n->n; - n->code = NR_CURVETO; - } else if (appr_p < appr_n) { // closer to n - n->dragging_out = &n->n; - opposite = &n->p; - n->n.other->code = NR_CURVETO; - } else { // p and n nodes are the same - if (n->n.pos != n->pos) { // n handle already dragged, drag p - n->dragging_out = &n->p; - opposite = &n->n; - n->code = NR_CURVETO; - } else if (n->p.pos != n->pos) { // p handle already dragged, drag n - n->dragging_out = &n->n; - opposite = &n->p; - n->n.other->code = NR_CURVETO; - } else { // find out to which handle of the adjacent node we're closer; note that n->n.other == n->p.other - double appr_other_n = (n->n.other ? Geom::L2(n->n.other->n.pos - n->pos) - Geom::L2(n->n.other->n.pos - p) : -HUGE_VAL); - double appr_other_p = (n->n.other ? Geom::L2(n->n.other->p.pos - n->pos) - Geom::L2(n->n.other->p.pos - p) : -HUGE_VAL); - if (appr_other_p > appr_other_n) { // closer to other's p handle - n->dragging_out = &n->n; - opposite = &n->p; - n->n.other->code = NR_CURVETO; - } else { // closer to other's n handle - n->dragging_out = &n->p; - opposite = &n->n; - n->code = NR_CURVETO; - } - } - } - - // if there's another handle, make sure the one we drag out starts parallel to it - if (opposite->pos != n->pos) { - mouse = n->pos - Geom::L2(mouse - n->pos) * Geom::unit_vector(opposite->pos - n->pos); - } - - // knots might not be created yet! - sp_node_ensure_knot_exists (n->subpath->nodepath->desktop, n, n->dragging_out); - sp_node_ensure_knot_exists (n->subpath->nodepath->desktop, n, opposite); - } - - // pass this on to the handle-moved callback - node_handle_moved(n->dragging_out->knot, mouse, state, (gpointer) n); - sp_node_update_handles(n); - return TRUE; - } - - if (state & GDK_CONTROL_MASK) { // constrained motion - - // calculate relative distances of handles - // n handle: - yn = n->n.pos[Geom::Y] - n->pos[Geom::Y]; - xn = n->n.pos[Geom::X] - n->pos[Geom::X]; - // if there's no n handle (straight line), see if we can use the direction to the next point on path - if ((n->n.other && n->n.other->code == NR_LINETO) || fabs(yn) + fabs(xn) < 1e-6) { - if (n->n.other) { // if there is the next point - if (L2(n->n.other->p.pos - n->n.other->pos) < 1e-6) // and the next point has no handle either - yn = n->n.other->origin[Geom::Y] - n->origin[Geom::Y]; // use origin because otherwise the direction will change as you drag - xn = n->n.other->origin[Geom::X] - n->origin[Geom::X]; - } - } - if (xn < 0) { xn = -xn; yn = -yn; } // limit the angle to between 0 and pi - if (yn < 0) { xn = -xn; yn = -yn; } - - // p handle: - yp = n->p.pos[Geom::Y] - n->pos[Geom::Y]; - xp = n->p.pos[Geom::X] - n->pos[Geom::X]; - // if there's no p handle (straight line), see if we can use the direction to the prev point on path - if (n->code == NR_LINETO || fabs(yp) + fabs(xp) < 1e-6) { - if (n->p.other) { - if (L2(n->p.other->n.pos - n->p.other->pos) < 1e-6) - yp = n->p.other->origin[Geom::Y] - n->origin[Geom::Y]; - xp = n->p.other->origin[Geom::X] - n->origin[Geom::X]; - } - } - if (xp < 0) { xp = -xp; yp = -yp; } // limit the angle to between 0 and pi - if (yp < 0) { xp = -xp; yp = -yp; } - - if (state & GDK_MOD1_MASK && !(xn == 0 && xp == 0)) { - // sliding on handles, only if at least one of the handles is non-vertical - // (otherwise it's the same as ctrl+drag anyway) - - // calculate angles of the handles - if (xn == 0) { - if (yn == 0) { // no handle, consider it the continuation of the other one - an = 0; - collinear = TRUE; - } - else an = 0; // vertical; set the angle to horizontal - } else an = yn/xn; - - if (xp == 0) { - if (yp == 0) { // no handle, consider it the continuation of the other one - ap = an; - } - else ap = 0; // vertical; set the angle to horizontal - } else ap = yp/xp; - - if (collinear) an = ap; - - // angles of the perpendiculars; HUGE_VAL means vertical - if (an == 0) na = HUGE_VAL; else na = -1/an; - if (ap == 0) pa = HUGE_VAL; else pa = -1/ap; - - // mouse point relative to the node's original pos - pr = p - n->origin; - - // distances to the four lines (two handles and two perpendiculars) - d_an = point_line_distance(&pr, an); - d_na = point_line_distance(&pr, na); - d_ap = point_line_distance(&pr, ap); - d_pa = point_line_distance(&pr, pa); - - // find out which line is the closest, save its closest point in c - if (d_an <= d_na && d_an <= d_ap && d_an <= d_pa) { - point_line_closest(&pr, an, &c); - } else if (d_ap <= d_an && d_ap <= d_na && d_ap <= d_pa) { - point_line_closest(&pr, ap, &c); - } else if (d_na <= d_an && d_na <= d_ap && d_na <= d_pa) { - point_line_closest(&pr, na, &c); - } else if (d_pa <= d_an && d_pa <= d_ap && d_pa <= d_na) { - point_line_closest(&pr, pa, &c); - } - - // move the node to the closest point - sp_nodepath_selected_nodes_move(n->subpath->nodepath, - n->origin[Geom::X] + c[Geom::X] - n->pos[Geom::X], - n->origin[Geom::Y] + c[Geom::Y] - n->pos[Geom::Y], - true); - - } else { // constraining to hor/vert - - if (fabs(p[Geom::X] - n->origin[Geom::X]) > fabs(p[Geom::Y] - n->origin[Geom::Y])) { // snap to hor - sp_nodepath_selected_nodes_move(n->subpath->nodepath, - p[Geom::X] - n->pos[Geom::X], - n->origin[Geom::Y] - n->pos[Geom::Y], - true, - true, Inkscape::Snapper::ConstraintLine(component_vectors[Geom::X])); - } else { // snap to vert - sp_nodepath_selected_nodes_move(n->subpath->nodepath, - n->origin[Geom::X] - n->pos[Geom::X], - p[Geom::Y] - n->pos[Geom::Y], - true, - true, Inkscape::Snapper::ConstraintLine(component_vectors[Geom::Y])); - } - } - } else { // move freely - if (n->is_dragging) { - if (state & GDK_MOD1_MASK) { // sculpt - sp_nodepath_selected_nodes_sculpt(n->subpath->nodepath, n, p - n->origin); - } else { - sp_nodepath_selected_nodes_move(n->subpath->nodepath, - p[Geom::X] - n->pos[Geom::X], - p[Geom::Y] - n->pos[Geom::Y], - (state & GDK_SHIFT_MASK) == 0); - } - } - } - - n->subpath->nodepath->desktop->scroll_to_point(p); - - return TRUE; -} - -/** - * Node handle clicked callback. - */ -static void node_handle_clicked(SPKnot *knot, guint state, gpointer data) -{ - Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data; - - if (state & GDK_CONTROL_MASK) { // "delete" handle - if (n->p.knot == knot) { - n->p.pos = n->pos; - } else if (n->n.knot == knot) { - n->n.pos = n->pos; - } - sp_node_update_handles(n); - Inkscape::NodePath::Path *nodepath = n->subpath->nodepath; - sp_nodepath_update_repr(nodepath, _("Retract handle")); - sp_nodepath_update_statusbar(nodepath); - - } else { // just select or add to selection, depending in Shift - sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE); - } -} - -/** - * Node handle grabbed callback. - */ -static void node_handle_grabbed(SPKnot *knot, guint state, gpointer data) -{ - Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data; - - // convert auto -> smooth when dragging handle - if (n->type == Inkscape::NodePath::NODE_AUTO) { - n->type = Inkscape::NodePath::NODE_SMOOTH; - sp_nodepath_update_node_knot (n); - } - - if (!n->selected) { - sp_nodepath_node_select(n, (state & GDK_SHIFT_MASK), FALSE); - } - - // remember the origin point of the handle - if (n->p.knot == knot) { - n->p.origin_radial = n->p.pos - n->pos; - } else if (n->n.knot == knot) { - n->n.origin_radial = n->n.pos - n->pos; - } else { - g_assert_not_reached(); - } - - sp_canvas_force_full_redraw_after_interruptions(n->subpath->nodepath->desktop->canvas, 5); -} - -/** - * Node handle ungrabbed callback. - */ -static void node_handle_ungrabbed(SPKnot *knot, guint state, gpointer data) -{ - Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data; - - // forget origin and set knot position once more (because it can be wrong now due to restrictions) - if (n->p.knot == knot) { - n->p.origin_radial.a = 0; - sp_knot_set_position(knot, n->p.pos, state); - } else if (n->n.knot == knot) { - n->n.origin_radial.a = 0; - sp_knot_set_position(knot, n->n.pos, state); - } else { - g_assert_not_reached(); - } - - sp_nodepath_update_repr(n->subpath->nodepath, _("Move node handle")); -} - -/** - * Node handle "request" signal callback. - */ -static gboolean node_handle_request(SPKnot *knot, Geom::Point &p, guint state, gpointer data) -{ - Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data; - - Inkscape::NodePath::NodeSide *me, *opposite; - gint which; - if (n->p.knot == knot) { - me = &n->p; - opposite = &n->n; - which = -1; - } else if (n->n.knot == knot) { - me = &n->n; - opposite = &n->p; - which = 1; - } else { - me = opposite = NULL; - which = 0; - g_assert_not_reached(); - } - - SPDesktop *desktop = n->subpath->nodepath->desktop; - SnapManager &m = desktop->namedview->snap_manager; - m.setup(desktop, true, n->subpath->nodepath->item); - Inkscape::SnappedPoint s; - - if ((state & GDK_SHIFT_MASK) != 0) { - // We will not try to snap when the shift-key is pressed - // so remove the old snap indicator and don't wait for it to time-out - desktop->snapindicator->remove_snaptarget(); - } - - Inkscape::NodePath::Node *othernode = opposite->other; - Inkscape::SnapSourceType source_type = (n->type == Inkscape::NodePath::NODE_SMOOTH ? Inkscape::SNAPSOURCE_NODE_SMOOTH : Inkscape::SNAPSOURCE_NODE_CUSP); - if (othernode) { - if ((n->type != Inkscape::NodePath::NODE_CUSP) && sp_node_side_is_line(n, opposite)) { - /* We are smooth node adjacent with line */ - Geom::Point const delta = p - n->pos; - Geom::Coord const len = Geom::L2(delta); - Inkscape::NodePath::Node *othernode = opposite->other; - Geom::Point const ndelta = n->pos - othernode->pos; - Geom::Coord const linelen = Geom::L2(ndelta); - if (len > NR_EPSILON && linelen > NR_EPSILON) { - Geom::Coord const scal = dot(delta, ndelta) / linelen; - p = n->pos + (scal / linelen) * ndelta; - } - if ((state & GDK_SHIFT_MASK) == 0) { - s = m.constrainedSnap(Inkscape::SnapPreferences::SNAPPOINT_NODE, Inkscape::SnapCandidatePoint(p, source_type), Inkscape::Snapper::ConstraintLine(p, ndelta)); - } - } else { - if ((state & GDK_SHIFT_MASK) == 0) { - s = m.freeSnap(Inkscape::SnapPreferences::SNAPPOINT_NODE, Inkscape::SnapCandidatePoint(p, source_type)); - } - } - } else { - if ((state & GDK_SHIFT_MASK) == 0) { - s = m.freeSnap(Inkscape::SnapPreferences::SNAPPOINT_NODE, Inkscape::SnapCandidatePoint(p, source_type)); - } - } - - s.getPoint(p); - - sp_node_adjust_handle(n, -which); - - return FALSE; -} - -/** - * Node handle moved callback. - */ -static void node_handle_moved(SPKnot *knot, Geom::Point const &p, guint state, gpointer data) -{ - Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) data; - Inkscape::Preferences *prefs = Inkscape::Preferences::get(); - - Inkscape::NodePath::NodeSide *me; - Inkscape::NodePath::NodeSide *other; - if (n->p.knot == knot) { - me = &n->p; - other = &n->n; - } else if (n->n.knot == knot) { - me = &n->n; - other = &n->p; - } else { - me = NULL; - other = NULL; - g_assert_not_reached(); - } - - // calculate radial coordinates of the grabbed handle, its other handle, and the mouse point - Radial rme(me->pos - n->pos); - Radial rother(other->pos - n->pos); - Radial rnew(p - n->pos); - - if (state & GDK_CONTROL_MASK && rnew.a != HUGE_VAL) { - int const snaps = prefs->getInt("/options/rotationsnapsperpi/value", 12); - /* 0 interpreted as "no snapping". */ - - // 1. Snap to the closest PI/snaps angle, starting from zero. - double a_snapped = floor(rnew.a/(M_PI/snaps) + 0.5) * (M_PI/snaps); - - // 2. Snap to the original angle, its opposite and perpendiculars - if (me->origin_radial.a != HUGE_VAL) { // otherwise ortho doesn't exist: original handle was zero length - /* The closest PI/2 angle, starting from original angle */ - double const a_ortho = me->origin_radial.a + floor((rnew.a - me->origin_radial.a)/(M_PI/2) + 0.5) * (M_PI/2); - - // Snap to the closest. - a_snapped = ( fabs(a_snapped - rnew.a) < fabs(a_ortho - rnew.a) - ? a_snapped - : a_ortho ); - } - - // 3. Snap to the angle of the opposite line, if any - Inkscape::NodePath::Node *othernode = other->other; - if (othernode) { - Geom::Point other_to_snap(0,0); - if (sp_node_side_is_line(n, other)) { - other_to_snap = othernode->pos - n->pos; - } else { - other_to_snap = other->pos - n->pos; - } - if (Geom::L2(other_to_snap) > 1e-3) { - Radial rother_to_snap(other_to_snap); - /* The closest PI/2 angle, starting from the angle of the opposite line segment */ - double const a_oppo = rother_to_snap.a + floor((rnew.a - rother_to_snap.a)/(M_PI/2) + 0.5) * (M_PI/2); - - // Snap to the closest. - a_snapped = ( fabs(a_snapped - rnew.a) < fabs(a_oppo - rnew.a) - ? a_snapped - : a_oppo ); - } - } - - rnew.a = a_snapped; - } - - if (state & GDK_MOD1_MASK) { - // lock handle length - rnew.r = me->origin_radial.r; - } - - if (( n->type !=Inkscape::NodePath::NODE_CUSP || (!n->dragging_out && (state & GDK_SHIFT_MASK))) - && (rme.a != HUGE_VAL) && (rnew.a != HUGE_VAL) && ((fabs(rme.a - rnew.a) > 0.001) || (n->type ==Inkscape::NodePath::NODE_SYMM))) { - // rotate the other handle correspondingly, if both old and new angles exist and are not the same - rother.a += rnew.a - rme.a; - other->pos = Geom::Point(rother) + n->pos; - if (other->knot) { - sp_ctrlline_set_coords(SP_CTRLLINE(other->line), n->pos, other->pos); - sp_knot_moveto(other->knot, other->pos); - } - } - - me->pos = Geom::Point(rnew) + n->pos; - sp_ctrlline_set_coords(SP_CTRLLINE(me->line), n->pos, me->pos); - - // move knot, but without emitting the signal: - // we cannot emit a "moved" signal because we're now processing it - sp_knot_moveto(me->knot, me->pos); - - update_object(n->subpath->nodepath); - - /* status text */ - SPDesktop *desktop = n->subpath->nodepath->desktop; - if (!desktop) return; - SPEventContext *ec = desktop->event_context; - if (!ec) return; - - Inkscape::MessageContext *mc = get_message_context(ec); - - if (!mc) return; - - double degrees = 180 / M_PI * rnew.a; - if (degrees > 180) degrees -= 360; - if (degrees < -180) degrees += 360; - if (prefs->getBool("/options/compassangledisplay/value")) - degrees = angle_to_compass (degrees); - - GString *length = SP_PX_TO_METRIC_STRING(rnew.r, desktop->namedview->getDefaultMetric()); - - mc->setF(Inkscape::IMMEDIATE_MESSAGE, - _("<b>Node handle</b>: angle %0.2f°, length %s; with <b>Ctrl</b> to snap angle; with <b>Alt</b> to lock length; with <b>Shift</b> to rotate both handles"), degrees, length->str); - - g_string_free(length, TRUE); -} - -/** - * Node handle event callback. - */ -static gboolean node_handle_event(SPKnot *knot, GdkEvent *event,Inkscape::NodePath::Node *n) -{ - gboolean ret = FALSE; - switch (event->type) { - case GDK_KEY_PRESS: - switch (get_group0_keyval (&event->key)) { - case GDK_space: - if (event->key.state & GDK_BUTTON1_MASK) { - Inkscape::NodePath::Path *nodepath = n->subpath->nodepath; - stamp_repr(nodepath); - ret = TRUE; - } - break; - default: - break; - } - break; - case GDK_ENTER_NOTIFY: - // we use an experimentally determined threshold that seems to work fine - if (Geom::L2(n->pos - knot->pos) < 0.75) - Inkscape::NodePath::Path::active_node = n; - break; - case GDK_LEAVE_NOTIFY: - // we use an experimentally determined threshold that seems to work fine - if (Geom::L2(n->pos - knot->pos) < 0.75) - Inkscape::NodePath::Path::active_node = NULL; - break; - default: - break; - } - - return ret; -} - -static void node_rotate_one_internal(Inkscape::NodePath::Node const &n, gdouble const angle, - Radial &rme, Radial &rother, gboolean const both) -{ - rme.a += angle; - if ( both - || ( n.type == Inkscape::NodePath::NODE_SMOOTH ) - || ( n.type == Inkscape::NodePath::NODE_SYMM ) ) - { - rother.a += angle; - } -} - -static void node_rotate_one_internal_screen(Inkscape::NodePath::Node const &n, gdouble const angle, - Radial &rme, Radial &rother, gboolean const both) -{ - gdouble const norm_angle = angle / n.subpath->nodepath->desktop->current_zoom(); - - gdouble r; - if ( both - || ( n.type == Inkscape::NodePath::NODE_SMOOTH ) - || ( n.type == Inkscape::NodePath::NODE_SYMM ) ) - { - r = MAX(rme.r, rother.r); - } else { - r = rme.r; - } - - gdouble const weird_angle = atan2(norm_angle, r); -/* Bulia says norm_angle is just the visible distance that the - * object's end must travel on the screen. Left as 'angle' for want of - * a better name.*/ - - rme.a += weird_angle; - if ( both - || ( n.type == Inkscape::NodePath::NODE_SMOOTH ) - || ( n.type == Inkscape::NodePath::NODE_SYMM ) ) - { - rother.a += weird_angle; - } -} - -/** - * Rotate one node. - */ -static void node_rotate_one (Inkscape::NodePath::Node *n, gdouble angle, int which, gboolean screen) -{ - Inkscape::NodePath::NodeSide *me, *other; - bool both = false; - - double xn = n->n.other? n->n.other->pos[Geom::X] : n->pos[Geom::X]; - double xp = n->p.other? n->p.other->pos[Geom::X] : n->pos[Geom::X]; - - if (!n->n.other) { // if this is an endnode, select its single handle regardless of "which" - me = &(n->p); - other = &(n->n); - } else if (!n->p.other) { - me = &(n->n); - other = &(n->p); - } else { - if (which > 0) { // right handle - if (xn > xp) { - me = &(n->n); - other = &(n->p); - } else { - me = &(n->p); - other = &(n->n); - } - } else if (which < 0){ // left handle - if (xn <= xp) { - me = &(n->n); - other = &(n->p); - } else { - me = &(n->p); - other = &(n->n); - } - } else { // both handles - me = &(n->n); - other = &(n->p); - both = true; - } - } - - Radial rme(me->pos - n->pos); - Radial rother(other->pos - n->pos); - - if (screen) { - node_rotate_one_internal_screen (*n, angle, rme, rother, both); - } else { - node_rotate_one_internal (*n, angle, rme, rother, both); - } - - me->pos = n->pos + Geom::Point(rme); - - if (both || n->type == Inkscape::NodePath::NODE_SMOOTH || n->type == Inkscape::NodePath::NODE_SYMM) { - other->pos = n->pos + Geom::Point(rother); - } - - // this function is only called from sp_nodepath_selected_nodes_rotate that will update display at the end, - // so here we just move all the knots without emitting move signals, for speed - sp_node_update_handles(n, false); -} - -/** - * Rotate selected nodes. - */ -void sp_nodepath_selected_nodes_rotate(Inkscape::NodePath::Path *nodepath, gdouble angle, int which, bool screen) -{ - if (!nodepath || !nodepath->selected) return; - - if (g_list_length(nodepath->selected) == 1) { - Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data; - node_rotate_one (n, angle, which, screen); - } else { - // rotate as an object: - - Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data; - Geom::Rect box (n0->pos, n0->pos); // originally includes the first selected node - for (GList *l = nodepath->selected; l != NULL; l = l->next) { - Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data; - box.expandTo (n->pos); // contain all selected nodes - } - - gdouble rot; - if (screen) { - gdouble const zoom = nodepath->desktop->current_zoom(); - gdouble const zmove = angle / zoom; - gdouble const r = Geom::L2(box.max() - box.midpoint()); - rot = atan2(zmove, r); - } else { - rot = angle; - } - - Geom::Point rot_center; - if (Inkscape::NodePath::Path::active_node == NULL) - rot_center = box.midpoint(); - else - rot_center = Inkscape::NodePath::Path::active_node->pos; - - Geom::Matrix t = - Geom::Matrix (Geom::Translate(-rot_center)) * - Geom::Matrix (Geom::Rotate(rot)) * - Geom::Matrix (Geom::Translate(rot_center)); - - for (GList *l = nodepath->selected; l != NULL; l = l->next) { - Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data; - n->pos *= t; - n->n.pos *= t; - n->p.pos *= t; - sp_node_update_handles(n, false); - } - } - - sp_nodepath_update_repr_keyed(nodepath, angle > 0 ? "nodes:rot:p" : "nodes:rot:n", _("Rotate nodes")); -} - -/** - * Scale one node. - */ -static void node_scale_one (Inkscape::NodePath::Node *n, gdouble grow, int which) -{ - bool both = false; - Inkscape::NodePath::NodeSide *me, *other; - - double xn = n->n.other? n->n.other->pos[Geom::X] : n->pos[Geom::X]; - double xp = n->p.other? n->p.other->pos[Geom::X] : n->pos[Geom::X]; - - if (!n->n.other) { // if this is an endnode, select its single handle regardless of "which" - me = &(n->p); - other = &(n->n); - n->code = NR_CURVETO; - } else if (!n->p.other) { - me = &(n->n); - other = &(n->p); - if (n->n.other) - n->n.other->code = NR_CURVETO; - } else { - if (which > 0) { // right handle - if (xn > xp) { - me = &(n->n); - other = &(n->p); - if (n->n.other) - n->n.other->code = NR_CURVETO; - } else { - me = &(n->p); - other = &(n->n); - n->code = NR_CURVETO; - } - } else if (which < 0){ // left handle - if (xn <= xp) { - me = &(n->n); - other = &(n->p); - if (n->n.other) - n->n.other->code = NR_CURVETO; - } else { - me = &(n->p); - other = &(n->n); - n->code = NR_CURVETO; - } - } else { // both handles - me = &(n->n); - other = &(n->p); - both = true; - n->code = NR_CURVETO; - if (n->n.other) - n->n.other->code = NR_CURVETO; - } - } - - Radial rme(me->pos - n->pos); - Radial rother(other->pos - n->pos); - - rme.r += grow; - if (rme.r < 0) rme.r = 0; - if (rme.a == HUGE_VAL) { - if (me->other) { // if direction is unknown, initialize it towards the next node - Radial rme_next(me->other->pos - n->pos); - rme.a = rme_next.a; - } else { // if there's no next, initialize to 0 - rme.a = 0; - } - } - if (both || n->type == Inkscape::NodePath::NODE_SYMM) { - rother.r += grow; - if (rother.r < 0) rother.r = 0; - if (rother.a == HUGE_VAL) { - rother.a = rme.a + M_PI; - } - } - - me->pos = n->pos + Geom::Point(rme); - - if (both || n->type == Inkscape::NodePath::NODE_SYMM) { - other->pos = n->pos + Geom::Point(rother); - } - - // this function is only called from sp_nodepath_selected_nodes_scale that will update display at the end, - // so here we just move all the knots without emitting move signals, for speed - sp_node_update_handles(n, false); -} - -/** - * Scale selected nodes. - */ -void sp_nodepath_selected_nodes_scale(Inkscape::NodePath::Path *nodepath, gdouble const grow, int const which) -{ - if (!nodepath || !nodepath->selected) return; - - if (g_list_length(nodepath->selected) == 1) { - // scale handles of the single selected node - Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data; - node_scale_one (n, grow, which); - } else { - // scale nodes as an "object": - - Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data; - Geom::Rect box (n0->pos, n0->pos); // originally includes the first selected node - for (GList *l = nodepath->selected; l != NULL; l = l->next) { - Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data; - box.expandTo (n->pos); // contain all selected nodes - } - - if ( Geom::are_near(box.maxExtent(), 0) ) { - SPEventContext *ec = nodepath->desktop->event_context; - if (!ec) return; - Inkscape::MessageContext *mc = get_message_context(ec); - if (!mc) return; - mc->setF(Inkscape::WARNING_MESSAGE, - _("Cannot scale nodes when all are at the same location.")); - return; - } - double scale = (box.maxExtent() + grow)/box.maxExtent(); - - - Geom::Point scale_center; - if (Inkscape::NodePath::Path::active_node == NULL) - scale_center = box.midpoint(); - else - scale_center = Inkscape::NodePath::Path::active_node->pos; - - Geom::Matrix t = - Geom::Translate(-scale_center) * - Geom::Scale(scale, scale) * - Geom::Translate(scale_center); - - for (GList *l = nodepath->selected; l != NULL; l = l->next) { - Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data; - n->pos *= t; - n->n.pos *= t; - n->p.pos *= t; - sp_node_update_handles(n, false); - } - } - - sp_nodepath_update_repr_keyed(nodepath, grow > 0 ? "nodes:scale:p" : "nodes:scale:n", _("Scale nodes")); -} - -void sp_nodepath_selected_nodes_scale_screen(Inkscape::NodePath::Path *nodepath, gdouble const grow, int const which) -{ - if (!nodepath) return; - sp_nodepath_selected_nodes_scale(nodepath, grow / nodepath->desktop->current_zoom(), which); -} - -/** - * Flip selected nodes horizontally/vertically. - */ -void sp_nodepath_flip (Inkscape::NodePath::Path *nodepath, Geom::Dim2 axis, boost::optional<Geom::Point> center) -{ - if (!nodepath || !nodepath->selected) return; - - if (g_list_length(nodepath->selected) == 1 && !center) { - // flip handles of the single selected node - Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nodepath->selected->data; - double temp = n->p.pos[axis]; - n->p.pos[axis] = n->n.pos[axis]; - n->n.pos[axis] = temp; - sp_node_update_handles(n, false); - } else { - // scale nodes as an "object": - - Geom::Rect box = sp_node_selected_bbox (nodepath); - if (!center) { - center = box.midpoint(); - } - Geom::Matrix t = - Geom::Matrix (Geom::Translate(- *center)) * - Geom::Matrix ((axis == Geom::X)? Geom::Scale(-1, 1) : Geom::Scale(1, -1)) * - Geom::Matrix (Geom::Translate(*center)); - - for (GList *l = nodepath->selected; l != NULL; l = l->next) { - Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data; - n->pos *= t; - n->n.pos *= t; - n->p.pos *= t; - sp_node_update_handles(n, false); - } - } - - sp_nodepath_update_repr(nodepath, _("Flip nodes")); -} - -Geom::Rect sp_node_selected_bbox (Inkscape::NodePath::Path *nodepath) -{ - g_assert (nodepath->selected); - - Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data; - Geom::Rect box (n0->pos, n0->pos); // originally includes the first selected node - for (GList *l = nodepath->selected; l != NULL; l = l->next) { - Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data; - box.expandTo (n->pos); // contain all selected nodes - } - return box; -} - -//----------------------------------------------- -/** - * Return new subpath under given nodepath. - */ -static Inkscape::NodePath::SubPath *sp_nodepath_subpath_new(Inkscape::NodePath::Path *nodepath) -{ - g_assert(nodepath); - g_assert(nodepath->desktop); - - Inkscape::NodePath::SubPath *s = g_new(Inkscape::NodePath::SubPath, 1); - - s->nodepath = nodepath; - s->closed = FALSE; - s->nodes = NULL; - s->first = NULL; - s->last = NULL; - - // using prepend here saves up to 10% of time on paths with many subpaths, but requires that - // the caller reverses the list after it's ready (this is done in sp_nodepath_new) - nodepath->subpaths = g_list_prepend (nodepath->subpaths, s); - - return s; -} - -/** - * Destroy nodes in subpath, then subpath itself. - */ -static void sp_nodepath_subpath_destroy(Inkscape::NodePath::SubPath *subpath) -{ - g_assert(subpath); - g_assert(subpath->nodepath); - g_assert(g_list_find(subpath->nodepath->subpaths, subpath)); - - while (subpath->nodes) { - sp_nodepath_node_destroy((Inkscape::NodePath::Node *) subpath->nodes->data); - } - - subpath->nodepath->subpaths = g_list_remove(subpath->nodepath->subpaths, subpath); - - g_free(subpath); -} - -/** - * Link head to tail in subpath. - */ -static void sp_nodepath_subpath_close(Inkscape::NodePath::SubPath *sp) -{ - g_assert(!sp->closed); - g_assert(sp->last != sp->first); - g_assert(sp->first->code == NR_MOVETO); - - sp->closed = TRUE; - - //Link the head to the tail - sp->first->p.other = sp->last; - sp->last->n.other = sp->first; - sp->last->n.pos = sp->last->pos + (sp->first->n.pos - sp->first->pos); - sp->first = sp->last; - - //Remove the extra end node - sp_nodepath_node_destroy(sp->last->n.other); -} - -/** - * Open closed (loopy) subpath at node. - */ -static void sp_nodepath_subpath_open(Inkscape::NodePath::SubPath *sp,Inkscape::NodePath::Node *n) -{ - g_assert(sp->closed); - g_assert(n->subpath == sp); - g_assert(sp->first == sp->last); - - /* We create new startpoint, current node will become last one */ - - Inkscape::NodePath::Node *new_path = sp_nodepath_node_new(sp, n->n.other,Inkscape::NodePath::NODE_CUSP, NR_MOVETO, - &n->pos, &n->pos, &n->n.pos); - - - sp->closed = FALSE; - - //Unlink to make a head and tail - sp->first = new_path; - sp->last = n; - n->n.other = NULL; - new_path->p.other = NULL; -} - -/** - * Return new node in subpath with given properties. - * \param pos Position of node. - * \param ppos Handle position in previous direction - * \param npos Handle position in previous direction - */ -Inkscape::NodePath::Node * -sp_nodepath_node_new(Inkscape::NodePath::SubPath *sp, Inkscape::NodePath::Node *next, Inkscape::NodePath::NodeType type, NRPathcode code, Geom::Point *ppos, Geom::Point *pos, Geom::Point *npos) -{ - g_assert(sp); - g_assert(sp->nodepath); - g_assert(sp->nodepath->desktop); - - if (nodechunk == NULL) - nodechunk = g_mem_chunk_create(Inkscape::NodePath::Node, 32, G_ALLOC_AND_FREE); - - Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node*)g_mem_chunk_alloc(nodechunk); - - n->subpath = sp; - - if (type != Inkscape::NodePath::NODE_NONE) { - // use the type from sodipodi:nodetypes - n->type = type; - } else { - if (fabs (Inkscape::Util::triangle_area (*pos, *ppos, *npos)) < 1e-2) { - // points are (almost) collinear - if (Geom::L2(*pos - *ppos) < 1e-6 || Geom::L2(*pos - *npos) < 1e-6) { - // endnode, or a node with a retracted handle - n->type = Inkscape::NodePath::NODE_CUSP; - } else { - n->type = Inkscape::NodePath::NODE_SMOOTH; - } - } else { - n->type = Inkscape::NodePath::NODE_CUSP; - } - } - - n->code = code; - n->selected = FALSE; - n->pos = *pos; - n->p.pos = *ppos; - n->n.pos = *npos; - - n->dragging_out = NULL; - - Inkscape::NodePath::Node *prev; - if (next) { - //g_assert(g_list_find(sp->nodes, next)); - prev = next->p.other; - } else { - prev = sp->last; - } - - if (prev) - prev->n.other = n; - else - sp->first = n; - - if (next) - next->p.other = n; - else - sp->last = n; - - n->p.other = prev; - n->n.other = next; - - n->knot = sp_knot_new(sp->nodepath->desktop, _("<b>Node</b>: drag to edit the path; with <b>Ctrl</b> to snap to horizontal/vertical; with <b>Ctrl+Alt</b> to snap to handles' directions")); - sp_knot_set_position(n->knot, *pos, 0); - - n->knot->setAnchor (GTK_ANCHOR_CENTER); - n->knot->setFill(NODE_FILL, NODE_FILL_HI, NODE_FILL_HI); - n->knot->setStroke(NODE_STROKE, NODE_STROKE_HI, NODE_STROKE_HI); - - sp_nodepath_update_node_knot(n); - - g_signal_connect(G_OBJECT(n->knot), "event", G_CALLBACK(node_event), n); - g_signal_connect(G_OBJECT(n->knot), "clicked", G_CALLBACK(node_clicked), n); - g_signal_connect(G_OBJECT(n->knot), "grabbed", G_CALLBACK(node_grabbed), n); - g_signal_connect(G_OBJECT(n->knot), "ungrabbed", G_CALLBACK(node_ungrabbed), n); - g_signal_connect(G_OBJECT(n->knot), "request", G_CALLBACK(node_request), n); - sp_knot_show(n->knot); - - // We only create handle knots and lines on demand - n->p.knot = NULL; - n->p.line = NULL; - n->n.knot = NULL; - n->n.line = NULL; - - sp->nodes = g_list_prepend(sp->nodes, n); - - return n; -} - -/** - * Destroy node and its knots, link neighbors in subpath. - */ -static void sp_nodepath_node_destroy(Inkscape::NodePath::Node *node) -{ - g_assert(node); - g_assert(node->subpath); - g_assert(SP_IS_KNOT(node->knot)); - - Inkscape::NodePath::SubPath *sp = node->subpath; - - if (node->selected) { // first, deselect - g_assert(g_list_find(node->subpath->nodepath->selected, node)); - node->subpath->nodepath->selected = g_list_remove(node->subpath->nodepath->selected, node); - } - - node->subpath->nodes = g_list_remove(node->subpath->nodes, node); - - g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_event), node); - g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_clicked), node); - g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_grabbed), node); - g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_ungrabbed), node); - g_signal_handlers_disconnect_by_func(G_OBJECT(node->knot), (gpointer) G_CALLBACK(node_request), node); - g_object_unref(G_OBJECT(node->knot)); - - if (node->p.knot) { - g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_clicked), node); - g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_grabbed), node); - g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_ungrabbed), node); - g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_request), node); - g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_moved), node); - g_signal_handlers_disconnect_by_func(G_OBJECT(node->p.knot), (gpointer) G_CALLBACK(node_handle_event), node); - g_object_unref(G_OBJECT(node->p.knot)); - node->p.knot = NULL; - } - - if (node->n.knot) { - g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_clicked), node); - g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_grabbed), node); - g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_ungrabbed), node); - g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_request), node); - g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_moved), node); - g_signal_handlers_disconnect_by_func(G_OBJECT(node->n.knot), (gpointer) G_CALLBACK(node_handle_event), node); - g_object_unref(G_OBJECT(node->n.knot)); - node->n.knot = NULL; - } - - if (node->p.line) - gtk_object_destroy(GTK_OBJECT(node->p.line)); - if (node->n.line) - gtk_object_destroy(GTK_OBJECT(node->n.line)); - - if (sp->nodes) { // there are others nodes on the subpath - if (sp->closed) { - if (sp->first == node) { - g_assert(sp->last == node); - sp->first = node->n.other; - sp->last = sp->first; - } - node->p.other->n.other = node->n.other; - node->n.other->p.other = node->p.other; - } else { - if (sp->first == node) { - sp->first = node->n.other; - sp->first->code = NR_MOVETO; - } - if (sp->last == node) sp->last = node->p.other; - if (node->p.other) node->p.other->n.other = node->n.other; - if (node->n.other) node->n.other->p.other = node->p.other; - } - } else { // this was the last node on subpath - sp->nodepath->subpaths = g_list_remove(sp->nodepath->subpaths, sp); - } - - g_mem_chunk_free(nodechunk, node); -} - -/** - * Returns one of the node's two sides. - * \param which Indicates which side. - * \return Pointer to previous node side if which==-1, next if which==1. - */ -static Inkscape::NodePath::NodeSide *sp_node_get_side(Inkscape::NodePath::Node *node, gint which) -{ - g_assert(node); - Inkscape::NodePath::NodeSide * result = 0; - switch (which) { - case -1: - result = &node->p; - break; - case 1: - result = &node->n; - break; - default: - g_assert_not_reached(); - } - - return result; -} - -/** - * Return the other side of the node, given one of its sides. - */ -static Inkscape::NodePath::NodeSide *sp_node_opposite_side(Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeSide *me) -{ - g_assert(node); - Inkscape::NodePath::NodeSide *result = 0; - - if (me == &node->p) { - result = &node->n; - } else if (me == &node->n) { - result = &node->p; - } else { - g_assert_not_reached(); - } - - return result; -} - -/** - * Return NRPathcode on the given side of the node. - */ -static NRPathcode sp_node_path_code_from_side(Inkscape::NodePath::Node *node,Inkscape::NodePath::NodeSide *me) -{ - g_assert(node); - - NRPathcode result = NR_END; - if (me == &node->p) { - if (node->p.other) { - result = (NRPathcode)node->code; - } else { - result = NR_MOVETO; - } - } else if (me == &node->n) { - if (node->n.other) { - result = (NRPathcode)node->n.other->code; - } else { - result = NR_MOVETO; - } - } else { - g_assert_not_reached(); - } - - return result; -} - -/** - * Return node with the given index - */ -Inkscape::NodePath::Node * -sp_nodepath_get_node_by_index(Inkscape::NodePath::Path *nodepath, int index) -{ - Inkscape::NodePath::Node *e = NULL; - - if (!nodepath) { - return e; - } - - //find segment - for (GList *l = nodepath->subpaths; l ; l=l->next) { - - Inkscape::NodePath::SubPath *sp = (Inkscape::NodePath::SubPath *)l->data; - int n = g_list_length(sp->nodes); - if (sp->closed) { - n++; - } - - //if the piece belongs to this subpath grab it - //otherwise move onto the next subpath - if (index < n) { - e = sp->first; - for (int i = 0; i < index; ++i) { - e = e->n.other; - } - break; - } else { - if (sp->closed) { - index -= (n+1); - } else { - index -= n; - } - } - } - - return e; -} - -/** - * Returns plain text meaning of node type. - */ -static gchar const *sp_node_type_description(Inkscape::NodePath::Node *node) -{ - unsigned retracted = 0; - bool endnode = false; - - for (int which = -1; which <= 1; which += 2) { - Inkscape::NodePath::NodeSide *side = sp_node_get_side(node, which); - if (side->other && Geom::L2(side->pos - node->pos) < 1e-6) - retracted ++; - if (!side->other) - endnode = true; - } - - if (retracted == 0) { - if (endnode) { - // TRANSLATORS: "end" is an adjective here (NOT a verb) - return _("end node"); - } else { - switch (node->type) { - case Inkscape::NodePath::NODE_CUSP: - // TRANSLATORS: "cusp" means "sharp" (cusp node); see also the Advanced Tutorial - return _("cusp"); - case Inkscape::NodePath::NODE_SMOOTH: - // TRANSLATORS: "smooth" is an adjective here - return _("smooth"); - case Inkscape::NodePath::NODE_AUTO: - return _("auto"); - case Inkscape::NodePath::NODE_SYMM: - return _("symmetric"); - } - } - } else if (retracted == 1) { - if (endnode) { - // TRANSLATORS: "end" is an adjective here (NOT a verb) - return _("end node, handle retracted (drag with <b>Shift</b> to extend)"); - } else { - return _("one handle retracted (drag with <b>Shift</b> to extend)"); - } - } else { - return _("both handles retracted (drag with <b>Shift</b> to extend)"); - } - - return NULL; -} - -/** - * Handles content of statusbar as long as node tool is active. - */ -void -sp_nodepath_update_statusbar(Inkscape::NodePath::Path *nodepath)//!!!move to ShapeEditorsCollection -{ - gchar const *when_selected = _("<b>Drag</b> nodes or node handles; <b>Alt+drag</b> nodes to sculpt; <b>arrow</b> keys to move nodes, <b>< ></b> to scale, <b>[ ]</b> to rotate"); - gchar const *when_selected_one = _("<b>Drag</b> the node or its handles; <b>arrow</b> keys to move the node"); - - gint total_nodes = sp_nodepath_get_node_count(nodepath); - gint selected_nodes = sp_nodepath_selection_get_node_count(nodepath); - gint total_subpaths = sp_nodepath_get_subpath_count(nodepath); - gint selected_subpaths = sp_nodepath_selection_get_subpath_count(nodepath); - - SPDesktop *desktop = NULL; - if (nodepath) { - desktop = nodepath->desktop; - } else { - desktop = SP_ACTIVE_DESKTOP; // when this is eliminated also remove #include "inkscape.h" above - } - - SPEventContext *ec = desktop->event_context; - if (!ec) return; - - Inkscape::MessageContext *mc = get_message_context(ec); - if (!mc) return; - - inkscape_active_desktop()->emitToolSubselectionChanged(NULL); - - if (selected_nodes == 0) { - Inkscape::Selection *sel = desktop->selection; - if (!sel || sel->isEmpty()) { - mc->setF(Inkscape::NORMAL_MESSAGE, - _("Select a single object to edit its nodes or handles.")); - } else { - if (nodepath) { - mc->setF(Inkscape::NORMAL_MESSAGE, - ngettext("<b>0</b> out of <b>%i</b> node selected. <b>Click</b>, <b>Shift+click</b>, or <b>drag around</b> nodes to select.", - "<b>0</b> out of <b>%i</b> nodes selected. <b>Click</b>, <b>Shift+click</b>, or <b>drag around</b> nodes to select.", - total_nodes), - total_nodes); - } else { - if (g_slist_length((GSList *)sel->itemList()) == 1) { - mc->setF(Inkscape::NORMAL_MESSAGE, _("Drag the handles of the object to modify it.")); - } else { - mc->setF(Inkscape::NORMAL_MESSAGE, _("Select a single object to edit its nodes or handles.")); - } - } - } - } else if (nodepath && selected_nodes == 1) { - mc->setF(Inkscape::NORMAL_MESSAGE, - ngettext("<b>%i</b> of <b>%i</b> node selected; %s. %s.", - "<b>%i</b> of <b>%i</b> nodes selected; %s. %s.", - total_nodes), - selected_nodes, total_nodes, sp_node_type_description((Inkscape::NodePath::Node *) nodepath->selected->data), when_selected_one); - } else { - if (selected_subpaths > 1) { - mc->setF(Inkscape::NORMAL_MESSAGE, - ngettext("<b>%i</b> of <b>%i</b> node selected in <b>%i</b> of <b>%i</b> subpaths. %s.", - "<b>%i</b> of <b>%i</b> nodes selected in <b>%i</b> of <b>%i</b> subpaths. %s.", - total_nodes), - selected_nodes, total_nodes, selected_subpaths, total_subpaths, when_selected); - } else { - mc->setF(Inkscape::NORMAL_MESSAGE, - ngettext("<b>%i</b> of <b>%i</b> node selected. %s.", - "<b>%i</b> of <b>%i</b> nodes selected. %s.", - total_nodes), - selected_nodes, total_nodes, when_selected); - } - } -} - -/* - * returns a *copy* of the curve of that object. - */ -SPCurve* sp_nodepath_object_get_curve(SPObject *object, const gchar *key) { - if (!object) - return NULL; - - SPCurve *curve = NULL; - if (SP_IS_PATH(object)) { - SPCurve *curve_new = sp_path_get_curve_for_edit(SP_PATH(object)); - curve = curve_new->copy(); - } else if ( IS_LIVEPATHEFFECT(object) && key) { - const gchar *svgd = object->repr->attribute(key); - if (svgd) { - Geom::PathVector pv = sp_svg_read_pathv(svgd); - SPCurve *curve_new = new SPCurve(pv); - if (curve_new) { - curve = curve_new; // don't do curve_copy because curve_new is already only created for us! - } - } - } - - return curve; -} - -void sp_nodepath_set_curve (Inkscape::NodePath::Path *np, SPCurve *curve) { - if (!np || !np->object || !curve) - return; - - if (SP_IS_PATH(np->object)) { - if (sp_lpe_item_has_path_effect_recursive(SP_LPE_ITEM(np->object))) { - sp_path_set_original_curve(SP_PATH(np->object), curve, true, false); - } else { - sp_shape_set_curve(SP_SHAPE(np->object), curve, true); - } - } else if ( IS_LIVEPATHEFFECT(np->object) ) { - Inkscape::LivePathEffect::Effect * lpe = LIVEPATHEFFECT(np->object)->get_lpe(); - if (lpe) { - Inkscape::LivePathEffect::PathParam *pathparam = dynamic_cast<Inkscape::LivePathEffect::PathParam *>( lpe->getParameter(np->repr_key) ); - if (pathparam) { - pathparam->set_new_value(np->curve->get_pathvector(), false); // do not write to SVG - np->object->requestModified(SP_OBJECT_MODIFIED_FLAG); - } - } - } -} - -/* -SPCanvasItem * -sp_nodepath_path_to_canvasitem(Inkscape::NodePath::Path *np, SPPath *path) { - return sp_nodepath_make_helper_item(np, sp_path_get_curve_for_edit(path)); -} -*/ - - -/// \todo this code to generate a helper canvasitem from an spcurve should be moved to different file -SPCanvasItem * -sp_nodepath_generate_helperpath(SPDesktop *desktop, SPCurve *curve, const Geom::Matrix & i2d, guint32 color = 0xff0000ff) { - SPCurve *flash_curve = curve->copy(); - flash_curve->transform(i2d); - SPCanvasItem * canvasitem = sp_canvas_bpath_new(sp_desktop_tempgroup(desktop), flash_curve); - // would be nice if its color could be XORed or something, now it is invisible for red stroked objects... - // unless we also flash the nodes... - sp_canvas_bpath_set_stroke(SP_CANVAS_BPATH(canvasitem), color, 1.0, SP_STROKE_LINEJOIN_MITER, SP_STROKE_LINECAP_BUTT); - sp_canvas_bpath_set_fill(SP_CANVAS_BPATH(canvasitem), 0, SP_WIND_RULE_NONZERO); - sp_canvas_item_show(canvasitem); - flash_curve->unref(); - return canvasitem; -} - -SPCanvasItem * -sp_nodepath_generate_helperpath(SPDesktop *desktop, SPItem *item) { - if (!item || !desktop) { - return NULL; - } - - Inkscape::Preferences *prefs = Inkscape::Preferences::get(); - guint32 color = prefs->getInt("/tools/nodes/highlight_color", 0xff0000ff); - - Geom::Matrix i2d = sp_item_i2d_affine(item); - - SPCurve *curve = NULL; - if (SP_IS_PATH(item)) { - curve = sp_path_get_curve_for_edit(SP_PATH(item)); - } else if ( SP_IS_SHAPE(item) && SP_SHAPE(item)->curve ) { - curve = sp_shape_get_curve (SP_SHAPE(item)); - } else if ( SP_IS_TEXT(item) ) { - // do not display helperpath for text - we cannot do anything with it in Node tool anyway - // curve = SP_TEXT(item)->getNormalizedBpath(); - return NULL; - } else { - g_warning ("-----> sp_nodepath_generate_helperpath(SPDesktop *desktop, SPItem *item): TODO: generate the helper path for this item type!\n"); - return NULL; - } - - SPCanvasItem * helperpath = sp_nodepath_generate_helperpath(desktop, curve, i2d, color); - - curve->unref(); - - return helperpath; -} - - -// TODO: Merge this with sp_nodepath_make_helper_item()! -void sp_nodepath_show_helperpath(Inkscape::NodePath::Path *np, bool show) { - np->show_helperpath = show; - - if (show) { - SPCurve *helper_curve = np->curve->copy(); - helper_curve->transform(np->i2d); - if (!np->helper_path) { - //np->helper_path = sp_nodepath_make_helper_item(np, desktop, helper_curve, true); // Caution: this applies the transform np->i2d twice!! - - np->helper_path = sp_canvas_bpath_new(sp_desktop_controls(np->desktop), helper_curve); - sp_canvas_bpath_set_stroke(SP_CANVAS_BPATH(np->helper_path), np->helperpath_rgba, np->helperpath_width, SP_STROKE_LINEJOIN_MITER, SP_STROKE_LINECAP_BUTT); - sp_canvas_bpath_set_fill(SP_CANVAS_BPATH(np->helper_path), 0, SP_WIND_RULE_NONZERO); - sp_canvas_item_move_to_z(np->helper_path, 0); - sp_canvas_item_show(np->helper_path); - } else { - sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(np->helper_path), helper_curve); - } - helper_curve->unref(); - } else { - if (np->helper_path) { - GtkObject *temp = np->helper_path; - np->helper_path = NULL; - gtk_object_destroy(temp); - } - } -} - -/* sp_nodepath_make_straight_path: - * Prevents user from curving the path by dragging a segment or activating handles etc. - * The resulting path is a linear interpolation between nodal points, with only straight segments. - * !!! this function does not work completely yet: it does not actively straighten the path, only prevents the path from being curved - */ -void sp_nodepath_make_straight_path(Inkscape::NodePath::Path *np) { - np->straight_path = true; - np->show_handles = false; - g_message("add code to make the path straight."); - // do sp_nodepath_convert_node_type on all nodes? - // coding tip: search for this text : "Make selected segments lines" -} - -/* - 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/nodepath.h b/src/nodepath.h deleted file mode 100644 index 1dcb4527c..000000000 --- a/src/nodepath.h +++ /dev/null @@ -1,345 +0,0 @@ -/** @file - * @brief Path handler in node edit mode - */ -/* Authors: - * Lauris Kaplinski <lauris@kaplinski.com> - * - * This code is in public domain - */ - -#ifndef SEEN_SP_NODEPATH_H -#define SEEN_SP_NODEPATH_H - -#include "libnr/nr-path-code.h" -#include <glibmm/ustring.h> -#include <gdk/gdkevents.h> -#include <list> -#include <2geom/point.h> -#include <2geom/matrix.h> -#include <boost/optional.hpp> - - -struct SPCanvasItem; -class SPCurve; -struct SPItem; -class SPObject; -class SPDesktop; -class SPPath; -class SPKnot; -class LivePathEffectObject; - -namespace Inkscape { - namespace XML { - class Node; - } - - namespace LivePathEffect { - class Effect; - } -} - -typedef std::map<Inkscape::LivePathEffect::Effect *, std::vector<SPCanvasItem *> > HelperPathList; - -/** - * Radial objects are represented by an angle and a distance from - * 0,0. 0,0 is represented by a == big_num. - */ -class Radial{ - public: -/** Radius */ - double r; -/** Amplitude */ - double a; - Radial() {} - // Radial(Geom::Point const &p); // Convert a point to radial coordinates - Radial(Radial &p) : r(p.r),a(p.a) {} - // operator Geom::Point() const; - -/** - * Construct Radial from Geom::Point. - */ -Radial(Geom::Point const &p) -{ - r = Geom::L2(p); - if (r > 0) { - a = Geom::atan2 (p); - } else { - a = HUGE_VAL; //undefined - } -} - -/** - * Cast Radial to cartesian Geom::Point. - */ -operator Geom::Point() const -{ - if (a == HUGE_VAL) { - return Geom::Point(0,0); - } else { - return r*Geom::Point(cos(a), sin(a)); - } -} - -}; - -class ShapeEditor; - -namespace Inkscape { -namespace NodePath { - -/** - * The entire nodepath, containing multiple subpaths - */ -class Path; - -/** - * A subpath is a continuous chain of linked nodes - */ -class SubPath; - -/** - * One side of a node, i.e. prev or next - */ -class NodeSide; - -/** - * A node on a subpath - */ -class Node; - - -/** - * This is the lowest list item, a simple list of nodes. - */ -class SubPath { - public: -/** The parent of this subpath */ - Path * nodepath; -/** Is this path closed (no endpoints) or not?*/ - gboolean closed; -/** The nodes in this subpath. */ - GList * nodes; -/** The first node of the subpath (does not imply open/closed)*/ - Node * first; -/** The last node of the subpath */ - Node * last; -}; - - - -/** - * What kind of node is this? This is the value for the node->type - * field. NodeType indicates the degree of continuity required for - * the node. I think that the corresponding integer indicates which - * derivate is connected. (Thus 2 means that the node is continuous - * to the second derivative, i.e. has matching endpoints and tangents) - */ -typedef enum { -/** A normal node */ - NODE_NONE, -/** This node non-continuously joins two segments.*/ - NODE_CUSP, -/** This node continuously joins two segments. */ - NODE_SMOOTH, -/** This node has automatic handles. */ - NODE_AUTO, -/** This node is symmetric. */ - NODE_SYMM -} NodeType; - - - -/** - * A NodeSide is a datarecord which may be on either side (n or p) of a node, - * which describes the segment going to the next node. - */ -class NodeSide{ - public: -/** Pointer to the next node, */ - Node * other; -/** Position */ - Geom::Point pos; -/** Origin (while dragging) in radial notation */ - Radial origin_radial; -/** Origin (while dragging) in x/y notation */ - Geom::Point origin; -/** Knots are Inkscape's way of providing draggable points. This - * Knot is the point on the curve representing the control point in a - * bezier curve.*/ - SPKnot * knot; -/** What kind of rendering? */ - SPCanvasItem * line; -}; - -/** - * A node along a NodePath - */ -class Node { - public: -/** The parent subpath of this node */ - SubPath * subpath; -/** Type is selected from NodeType.*/ - guint type : 4; -/** Code refers to which ArtCode is used to represent the segment - * (which segment?).*/ - guint code : 4; -/** Boolean. Am I currently selected or not? */ - guint selected : 1; -/** */ - Geom::Point pos; -/** */ - Geom::Point origin; -/** Knots are Inkscape's way of providing draggable points. This - * Knot is the point on the curve representing the endpoint.*/ - SPKnot * knot; -/** The NodeSide in the 'next' direction */ - NodeSide n; -/** The NodeSide in the 'previous' direction */ - NodeSide p; - - /** The pointer to the nodeside which we are dragging out with Shift */ - NodeSide *dragging_out; - - /** Boolean. Am I being dragged? */ - guint is_dragging : 1; -}; - -/** - * This is a collection of subpaths which contain nodes - * - * In the following data model. Nodepaths are made up of subpaths which - * are comprised of nodes. - * - * Nodes are linked thus: - * \verbatim - n other - node -----> nodeside ------> node \endverbatim - */ -class Path { - public: - /** Constructor should private, people should create new nodepaths using sp_nodepath_new - * But for some reason I cannot make sp_nodepath_new a friend :-( - */ - Path() {}; - /** Destructor */ - ~Path(); - -/** Pointer to the current desktop, for reporting purposes */ - SPDesktop * desktop; -/** The parent path of this nodepath */ - SPObject * object; -/** The parent livepatheffect of this nodepath, if applicable */ - SPItem * item; -/** The context which created this nodepath. Important if this nodepath is deleted */ - ShapeEditor *shape_editor; -/** The subpaths which comprise this NodePath */ - GList * subpaths; -/** A list of nodes which are currently selected */ - GList * selected; -/** Transforms (userspace <---> virtual space? someone please describe ) - njh: I'd be guessing that these are item <-> desktop transforms.*/ - Geom::Matrix i2d, d2i; -/** The DOM node which describes this NodePath */ - Inkscape::XML::Node *repr; - gchar *repr_key; - gchar *repr_nodetypes_key; - //STL compliant method to get the selected nodes - void selection(std::list<Node *> &l); - - guint numSelected() {return (selected? g_list_length(selected) : 0);} - Geom::Point& singleSelectedCoords() {return (((Node *) selected->data)->pos);} - - /// draw a "sketch" of the path by using these variables - SPCanvasItem *helper_path; - SPCurve *curve; - bool show_helperpath; - guint32 helperpath_rgba; - gdouble helperpath_width; - - // the helperpaths provided by all LPEs (and their paramaters) of the current item - HelperPathList helper_path_vec; - - /// true if we changed repr, to tell this change from an external one such as from undo, simplify, or another desktop - unsigned int local_change; - - /// true if we're showing selected nodes' handles - bool show_handles; - - /// true if the path cannot contain curves, just straight lines - bool straight_path; - - /// active_node points to the node that is currently mouseovered (= NULL if - /// there isn't any); we also consider the node mouseovered if it is covered - /// by one of its handles and the latter is mouseovered - static Node *active_node; - - /// Location of mouse pointer when we started dragging, needed for snapping - Geom::Point drag_origin_mouse; - -}; - -} // namespace NodePath -} // namespace Inkscape - -enum { - SCULPT_PROFILE_LINEAR, - SCULPT_PROFILE_BELL, - SCULPT_PROFILE_ELLIPTIC -}; - -// Do function documentation in nodepath.cpp -Inkscape::NodePath::Path * sp_nodepath_new (SPDesktop * desktop, SPObject *object, bool show_handles, const char * repr_key = NULL, SPItem *item = NULL); -void sp_nodepath_deselect (Inkscape::NodePath::Path *nodepath); -void sp_nodepath_select_all (Inkscape::NodePath::Path *nodepath, bool invert); -void sp_nodepath_select_all_from_subpath(Inkscape::NodePath::Path *nodepath, bool invert); -void sp_nodepath_select_next (Inkscape::NodePath::Path *nodepath); -void sp_nodepath_select_prev (Inkscape::NodePath::Path *nodepath); -void sp_nodepath_select_rect (Inkscape::NodePath::Path * nodepath, Geom::Rect const &b, gboolean incremental); -GList *save_nodepath_selection (Inkscape::NodePath::Path *nodepath); -void restore_nodepath_selection (Inkscape::NodePath::Path *nodepath, GList *r); -gboolean nodepath_repr_d_changed (Inkscape::NodePath::Path * np, const char *newd); -gboolean nodepath_repr_typestr_changed (Inkscape::NodePath::Path * np, const char *newtypestr); -gboolean node_key (GdkEvent * event); -void sp_nodepath_update_repr(Inkscape::NodePath::Path *np, const gchar *annotation); -void sp_nodepath_update_statusbar (Inkscape::NodePath::Path *nodepath); -void sp_nodepath_selected_align(Inkscape::NodePath::Path *nodepath, Geom::Dim2 axis); -void sp_nodepath_selected_distribute(Inkscape::NodePath::Path *nodepath, Geom::Dim2 axis); -void sp_nodepath_select_segment_near_point(Inkscape::NodePath::Path *nodepath, Geom::Point p, bool toggle); -void sp_nodepath_add_node_near_point(Inkscape::NodePath::Path *nodepath, Geom::Point p); -void sp_nodepath_curve_drag(Inkscape::NodePath::Path *nodepath, int node, double t, Geom::Point delta); -Inkscape::NodePath::Node * sp_nodepath_get_node_by_index(Inkscape::NodePath::Path *np, int index); -bool sp_node_side_is_line (Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeSide *side); - -/* possibly private functions */ - -void sp_node_selected_add_node (Inkscape::NodePath::Path *nodepath); -void sp_node_selected_break (Inkscape::NodePath::Path *nodepath); -void sp_node_selected_duplicate (Inkscape::NodePath::Path *nodepath); -void sp_node_selected_join (Inkscape::NodePath::Path *nodepath); -void sp_node_selected_join_segment (Inkscape::NodePath::Path *nodepath); -void sp_node_delete_preserve (GList *nodes_to_delete); -void sp_node_selected_delete (Inkscape::NodePath::Path *nodepath); -void sp_node_selected_delete_segment (Inkscape::NodePath::Path *nodepath); -void sp_node_selected_set_type (Inkscape::NodePath::Path *nodepath, Inkscape::NodePath::NodeType type); -void sp_node_selected_set_line_type (Inkscape::NodePath::Path *nodepath, NRPathcode code); -void sp_node_selected_move (Inkscape::NodePath::Path *nodepath, gdouble dx, gdouble dy); -void sp_node_selected_move_screen (SPDesktop *desktop, Inkscape::NodePath::Path *nodepath, gdouble dx, gdouble dy); -void sp_node_selected_move_absolute (Inkscape::NodePath::Path *nodepath, Geom::Coord val, Geom::Dim2 axis); -Geom::Rect sp_node_selected_bbox (Inkscape::NodePath::Path *nodepath); -boost::optional<Geom::Coord> sp_node_selected_common_coord (Inkscape::NodePath::Path *nodepath, Geom::Dim2 axis); - -void sp_nodepath_show_handles(Inkscape::NodePath::Path *nodepath, bool show); -SPCanvasItem *sp_nodepath_generate_helperpath(SPDesktop *desktop, SPCurve *curve, const Geom::Matrix & i2d, guint32 color); -SPCanvasItem *sp_nodepath_generate_helperpath(SPDesktop *desktop, SPItem *item); -void sp_nodepath_show_helperpath(Inkscape::NodePath::Path *nodepath, bool show); -void sp_nodepath_update_helperpaths(Inkscape::NodePath::Path *np); -void sp_nodepath_make_straight_path(Inkscape::NodePath::Path *np); - -void sp_nodepath_selected_nodes_rotate (Inkscape::NodePath::Path * nodepath, gdouble angle, int which, bool screen); - -void sp_nodepath_selected_nodes_scale (Inkscape::NodePath::Path * nodepath, gdouble grow, int which); -void sp_nodepath_selected_nodes_scale_screen (Inkscape::NodePath::Path * nodepath, gdouble grow, int which); - -void sp_nodepath_flip (Inkscape::NodePath::Path *nodepath, Geom::Dim2 axis, boost::optional<Geom::Point> center); - -#endif diff --git a/src/preferences-skeleton.h b/src/preferences-skeleton.h index 4e5291baf..90fc85757 100644 --- a/src/preferences-skeleton.h +++ b/src/preferences-skeleton.h @@ -113,7 +113,7 @@ static char const preferences_skeleton[] = " font_sample=\"AaBbCcIiPpQq12369$\342\202\254\302\242?.;/()\"\n" " show_sample_in_list=\"1\"\n" " style=\"fill:black;fill-opacity:1;stroke:none;font-family:Bitstream Vera Sans;font-style:normal;font-weight:normal;font-size:40px;\" selcue=\"1\"/>\n" -" <eventcontext id=\"nodes\" selcue=\"1\" gradientdrag=\"1\" highlight_color=\"4278190335\" pathflash_enabled=\"1\" pathflash_unselected=\"0\" pathflash_timeout=\"500\" show_handles=\"1\" show_helperpath=\"0\" sculpting_profile=\"1\" />\n" +" <eventcontext id=\"nodes\" selcue=\"1\" gradientdrag=\"1\" highlight_color=\"4278190335\" pathflash_enabled=\"1\" pathflash_unselected=\"0\" pathflash_timeout=\"500\" show_handles=\"1\" show_outline=\"0\" sculpting_profile=\"1\" />\n" " <eventcontext id=\"tweak\" selcue=\"0\" gradientdrag=\"0\" show_handles=\"0\" width=\"0.2\" force=\"0.2\" fidelity=\"0.5\" usepressure=\"1\" style=\"fill:red;stroke:none;\" usecurrent=\"0\"/>\n" " <eventcontext id=\"spray\" selcue=\"0\" gradientdrag=\"0\" show_handles=\"0\" width=\"0.2\" force=\"0.2\" fidelity=\"0.5\" usepressure=\"1\" style=\"fill:red;stroke:none;\" usecurrent=\"0\"/>\n" " <eventcontext id=\"gradient\" selcue=\"1\"/>\n" diff --git a/src/preferences.cpp b/src/preferences.cpp index 39a9e4d69..315c668b4 100644 --- a/src/preferences.cpp +++ b/src/preferences.cpp @@ -11,6 +11,7 @@ */ #include <cstring> +#include <sstream> #include <glibmm/fileutils.h> #include <glibmm/i18n.h> #include <glib.h> @@ -446,6 +447,13 @@ void Preferences::setDouble(Glib::ustring const &pref_path, double value) _setRawValue(pref_path, buf); } +void Preferences::setColor(Glib::ustring const &pref_path, guint32 value) +{ + gchar buf[16]; + g_snprintf(buf, 16, "#%08x", value); + _setRawValue(pref_path, buf); +} + /** * @brief Set a string attribute of a preference * @param pref_path Path of the preference to modify @@ -732,6 +740,20 @@ Glib::ustring Preferences::_extractString(Entry const &v) return Glib::ustring(static_cast<gchar const *>(v._value)); } +guint32 Preferences::_extractColor(Entry const &v) +{ + gchar const *s = static_cast<gchar const *>(v._value); + std::istringstream hr(s); + guint32 color; + if (s[0] == '#') { + hr.ignore(1); + hr >> std::hex >> color; + } else { + hr >> color; + } + return color; +} + SPCSSAttr *Preferences::_extractStyle(Entry const &v) { SPCSSAttr *style = sp_repr_css_attr_new(); diff --git a/src/preferences.h b/src/preferences.h index a7be08009..5e1ccf9d6 100644 --- a/src/preferences.h +++ b/src/preferences.h @@ -178,6 +178,11 @@ public: inline Glib::ustring getString() const; /** + * @brief Interpret the preference as an RGBA color value. + */ + inline guint32 getColor(guint32 def) const; + + /** * @brief Interpret the preference as a CSS style. * @return A CSS style that has to be unrefed when no longer necessary. Never NULL. */ @@ -329,6 +334,10 @@ public: return getEntry(pref_path).getString(); } + guint32 getColor(Glib::ustring const &pref_path, guint32 def=0x000000ff) { + return getEntry(pref_path).getColor(def); + } + /** * @brief Retrieve a CSS style * @param pref_path Path to the retrieved preference @@ -384,6 +393,11 @@ public: void setString(Glib::ustring const &pref_path, Glib::ustring const &value); /** + * @brief Set an RGBA color value + */ + void setColor(Glib::ustring const &pref_path, guint32 value); + + /** * @brief Set a CSS style */ void setStyle(Glib::ustring const &pref_path, SPCSSAttr *style); @@ -459,6 +473,7 @@ protected: int _extractInt(Entry const &v); double _extractDouble(Entry const &v); Glib::ustring _extractString(Entry const &v); + guint32 _extractColor(Entry const &v); SPCSSAttr *_extractStyle(Entry const &v); SPCSSAttr *_extractInheritedStyle(Entry const &v); @@ -567,6 +582,15 @@ inline Glib::ustring Preferences::Entry::getString() const } } +inline guint32 Preferences::Entry::getColor(guint32 def) const +{ + if (!this->isValid()) { + return def; + } else { + return Inkscape::Preferences::get()->_extractColor(*this); + } +} + inline SPCSSAttr *Preferences::Entry::getStyle() const { if (!this->isValid()) { diff --git a/src/selection-chemistry.cpp b/src/selection-chemistry.cpp index d97687267..d4978af3b 100644 --- a/src/selection-chemistry.cpp +++ b/src/selection-chemistry.cpp @@ -89,9 +89,7 @@ // For clippath editing #include "tools-switch.h" -#include "shape-editor.h" -#include "node-context.h" -#include "nodepath.h" +#include "ui/tool/node-tool.h" #include "ui/clipboard.h" @@ -1739,53 +1737,52 @@ void sp_selection_next_patheffect_param(SPDesktop * dt) } } +/*bool has_path_recursive(SPObject *obj) +{ + if (!obj) return false; + if (SP_IS_PATH(obj)) { + return true; + } + if (SP_IS_GROUP(obj) || SP_IS_OBJECTGROUP(obj)) { + for (SPObject *c = obj->children; c; c = c->next) { + if (has_path_recursive(c)) return true; + } + } + return false; +}*/ + void sp_selection_edit_clip_or_mask(SPDesktop * dt, bool clip) { - if (!dt) return; + return; + /*if (!dt) return; + using namespace Inkscape::UI; Inkscape::Selection *selection = sp_desktop_selection(dt); - if ( selection && !selection->isEmpty() ) { - SPItem *item = selection->singleItem(); - if ( item ) { - SPObject *obj = NULL; - if (clip) - obj = item->clip_ref ? SP_OBJECT(item->clip_ref->getObject()) : NULL; - else - obj = item->mask_ref ? SP_OBJECT(item->mask_ref->getObject()) : NULL; - - if (obj) { - // obj is a group object, the children are the actual clippers - for ( SPObject *child = obj->children ; child ; child = child->next ) { - if ( SP_IS_ITEM(child) ) { - // If not already in nodecontext, goto it! - if (!tools_isactive(dt, TOOLS_NODES)) { - tools_switch(dt, TOOLS_NODES); - } - - ShapeEditor * shape_editor = dt->event_context->shape_editor; - // TODO: should we set the item for nodepath or knotholder or both? seems to work with both. - shape_editor->set_item(SP_ITEM(child), SH_NODEPATH); - shape_editor->set_item(SP_ITEM(child), SH_KNOTHOLDER); - Inkscape::NodePath::Path *np = shape_editor->get_nodepath(); - if (np) { - // take colors from prefs (same as used in outline mode) - Inkscape::Preferences *prefs = Inkscape::Preferences::get(); - np->helperpath_rgba = clip ? - prefs->getInt("/options/wireframecolors/clips", 0x00ff00ff) : - prefs->getInt("/options/wireframecolors/masks", 0x0000ffff); - np->helperpath_width = 1.0; - sp_nodepath_show_helperpath(np, true); - } - break; // break out of for loop after 1st encountered item - } - } - } else if (clip) { - dt->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("The selection has no applied clip path.")); - } else { - dt->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("The selection has no applied mask.")); - } - } - } + if (!selection || selection->isEmpty()) return; + + GSList const *items = selection->itemList(); + bool has_path = false; + for (GSList *i = const_cast<GSList*>(items); i; i= i->next) { + SPItem *item = SP_ITEM(i->data); + SPObject *search = clip + ? SP_OBJECT(item->clip_ref ? item->clip_ref->getObject() : NULL) + : SP_OBJECT(item->mask_ref ? item->mask_ref->getObject() : NULL); + has_path |= has_path_recursive(search); + if (has_path) break; + } + if (has_path) { + if (!tools_isactive(dt, TOOLS_NODES)) { + tools_switch(dt, TOOLS_NODES); + } + ink_node_tool_set_mode(INK_NODE_TOOL(dt->event_context), + clip ? NODE_TOOL_EDIT_CLIPPING_PATHS : NODE_TOOL_EDIT_MASKS); + } else if (clip) { + dt->messageStack()->flash(Inkscape::WARNING_MESSAGE, + _("The selection has no applied clip path.")); + } else { + dt->messageStack()->flash(Inkscape::WARNING_MESSAGE, + _("The selection has no applied mask.")); + }*/ } diff --git a/src/shape-editor.cpp b/src/shape-editor.cpp index 44ad9dc9e..fa4360137 100644 --- a/src/shape-editor.cpp +++ b/src/shape-editor.cpp @@ -1,10 +1,9 @@ -#define __SHAPE_EDITOR_CPP__ - /* * Inkscape::ShapeEditor * * Authors: * bulia byak <buliabyak@users.sf.net> + * Krzysztof Kosiński <tweenk.pl@gmail.com> * */ @@ -25,7 +24,6 @@ #include "desktop-handles.h" #include "knotholder.h" #include "live_effects/parameter/point.h" -#include "nodepath.h" #include "xml/node-event-vector.h" #include "preferences.h" #include "object-edit.h" @@ -35,33 +33,14 @@ #include "shape-editor.h" - -ShapeEditorsCollective::ShapeEditorsCollective(SPDesktop */*dt*/) { -} - -ShapeEditorsCollective::~ShapeEditorsCollective() { -} - - -void ShapeEditorsCollective::update_statusbar() { - -//!!! move from nodepath: sp_nodepath_update_statusbar but summing for all nodepaths - -} - ShapeEditor::ShapeEditor(SPDesktop *dt) { this->desktop = dt; - this->grab_node = -1; - this->nodepath = NULL; this->knotholder = NULL; - this->hit = false; this->knotholder_listener_attached_for = NULL; - this->nodepath_listener_attached_for = NULL; } ShapeEditor::~ShapeEditor() { unset_item(SH_KNOTHOLDER); - unset_item(SH_NODEPATH); } void ShapeEditor::unset_item(SubType type, bool keep_knotholder) { @@ -69,18 +48,7 @@ void ShapeEditor::unset_item(SubType type, bool keep_knotholder) { switch (type) { case SH_NODEPATH: - if (this->nodepath) { - old_repr = this->nodepath->repr; - if (old_repr && old_repr == nodepath_listener_attached_for) { - sp_repr_remove_listener_by_data(old_repr, this); - Inkscape::GC::release(old_repr); - nodepath_listener_attached_for = NULL; - } - - this->grab_node = -1; - delete this->nodepath; - this->nodepath = NULL; - } + // defunct break; case SH_KNOTHOLDER: if (this->knotholder) { @@ -101,7 +69,7 @@ void ShapeEditor::unset_item(SubType type, bool keep_knotholder) { } bool ShapeEditor::has_nodepath () { - return (this->nodepath != NULL); + return false; } bool ShapeEditor::has_knotholder () { @@ -116,7 +84,8 @@ void ShapeEditor::update_knotholder () { bool ShapeEditor::has_local_change (SubType type) { switch (type) { case SH_NODEPATH: - return (this->nodepath && this->nodepath->local_change); + // defunct + return false; case SH_KNOTHOLDER: return (this->knotholder && this->knotholder->local_change != 0); default: @@ -127,9 +96,7 @@ bool ShapeEditor::has_local_change (SubType type) { void ShapeEditor::decrement_local_change (SubType type) { switch (type) { case SH_NODEPATH: - if (this->nodepath && this->nodepath->local_change > 0) { - this->nodepath->local_change--; - } + // defunct break; case SH_KNOTHOLDER: if (this->knotholder) { @@ -145,9 +112,7 @@ const SPItem *ShapeEditor::get_item (SubType type) { const SPItem *item = NULL; switch (type) { case SH_NODEPATH: - if (this->has_nodepath()) { - item = this->nodepath->item; - } + // defunct break; case SH_KNOTHOLDER: if (this->has_knotholder()) { @@ -159,50 +124,18 @@ const SPItem *ShapeEditor::get_item (SubType type) { } GList *ShapeEditor::save_nodepath_selection () { - if (this->nodepath) - return ::save_nodepath_selection (this->nodepath); + // defunct stub return NULL; } void ShapeEditor::restore_nodepath_selection (GList *saved) { - if (this->nodepath && saved) - ::restore_nodepath_selection (this->nodepath, saved); + // defunct stub } -bool ShapeEditor::nodepath_edits_repr_key(gchar const *name) { - if (nodepath && name) { - return ( !strcmp(name, nodepath->repr_key) || !strcmp(name, nodepath->repr_nodetypes_key) ); - } - - return false; -} - - void ShapeEditor::shapeeditor_event_attr_changed(gchar const *name) { - gboolean changed_np = FALSE; gboolean changed_kh = FALSE; - if (has_nodepath() && nodepath_edits_repr_key(name)) - { - changed_np = !has_local_change(SH_NODEPATH); - decrement_local_change(SH_NODEPATH); - } - - if (changed_np) { - GList *saved = NULL; - if (has_nodepath()) { - saved = save_nodepath_selection(); - } - - reset_item(SH_NODEPATH); - - if (has_nodepath() && saved) { - restore_nodepath_selection(saved); - g_list_free (saved); - } - } - if (has_knotholder()) { changed_kh = !has_local_change(SH_KNOTHOLDER); @@ -213,8 +146,6 @@ void ShapeEditor::shapeeditor_event_attr_changed(gchar const *name) reset_item(SH_KNOTHOLDER, !strcmp(name, "d")); } } - - update_statusbar(); //TODO: get_container()->update_statusbar(); } @@ -243,27 +174,11 @@ void ShapeEditor::set_item(SPItem *item, SubType type, bool keep_knotholder) { // since this freezes the handles unset_item(type, keep_knotholder); - this->grab_node = -1; - if (item) { Inkscape::XML::Node *repr; switch(type) { case SH_NODEPATH: - if (SP_IS_LPE_ITEM(item)) { - Inkscape::Preferences *prefs = Inkscape::Preferences::get(); - this->nodepath = sp_nodepath_new(desktop, item, (prefs->getBool("/tools/nodes/show_handles", true))); - } - if (this->nodepath) { - this->nodepath->shape_editor = this; - - // setting new listener - repr = SP_OBJECT_REPR(item); - if (repr != nodepath_listener_attached_for) { - Inkscape::GC::anchor(repr); - sp_repr_add_listener(repr, &shapeeditor_repr_events, this); - nodepath_listener_attached_for = repr; - } - } + // defunct break; case SH_KNOTHOLDER: @@ -286,34 +201,6 @@ void ShapeEditor::set_item(SPItem *item, SubType type, bool keep_knotholder) { } } -/** Please note that this function only works for path parameters. -* All other parameters probably will crash Inkscape! -*/ -void ShapeEditor::set_item_lpe_path_parameter(SPItem *item, LivePathEffectObject *lpeobject, const char * key) -{ - unset_item(SH_NODEPATH); - - this->grab_node = -1; - - if (lpeobject) { - Inkscape::Preferences *prefs = Inkscape::Preferences::get(); - this->nodepath = sp_nodepath_new( desktop, lpeobject, - (prefs->getInt("/tools/nodes/show_handles", true)), - key, item); - if (this->nodepath) { - this->nodepath->shape_editor = this; - - // setting new listener - Inkscape::XML::Node *repr = SP_OBJECT_REPR(lpeobject); - if (repr && repr != nodepath_listener_attached_for) { - Inkscape::GC::anchor(repr); - sp_repr_add_listener(repr, &shapeeditor_repr_events, this); - nodepath_listener_attached_for = repr; - } - } - } -} - /** FIXME: This thing is only called when the item needs to be updated in response to repr change. Why not make a reload function in NodePath and in KnotHolder? */ @@ -321,14 +208,7 @@ void ShapeEditor::reset_item (SubType type, bool keep_knotholder) { switch (type) { case SH_NODEPATH: - if ( (nodepath) && (IS_LIVEPATHEFFECT(nodepath->object)) ) { - char * key = g_strdup(nodepath->repr_key); - set_item_lpe_path_parameter(nodepath->item, LIVEPATHEFFECT(nodepath->object), key); // the above checks for nodepath, so it is indeed a path that we are editing - g_free(key); - } else { - SPObject *obj = sp_desktop_document(desktop)->getObjectByRepr(nodepath_listener_attached_for); /// note that it is not certain that this is an SPItem; it could be a LivePathEffectObject. - set_item(SP_ITEM(obj), SH_NODEPATH); - } + // defunct break; case SH_KNOTHOLDER: if ( knotholder ) { @@ -340,267 +220,12 @@ void ShapeEditor::reset_item (SubType type, bool keep_knotholder) } void ShapeEditor::nodepath_destroyed () { - this->nodepath = NULL; -} - -void ShapeEditor::update_statusbar () { - if (this->nodepath) - sp_nodepath_update_statusbar(this->nodepath); -} - -bool ShapeEditor::is_over_stroke (Geom::Point event_p, bool remember) { - if (!this->nodepath) - return false; // no stroke in knotholder - - const SPItem *item = get_item(SH_NODEPATH); - - if (!item || !SP_IS_ITEM(item)) - return false; - - //Translate click point into proper coord system - this->curvepoint_doc = desktop->w2d(event_p); - this->curvepoint_doc *= sp_item_dt2i_affine(item); - - SPCurve *curve = this->nodepath->curve; // not sure if np->curve is always up to date... - Geom::PathVector const &pathv = curve->get_pathvector(); - boost::optional<Geom::PathVectorPosition> pvpos = Geom::nearestPoint(pathv, this->curvepoint_doc); - if (!pvpos) { - g_print("Warning! Possible error?\n"); - return false; - } - - Geom::Point nearest = pathv[pvpos->path_nr].pointAt(pvpos->t); - Geom::Point delta = nearest - this->curvepoint_doc; - - delta = desktop->d2w(delta); - - Inkscape::Preferences *prefs = Inkscape::Preferences::get(); - double stroke_tolerance = - (( !SP_OBJECT_STYLE(item)->stroke.isNone() ? - desktop->current_zoom() * - SP_OBJECT_STYLE (item)->stroke_width.computed * 0.5 * - to_2geom(sp_item_i2d_affine(item)).descrim() - : 0.0) - + prefs->getIntLimited("/options/dragtolerance/value", 0, 0, 100)) / to_2geom(sp_item_i2d_affine(item)).descrim(); - bool close = (Geom::L2 (delta) < stroke_tolerance); - - if (remember && close) { - // calculate index for nodepath's representation. - double int_part; - double t = std::modf(pvpos->t, &int_part); - unsigned int segment_index = (unsigned int)int_part + 1; - for (unsigned int i = 0; i < pvpos->path_nr; ++i) { - segment_index += pathv[i].size() + 1; - if (pathv[i].closed()) - segment_index += 1; - } - - this->curvepoint_event[Geom::X] = (gint) event_p [Geom::X]; - this->curvepoint_event[Geom::Y] = (gint) event_p [Geom::Y]; - this->hit = true; - this->grab_t = t; - this->grab_node = segment_index; - } - - return close; -} - -void ShapeEditor::add_node_near_point() { - if (this->nodepath) { - sp_nodepath_add_node_near_point(this->nodepath, this->curvepoint_doc); - } else if (this->knotholder) { - // we do not add nodes in knotholder... yet - } -} - -void ShapeEditor::select_segment_near_point(bool toggle) { - if (this->nodepath) { - sp_nodepath_select_segment_near_point(this->nodepath, this->curvepoint_doc, toggle); - } - if (this->knotholder) { - // we do not select segments in knotholder... yet? - } -} - -void ShapeEditor::cancel_hit() { - this->hit = false; -} - -bool ShapeEditor::hits_curve() { - return (this->hit); -} - - -void ShapeEditor::curve_drag(gdouble eventx, gdouble eventy) { - if (this->nodepath && !this->nodepath->straight_path) { - - if (this->grab_node == -1) // don't know which segment to drag - return; - - // We round off the extra precision in the motion coordinates provided - // by some input devices (like tablets). As we'll store the coordinates - // as integers in curvepoint_event we need to do this rounding before - // comparing them with the last coordinates from curvepoint_event. - // See bug #1593499 for details. - - gint x = (gint) Inkscape::round(eventx); - gint y = (gint) Inkscape::round(eventy); - - - // The coordinates hasn't changed since the last motion event, abort - if (this->curvepoint_event[Geom::X] == x && - this->curvepoint_event[Geom::Y] == y) - return; - - Geom::Point const delta_w(eventx - this->curvepoint_event[Geom::X], - eventy - this->curvepoint_event[Geom::Y]); - Geom::Point const delta_dt(this->desktop->w2d(delta_w)); - - sp_nodepath_curve_drag (this->nodepath, this->grab_node, this->grab_t, delta_dt); - this->curvepoint_event[Geom::X] = x; - this->curvepoint_event[Geom::Y] = y; - - } - if (this->knotholder) { - // we do not drag curve in knotholder - } - -} - -void ShapeEditor::finish_drag() { - if (this->nodepath && this->hit) { - sp_nodepath_update_repr (this->nodepath, _("Drag curve")); - } -} - -void ShapeEditor::select_rect(Geom::Rect const &rect, bool add) { - if (this->nodepath) { - sp_nodepath_select_rect(this->nodepath, rect, add); - } } bool ShapeEditor::has_selection() { - if (this->nodepath) - return this->nodepath->selected; return false; // so far, knotholder cannot have selection } -void ShapeEditor::deselect() { - if (this->nodepath) - sp_nodepath_deselect(this->nodepath); -} - -void ShapeEditor::add_node () { - sp_node_selected_add_node(this->nodepath); -} - -void ShapeEditor::delete_nodes () { - sp_node_selected_delete(this->nodepath); -} - -void ShapeEditor::delete_nodes_preserving_shape () { - if (this->nodepath && this->nodepath->selected) { - sp_node_delete_preserve(g_list_copy(this->nodepath->selected)); - } -} - -void ShapeEditor::delete_segment () { - sp_node_selected_delete_segment(this->nodepath); -} - -void ShapeEditor::set_node_type(int type) { - sp_node_selected_set_type(this->nodepath, (Inkscape::NodePath::NodeType) type); -} - -void ShapeEditor::break_at_nodes() { - sp_node_selected_break(this->nodepath); -} - -void ShapeEditor::join_nodes() { - sp_node_selected_join(this->nodepath); -} - -void ShapeEditor::join_segments() { - sp_node_selected_join_segment(this->nodepath); -} - -void ShapeEditor::duplicate_nodes() { - sp_node_selected_duplicate(this->nodepath); -} - -void ShapeEditor::set_type_of_segments(NRPathcode code) { - sp_node_selected_set_line_type(this->nodepath, code); -} - -void ShapeEditor::move_nodes_screen(SPDesktop *desktop, gdouble dx, gdouble dy) { - sp_node_selected_move_screen(desktop, this->nodepath, dx, dy); -} -void ShapeEditor::move_nodes(gdouble dx, gdouble dy) { - sp_node_selected_move(this->nodepath, dx, dy); -} - -void ShapeEditor::rotate_nodes(gdouble angle, int which, bool screen) { - if (this->nodepath) - sp_nodepath_selected_nodes_rotate (this->nodepath, angle, which, screen); -} - -void ShapeEditor::scale_nodes(gdouble const grow, int const which) { - sp_nodepath_selected_nodes_scale(this->nodepath, grow, which); -} -void ShapeEditor::scale_nodes_screen(gdouble const grow, int const which) { - sp_nodepath_selected_nodes_scale_screen(this->nodepath, grow, which); -} - -void ShapeEditor::select_all (bool invert) { - if (this->nodepath) - sp_nodepath_select_all (this->nodepath, invert); -} -void ShapeEditor::select_all_from_subpath (bool invert) { - if (this->nodepath) - sp_nodepath_select_all_from_subpath (this->nodepath, invert); -} -void ShapeEditor::select_next () { - if (this->nodepath) { - sp_nodepath_select_next (this->nodepath); - if (this->nodepath->numSelected() >= 1) { - this->desktop->scroll_to_point(this->nodepath->singleSelectedCoords(), 1.0); - } - } -} -void ShapeEditor::select_prev () { - if (this->nodepath) { - sp_nodepath_select_prev (this->nodepath); - if (this->nodepath->numSelected() >= 1) { - this->desktop->scroll_to_point(this->nodepath->singleSelectedCoords(), 1.0); - } - } -} - -void ShapeEditor::show_handles (bool show) { - if (this->nodepath && !this->nodepath->straight_path) - sp_nodepath_show_handles (this->nodepath, show); -} - -void ShapeEditor::show_helperpath (bool show) { - if (this->nodepath) - sp_nodepath_show_helperpath (this->nodepath, show); -} - -void ShapeEditor::flip (Geom::Dim2 axis, boost::optional<Geom::Point> center) { - if (this->nodepath) - sp_nodepath_flip (this->nodepath, axis, center); -} - -void ShapeEditor::distribute (Geom::Dim2 axis) { - if (this->nodepath) - sp_nodepath_selected_distribute (this->nodepath, axis); -} -void ShapeEditor::align (Geom::Dim2 axis) { - if (this->nodepath) - sp_nodepath_selected_align (this->nodepath, axis); -} - - /* Local Variables: mode:c++ diff --git a/src/shape-editor.h b/src/shape-editor.h index 98dbb35d7..2374d6ac6 100644 --- a/src/shape-editor.h +++ b/src/shape-editor.h @@ -41,7 +41,6 @@ public: ~ShapeEditor(); void set_item (SPItem *item, SubType type, bool keep_knotholder = false); - void set_item_lpe_path_parameter(SPItem *item, LivePathEffectObject *lpeobject, const char * key); void unset_item (SubType type, bool keep_knotholder = false); bool has_nodepath (); //((deprecated)) @@ -55,68 +54,10 @@ public: void nodepath_destroyed (); - void update_statusbar (); - - bool is_over_stroke (Geom::Point event_p, bool remember); - - void add_node_near_point(); // uses the shapeeditor's remembered point, if any - - void select_segment_near_point(bool toggle); // uses the shapeeditor's remembered point, if any - - void cancel_hit (); - - bool hits_curve (); - - void curve_drag (gdouble eventx, gdouble eventy); - - void finish_drag (); - - void select_rect (Geom::Rect const &rect, bool add); - bool has_selection (); - void deselect (); - - Inkscape::NodePath::Path *get_nodepath() {return nodepath;} //((deprecated)) - ShapeEditorsCollective *get_container() {return container;} - - void add_node(); - - void delete_nodes(); - void delete_nodes_preserving_shape(); - void delete_segment(); - - void set_node_type(int type); - - void break_at_nodes(); - void join_nodes(); - void join_segments(); - - void duplicate_nodes(); - - void set_type_of_segments(NRPathcode code); - - void move_nodes(gdouble dx, gdouble dy); - void move_nodes_screen(SPDesktop *desktop, gdouble dx, gdouble dy); - - void rotate_nodes(gdouble angle, int which, bool screen); - - void scale_nodes(gdouble const grow, int const which); - void scale_nodes_screen(gdouble const grow, int const which); - - void select_all (bool invert); - void select_all_from_subpath (bool invert); - void select_next (); - void select_prev (); - void show_handles (bool show); - void show_helperpath (bool show); - - void flip (Geom::Dim2 axis, boost::optional<Geom::Point> center = boost::optional<Geom::Point>()); - - void distribute (Geom::Dim2 axis); - void align (Geom::Dim2 axis); - - bool nodepath_edits_repr_key(gchar const *name); + Inkscape::NodePath::Path *get_nodepath() {return NULL;} //((deprecated)) + ShapeEditorsCollective *get_container() {return NULL;} // this one is only public because it's called from non-C++ repr changed callback void shapeeditor_event_attr_changed(gchar const *name); @@ -127,44 +68,8 @@ private: const SPItem *get_item (SubType type); SPDesktop *desktop; - - Inkscape::NodePath::Path *nodepath; - - // TODO: std::list<KnotHolder *> knotholders; KnotHolder *knotholder; - - ShapeEditorsCollective *container; - - //Inkscape::XML::Node *lidtened_repr; - - double grab_t; - int grab_node; // number of node grabbed by sp_node_context_is_over_stroke - bool hit; - Geom::Point curvepoint_event; // int coords from event - Geom::Point curvepoint_doc; // same, in doc coords - Inkscape::XML::Node *knotholder_listener_attached_for; - Inkscape::XML::Node *nodepath_listener_attached_for; -}; - - -/* As the next stage, this will be a collection of multiple ShapeEditors, -with the same interface as the single ShapeEditor, passing the actions to all its -contained ShapeEditors. Thus it should be easy to switch node context from -using a single ShapeEditor to using a ShapeEditorsCollective. */ - -class ShapeEditorsCollective { -public: - - ShapeEditorsCollective(SPDesktop *desktop); - ~ShapeEditorsCollective(); - - void update_statusbar(); - -private: - std::vector<ShapeEditor> editors; - - SPNodeContext *nc; // who holds us }; #endif diff --git a/src/snap.cpp b/src/snap.cpp index 970f29ece..c39cd9e04 100644 --- a/src/snap.cpp +++ b/src/snap.cpp @@ -30,6 +30,7 @@ #include "inkscape.h" #include "desktop.h" +#include "selection.h" #include "sp-guide.h" #include "preferences.h" #include "event-context.h" @@ -207,25 +208,11 @@ Inkscape::SnappedPoint SnapManager::freeSnap(Inkscape::SnapPreferences::PointTyp return Inkscape::SnappedPoint(p, Inkscape::SNAPTARGET_UNDEFINED, NR_HUGE, 0, false, false); } - std::vector<SPItem const *> *items_to_ignore; - if (_item_to_ignore) { // If we have only a single item to ignore - // then build a list containing this single item; - // This single-item list will prevail over any other _items_to_ignore list, should that exist - items_to_ignore = new std::vector<SPItem const *>; - items_to_ignore->push_back(_item_to_ignore); - } else { - items_to_ignore = _items_to_ignore; - } - SnappedConstraints sc; SnapperList const snappers = getSnappers(); for (SnapperList::const_iterator i = snappers.begin(); i != snappers.end(); i++) { - (*i)->freeSnap(sc, point_type, p, bbox_to_snap, items_to_ignore, _unselected_nodes); - } - - if (_item_to_ignore) { - delete items_to_ignore; + (*i)->freeSnap(sc, point_type, p, bbox_to_snap, &_items_to_ignore, _unselected_nodes); } return findBestSnap(p, sc, false); @@ -363,17 +350,6 @@ Inkscape::SnappedPoint SnapManager::constrainedSnap(Inkscape::SnapPreferences::P return Inkscape::SnappedPoint(p, Inkscape::SNAPTARGET_UNDEFINED, NR_HUGE, 0, false, false); } - std::vector<SPItem const *> *items_to_ignore; - if (_item_to_ignore) { // If we have only a single item to ignore - // then build a list containing this single item; - // This single-item list will prevail over any other _items_to_ignore list, should that exist - items_to_ignore = new std::vector<SPItem const *>; - items_to_ignore->push_back(_item_to_ignore); - } else { - items_to_ignore = _items_to_ignore; - } - - // First project the mouse pointer onto the constraint Geom::Point pp = constraint.projection(p.getPoint()); // Then try to snap the projected point @@ -382,11 +358,7 @@ Inkscape::SnappedPoint SnapManager::constrainedSnap(Inkscape::SnapPreferences::P SnappedConstraints sc; SnapperList const snappers = getSnappers(); for (SnapperList::const_iterator i = snappers.begin(); i != snappers.end(); i++) { - (*i)->constrainedSnap(sc, point_type, candidate, bbox_to_snap, constraint, items_to_ignore); - } - - if (_item_to_ignore) { - delete items_to_ignore; + (*i)->constrainedSnap(sc, point_type, candidate, bbox_to_snap, constraint, &_items_to_ignore); } return findBestSnap(candidate, sc, true); @@ -1036,22 +1008,7 @@ Inkscape::SnappedPoint SnapManager::findBestSnap(Inkscape::SnapCandidatePoint co return bestSnappedPoint; } -/** - * \brief Prepare the snap manager for the actual snapping, which includes building a list of snap targets - * to ignore and toggling the snap indicator - * - * There are two overloaded setup() methods, of which this one only allows for a single item to be ignored - * whereas the other one will take a list of items to ignore - * - * \param desktop Reference to the desktop to which this snap manager is attached - * \param snapindicator If true then a snap indicator will be displayed automatically (when enabled in the preferences) - * \param item_to_ignore This item will not be snapped to, e.g. the item that is currently being dragged. This avoids "self-snapping" - * \param unselected_nodes Stationary nodes of the path that is currently being edited in the node tool and - * that can be snapped too. Nodes not in this list will not be snapped to, to avoid "self-snapping". Of each - * unselected node both the position (Geom::Point) and the type (Inkscape::SnapTargetType) will be stored - * \param guide_to_ignore Guide that is currently being dragged and should not be snapped to - */ - +/// Convenience shortcut when there is only one item to ignore void SnapManager::setup(SPDesktop const *desktop, bool snapindicator, SPItem const *item_to_ignore, @@ -1059,8 +1016,8 @@ void SnapManager::setup(SPDesktop const *desktop, SPGuide *guide_to_ignore) { g_assert(desktop != NULL); - _item_to_ignore = item_to_ignore; - _items_to_ignore = NULL; + _items_to_ignore.clear(); + _items_to_ignore.push_back(item_to_ignore); _desktop = desktop; _snapindicator = snapindicator; _unselected_nodes = unselected_nodes; @@ -1090,14 +1047,32 @@ void SnapManager::setup(SPDesktop const *desktop, SPGuide *guide_to_ignore) { g_assert(desktop != NULL); - _item_to_ignore = NULL; - _items_to_ignore = &items_to_ignore; + _items_to_ignore = items_to_ignore; _desktop = desktop; _snapindicator = snapindicator; _unselected_nodes = unselected_nodes; _guide_to_ignore = guide_to_ignore; } +/// Setup, taking the list of items to ignore from the desktop's selection. +void SnapManager::setupIgnoreSelection(SPDesktop const *desktop, + bool snapindicator, + std::vector<Inkscape::SnapCandidatePoint> *unselected_nodes, + SPGuide *guide_to_ignore) +{ + _desktop = desktop; + _snapindicator = snapindicator; + _unselected_nodes = unselected_nodes; + _guide_to_ignore = guide_to_ignore; + _items_to_ignore.clear(); + + Inkscape::Selection *sel = _desktop->selection; + GSList const *items = sel->itemList(); + for (GSList *i = const_cast<GSList*>(items); i; i = i->next) { + _items_to_ignore.push_back(static_cast<SPItem const *>(i->data)); + } +} + SPDocument *SnapManager::getDocument() const { return _named_view->document; diff --git a/src/snap.h b/src/snap.h index 40bf0996e..ae136a355 100644 --- a/src/snap.h +++ b/src/snap.h @@ -1,17 +1,7 @@ -#ifndef SEEN_SNAP_H -#define SEEN_SNAP_H - /** * \file snap.h - * \brief SnapManager class. - * - * The SnapManager class handles most (if not all) of the interfacing of the snapping mechanisms with the - * other parts of the code base. It stores the references to the various types of snappers for grid, guides - * and objects, and it stores most of the snapping preferences. Besides that it provides methods to setup - * the snapping environment (e.g. keeps a list of the items to ignore when looking for snap target candidates, - * and toggling of the snap indicator), and it provides many different methods for the snapping itself (free - * snapping vs. constrained snapping, returning the result by reference or through a return statement, etc.) - * + * \brief Per-desktop object that handles snapping queries + *//* * Authors: * Lauris Kaplinski <lauris@kaplinski.com> * Frank Felfe <innerspace@iname.com> @@ -25,8 +15,10 @@ * Released under GNU GPL, read the file 'COPYING' for more information */ -#include <vector> +#ifndef SEEN_SNAP_H +#define SEEN_SNAP_H +#include <vector> #include "guide-snapper.h" #include "object-snapper.h" #include "snap-preferences.h" @@ -42,11 +34,33 @@ enum SPGuideDragType { // used both here and in desktop-events.cpp class SPNamedView; /// Class to coordinate snapping operations - /** - * Each SPNamedView has one of these. It offers methods to snap points to whatever - * snappers are defined (e.g. grid, guides etc.). It also allows callers to snap - * points which have undergone some transformation (e.g. translation, scaling etc.) + * The SnapManager class handles most (if not all) of the interfacing of the snapping mechanisms + * with the other parts of the code base. It stores the references to the various types of snappers + * for grid, guides and objects, and it stores most of the snapping preferences. Besides that + * it provides methods to setup the snapping environment (e.g. keeps a list of the items to ignore + * when looking for snap target candidates, and toggling of the snap indicator), and it provides + * many different methods for snapping queries (free snapping vs. constrained snapping, + * returning the result by reference or through a return statement, etc.) + * + * Each SPNamedView has one of these. It offers methods to snap points to whatever + * snappers are defined (e.g. grid, guides etc.). It also allows callers to snap + * points which have undergone some transformation (e.g. translation, scaling etc.) + * + * \par How snapping is implemented in Inkscape + * \par + * The snapping system consists of two key elements. The first one is the snap manager + * (this class), which keeps some data about objects in the document and answers queries + * of the type "given this point and type of transformation, what is the best place + * to snap to?". + * + * The second is in event-context.cpp and implements the snapping timeout. Whenever a motion + * events happens over the canvas, it stores it for later use and initiates a timeout. + * This timeout is discarded whenever a new motion event occurs. When the timeout expires, + * a global flag in SnapManager, accessed via getSnapPostponedGlobally(), is set to true + * and the stored event is replayed, but this time with snapping enabled. This way you can + * write snapping code directly in your control point's dragged handler as if there was + * no timeout. */ class SnapManager @@ -73,10 +87,14 @@ public: SPGuide *guide_to_ignore = NULL); void setup(SPDesktop const *desktop, - bool snapindicator, - std::vector<SPItem const *> &items_to_ignore, - std::vector<Inkscape::SnapCandidatePoint> *unselected_nodes = NULL, - SPGuide *guide_to_ignore = NULL); + bool snapindicator, + std::vector<SPItem const *> &items_to_ignore, + std::vector<Inkscape::SnapCandidatePoint> *unselected_nodes = NULL, + SPGuide *guide_to_ignore = NULL); + void setupIgnoreSelection(SPDesktop const *desktop, + bool snapindicator = true, + std::vector<Inkscape::SnapCandidatePoint> *unselected_nodes = NULL, + SPGuide *guide_to_ignore = NULL); // freeSnapReturnByRef() is preferred over freeSnap(), because it only returns a // point if snapping has occurred (by overwriting p); otherwise p is untouched @@ -166,8 +184,7 @@ protected: SPNamedView const *_named_view; private: - std::vector<SPItem const *> *_items_to_ignore; ///< Items that should not be snapped to, for example the items that are currently being dragged. Set using the setup() method - SPItem const *_item_to_ignore; ///< Single item that should not be snapped to. If not NULL then this takes precedence over _items_to_ignore. Set using the setup() method + std::vector<SPItem const *> _items_to_ignore; ///< Items that should not be snapped to, for example the items that are currently being dragged. Set using the setup() method SPGuide *_guide_to_ignore; ///< A guide that should not be snapped to, e.g. the guide that is currently being dragged SPDesktop const *_desktop; bool _snapindicator; ///< When true, an indicator will be drawn at the position that was being snapped to diff --git a/src/snapper.h b/src/snapper.h index dbbf7ebe9..47c1c514e 100644 --- a/src/snapper.h +++ b/src/snapper.h @@ -69,6 +69,7 @@ public: public: ConstraintLine(Geom::Point const &d) : _has_point(false), _direction(d) {} ConstraintLine(Geom::Point const &p, Geom::Point const &d) : _has_point(true), _point(p), _direction(d) {} + ConstraintLine(Geom::Line const &l) : _has_point(true), _point(l.origin()), _direction(l.versor()) {} bool hasPoint() const { return _has_point; diff --git a/src/sp-lpe-item.cpp b/src/sp-lpe-item.cpp index 6b71541e6..1bb500dd2 100644 --- a/src/sp-lpe-item.cpp +++ b/src/sp-lpe-item.cpp @@ -33,7 +33,6 @@ #include "message-stack.h" #include "inkscape.h" #include "desktop.h" -#include "node-context.h" #include "shape-editor.h" #include <algorithm> @@ -261,18 +260,7 @@ sp_lpe_item_update(SPObject *object, SPCtx *ctx, guint flags) } // update the helperpaths of all LPEs applied to the item - // TODO: is there a more canonical place for this, since we don't have instant access to the item's nodepath? - // FIXME: this is called multiple (at least 3) times; how can we avoid this? - - // FIXME: ditch inkscape_active_event_context() - SPEventContext *ec = inkscape_active_event_context(); - if (!SP_IS_NODE_CONTEXT(ec)) return; - ShapeEditor *sh = ec->shape_editor; - g_assert(sh); - if (!sh->has_nodepath()) return; - - Inkscape::NodePath::Path *np = sh->get_nodepath(); - sp_nodepath_update_helperpaths(np); + // TODO: re-add for the new node tool } /** @@ -395,7 +383,8 @@ sp_lpe_item_update_patheffect (SPLPEItem *lpeitem, bool wholetree, bool write) if (dynamic_cast<Inkscape::LivePathEffect::LPEPathLength *>(lpe)) { if (!lpe->isVisible()) { // we manually disable text for LPEPathLength - dynamic_cast<Inkscape::LivePathEffect::LPEPathLength *>(lpe)->hideCanvasText(); + // use static_cast, because we already checked for the right type above + static_cast<Inkscape::LivePathEffect::LPEPathLength *>(lpe)->hideCanvasText(); } } } diff --git a/src/tools-switch.cpp b/src/tools-switch.cpp index 6c53ce61c..5f33453f0 100644 --- a/src/tools-switch.cpp +++ b/src/tools-switch.cpp @@ -26,7 +26,7 @@ #include <xml/repr.h> #include "select-context.h" -#include "node-context.h" +#include "ui/tool/node-tool.h" #include "tweak-context.h" #include "spray-context.h" #include "sp-path.h" @@ -126,10 +126,9 @@ tools_switch(SPDesktop *dt, int num) inkscape_eventcontext_set(sp_desktop_event_context(dt)); break; case TOOLS_NODES: - dt->set_event_context(SP_TYPE_NODE_CONTEXT, tool_names[num]); + dt->set_event_context(INK_TYPE_NODE_TOOL, tool_names[num]); dt->activate_guides(true); inkscape_eventcontext_set(sp_desktop_event_context(dt)); - dt->tipsMessageContext()->set(Inkscape::NORMAL_MESSAGE, _("To edit a path, <b>click</b>, <b>Shift+click</b>, or <b>drag around</b> nodes to select them, then <b>drag</b> nodes and handles. <b>Click</b> on an object to select.")); break; case TOOLS_TWEAK: dt->set_event_context(SP_TYPE_TWEAK_CONTEXT, tool_names[num]); 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..8c8d64ec0 100644 --- a/src/ui/dialog/align-and-distribute.cpp +++ b/src/ui/dialog/align-and-distribute.cpp @@ -27,17 +27,17 @@ #include "graphlayout/graphlayout.h" #include "inkscape.h" #include "macros.h" -#include "node-context.h" //For access to ShapeEditor #include "preferences.h" #include "removeoverlap/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/inkscape-preferences.cpp b/src/ui/dialog/inkscape-preferences.cpp index 90516063c..11850cffc 100644 --- a/src/ui/dialog/inkscape-preferences.cpp +++ b/src/ui/dialog/inkscape-preferences.cpp @@ -435,13 +435,22 @@ void InkscapePreferences::initPageTools() AddGradientCheckbox(_page_node, "/tools/nodes", true); _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_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(_("Transform Handles:")); + _t_node_show_transform_handles.init(_("Show transform handles"), "/tools/nodes/show_transform_handles", true); + _page_node.add_line( true, "", _t_node_show_transform_handles, "", _("Show scaling, rotation and skew handles for node selections.")); + _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.")); //Tweak this->AddPage(_page_tweak, _("Tweak"), iter_tools, PREFS_PAGE_TOOLS_TWEAK); diff --git a/src/ui/dialog/inkscape-preferences.h b/src/ui/dialog/inkscape-preferences.h index 16e62df59..638c84598 100644 --- a/src/ui/dialog/inkscape-preferences.h +++ b/src/ui/dialog/inkscape-preferences.h @@ -141,9 +141,13 @@ 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_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_show_transform_handles; + PrefCheckButton _t_node_single_node_transform_handles; PrefColorPicker _t_node_pathoutline_color; PrefRadioButton _win_dockable, _win_floating; diff --git a/src/ui/tool/Makefile_insert b/src/ui/tool/Makefile_insert new file mode 100644 index 000000000..e14943021 --- /dev/null +++ b/src/ui/tool/Makefile_insert @@ -0,0 +1,29 @@ +## 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/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/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..5a84592b6 --- /dev/null +++ b/src/ui/tool/control-point-selection.cpp @@ -0,0 +1,583 @@ +/** @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 <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) + , _sculpt_enabled(false) + , _sculpting(false) +{ + signal_update.connect( sigc::bind( + sigc::mem_fun(*this, &ControlPointSelection::_updateTransformHandles), + true)); + signal_point_changed.connect( + sigc::hide( sigc::hide( + sigc::bind( + sigc::mem_fun(*this, &ControlPointSelection::_updateTransformHandles), + false)))); + _handles->signal_transform.connect( + sigc::mem_fun(*this, &ControlPointSelection::transform)); + _handles->signal_commit.connect( + sigc::mem_fun(*this, &ControlPointSelection::_commitTransform)); +} + +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); + } + + boost::shared_ptr<connlist_type> clist(new connlist_type()); + + // hide event param and always return false + clist->push_back( + x->signal_grabbed.connect( + sigc::bind_return( + sigc::bind<0>( + sigc::mem_fun(*this, &ControlPointSelection::_selectionGrabbed), + x), + false))); + clist->push_back( + x->signal_dragged.connect( + sigc::mem_fun(*this, &ControlPointSelection::_selectionDragged))); + // hide event parameter + clist->push_back( + x->signal_ungrabbed.connect( + sigc::hide( + sigc::mem_fun(*this, &ControlPointSelection::_selectionUngrabbed)))); + + found = _points.insert(std::make_pair(x, clist)).first; + + x->updateState(); + _rot_radius.reset(); + signal_point_changed.emit(x, true); + + return std::pair<iterator, bool>(found, true); +} + +/** Remove a point from the selection. */ +void ControlPointSelection::erase(iterator pos) +{ + SelectableControlPoint *erased = pos->first; + boost::shared_ptr<connlist_type> clist = pos->second; + for (connlist_type::iterator i = clist->begin(); i != clist->end(); ++i) { + i->disconnect(); + } + _points.erase(pos); + erased->updateState(); + _rot_radius.reset(); + signal_point_changed.emit(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->first; + cur->transform(m); + } + // TODO preserving the rotation radius needs some rethinking... + if (_rot_radius) (*_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->first->position()[d])); + } + + double new_coord = bound->middle(); + for (iterator i = _points.begin(); i != _points.end(); ++i) { + Geom::Point pos = i->first->position(); + pos[d] = new_coord; + i->first->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->first->position(); + sm.insert(std::make_pair(pos[d], i->first)); + 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() +{ + Geom::OptRect bound; + for (iterator i = _points.begin(); i != _points.end(); ++i) { + SelectableControlPoint *cur = i->first; + Geom::Point p = cur->position(); + if (!bound) { + bound = Geom::Rect(p, p); + } else { + bound->expandTo(p); + } + } + return bound; +} + +Geom::OptRect ControlPointSelection::bounds() +{ + Geom::OptRect bound; + for (iterator i = _points.begin(); i != _points.end(); ++i) { + SelectableControlPoint *cur = i->first; + Geom::OptRect r = cur->bounds(); + bound.unionWith(r); + } + return bound; +} + +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::_selectionGrabbed(SelectableControlPoint *p, GdkEventMotion *event) +{ + hideTransformHandles(); + _dragging = true; + if (held_alt(*event) && _sculpt_enabled) { + _sculpting = true; + _grabbed_point = p; + } else { + _sculpting = false; + } +} + +void ControlPointSelection::_selectionDragged(Geom::Point const &old_pos, Geom::Point &new_pos, + GdkEventMotion *event) +{ + Geom::Point delta = new_pos - old_pos; + /*if (_sculpting) { + // for now we only support the default sculpting profile (bell) + // others will be added when preferences will be able to store enumerated values + double pressure, alpha; + if (gdk_event_get_axis (event, GDK_AXIS_PRESSURE, &pressure)) { + pressure = CLAMP(pressure, 0.2, 0.8); + } else { + pressure = 0.5; + } + + alpha = 1 - 2 * fabs(pressure - 0.5); + if (pressure > 0.5) alpha = 1/alpha; + + for (iterator i = _points.begin(); i != _points.end(); ++i) { + SelectableControlPoint *cur = i->first; + double dist = Geom::distance(cur->position(), _grabbed_point->position()); + + cur->move(cur->position() + delta); + } + } else*/ { + for (iterator i = _points.begin(); i != _points.end(); ++i) { + SelectableControlPoint *cur = i->first; + cur->move(cur->position() + delta); + } + _handles->rotationCenter().move(_handles->rotationCenter().position() + delta); + } + signal_update.emit(); +} + +void ControlPointSelection::_selectionUngrabbed() +{ + _dragging = false; + _grabbed_point = NULL; + restoreTransformHandles(); + signal_commit.emit(COMMIT_MOUSE_MOVE); +} + +void ControlPointSelection::_updateTransformHandles(bool preserve_center) +{ + if (_dragging) return; + + if (_handles_visible && size() > 1) { + Geom::OptRect b = pointwiseBounds(); + _handles->setBounds(*b, preserve_center); + _handles->setVisible(true); + } else if (_one_node_handles && size() == 1) { // only one control point in selection + SelectableControlPoint *p = begin()->first; + _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 + consume_same_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; +} + +/** 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 = _handles->rotationCenter(); + if (!_rot_radius) { + Geom::Rect b = *(size() == 1 ? bounds() : pointwiseBounds()); + double maxlen = 0; + for (unsigned i = 0; i < 4; ++i) { + double len = (b.corner(i) - rc).length(); + if (len > maxlen) maxlen = len; + } + _rot_radius = maxlen; + } + + 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(), *_rot_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; + + // TODO should the saved rotation center or the current center be used? + Geom::Rect bound = (size() == 1 ? *bounds() : *pointwiseBounds()); + double maxext = bound.maxExtent(); + if (Geom::are_near(maxext, 0)) return false; + Geom::Point 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::_commitTransform(CommitEvent ce) +{ + _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)) { + // TODO make a method for mode switching + 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); + } + 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..38df5c7e5 --- /dev/null +++ b/src/ui/tool/control-point-selection.h @@ -0,0 +1,151 @@ +/** @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 <tr1/unordered_map> +#include <tr1/unordered_set> +#include <boost/shared_ptr.hpp> +#include <boost/weak_ptr.hpp> +#include <boost/optional.hpp> +#include <sigc++/sigc++.h> +#include <2geom/forward.h> +#include <2geom/point.h> +#include "display/display-forward.h" +#include "util/accumulators.h" +#include "util/hash.h" +#include "ui/tool/commit-events.h" +#include "ui/tool/manipulator.h" + +namespace std { using namespace tr1; } + +class SPDesktop; + +namespace Inkscape { +namespace UI { + +class TransformHandleSet; +class SelectableControlPoint; + +class ControlPointSelection : public Manipulator { +public: + ControlPointSelection(SPDesktop *d, SPCanvasGroup *th_group); + ~ControlPointSelection(); + typedef std::list<sigc::connection> connlist_type; + typedef std::unordered_map< SelectableControlPoint *, + boost::shared_ptr<connlist_type> > map_type; + typedef std::unordered_set< SelectableControlPoint * > set_type; + typedef set_type Set; // convenience alias + + typedef map_type::iterator iterator; + typedef map_type::const_iterator const_iterator; + typedef map_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(); + + 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 + void hideTransformHandles(); + void restoreTransformHandles(); + + // TODO this is really only applicable to nodes... maybe derive a NodeSelection? + void setSculpting(bool v) { _sculpt_enabled = v; } + + sigc::signal<void> signal_update; + sigc::signal<void, SelectableControlPoint *, bool> signal_point_changed; + sigc::signal<void, CommitEvent> signal_commit; +private: + void _selectionGrabbed(SelectableControlPoint *, GdkEventMotion *); + void _selectionDragged(Geom::Point const &, Geom::Point &, GdkEventMotion *); + void _selectionUngrabbed(); + void _updateTransformHandles(bool preserve_center); + 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 _commitTransform(CommitEvent ce); + map_type _points; + set_type _all_points; + boost::optional<double> _rot_radius; + TransformHandleSet *_handles; + SelectableControlPoint *_grabbed_point; + unsigned _dragging : 1; + unsigned _handles_visible : 1; + unsigned _one_node_handles : 1; + unsigned _sculpt_enabled : 1; + unsigned _sculpting : 1; +}; + +} // 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..73dd0d19e --- /dev/null +++ b/src/ui/tool/control-point.cpp @@ -0,0 +1,630 @@ +/** @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. Control points use signals heavily - <b>read the libsigc++ + * tutorial on the wiki</b> before using this class.</b> + * + * @par Control point signals + * @par + * The control point has several signals which allow you to react to things that happen to it. + * The most important singals are the grabbed, dragged, ungrabbed and moved signals. + * When a drag happens, the order of emission is as follows: + * - <tt>signal_grabbed</tt> + * - <tt>signal_dragged</tt> + * - <tt>signal_dragged</tt> + * - <tt>signal_dragged</tt> + * - ... + * - <tt>signal_dragged</tt> + * - <tt>signal_ungrabbed</tt> + * + * The control point can also respond to clicks and double clicks. On a double click, + * <tt>signal_clicked</tt> is emitted, followed by <tt>signal_doubleclicked</tt>. + * + * A few signal usage hints if you can't be bothered to read the tutorial: + * - If you want some other object or a global function to react to signals of a control point + * from some other object, and you want to access the control point that emitted the signal + * in the handler, use <tt>sigc::bind</tt> like this: + * @code + * void handle_clicked_signal(ControlPoint *point, int button); + * point->signal_clicked.connect( + * sigc::bind<0>( sigc::ptr_fun(handle_clicked_signal), + * point )); + * @endcode + * - You can ignore unneeded parameters using sigc::hide. + * - If you want to get rid of the handlers added by constructors in superclasses, + * use the <tt>clear()</tt> method: @code signal_clicked.clear(); @endcode + * - To connect at the front of the slot list instead of at the end, use: + * @code + * signal_clicked.slots().push_front( + * sigc::mem_fun(*this, &FunkyPoint::_clickedHandler)); + * @endcode + * - Note that calling <tt>slots()</tt> does not copy anything. You can disconnect + * and reorder slots by manipulating the elements of the slot list. The returned object is + * of type @verbatim (signal type)::slot_list @endverbatim. + * + * @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. + */ + +/** + * @var ControlPoint::signal_dragged + * Emitted while dragging, but before moving the knot to new position. + * Old position will always be the same as position() - there are two parameters + * only for convenience. + * - First parameter: old position, always equal to position() + * - Second parameter: 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. + * - Third parameter: motion event + */ + +/** + * @var ControlPoint::signal_clicked + * Emitted when the control point is clicked, at mouse button release. The parameter contains + * the event that caused the signal to be emitted. Your signal handler should return true + * if the click had some effect. If it did nothing, return false. Improperly handling this signal + * can cause the context menu not to appear when a control point is right-clicked. + */ + +/** + * @var ControlPoint::signal_doubleclicked + * Emitted when the control point is doubleclicked, at mouse button release. The parameter + * contains the event that caused the signal to be emitted. Your signal handler should return true + * if the double click had some effect. If it did nothing, return false. + */ + +/** + * @var ControlPoint::signal_grabbed + * Emitted when the control point is grabbed and a drag starts. The parameter contains + * the causing event. Return true to prevent further processing. Because all control points + * handle drag tolerance, <tt>signal_dragged</tt> will be emitted immediately after this signal + * to move the point to its new position. + */ + +/** + * @var ControlPoint::signal_ungrabbed + * Emitted when the control point finishes a drag. The parameter contains the event which + * caused the signal, but it can be NULL if the grab was broken. + */ + +/** + * @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() +{ + _event_handler_connection = g_signal_connect(G_OBJECT(_canvas_item), "event", + G_CALLBACK(_event_handler), this); + SP_CTRL(_canvas_item)->moveto(_position); +} + +/** 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! + + // 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, to be + 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_2BUTTON_PRESS: + // store the button number for next release + next_release_doubleclick = event->button.button; + return true; + + case GDK_BUTTON_PRESS: + next_release_doubleclick = 0; + if (event->button.button == 1) { + // mouse 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; + + case GDK_MOTION_NOTIFY: + if (held_button<1>(event->motion) && !_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. + next_release_doubleclick = 0; + _drag_origin = _position; + transferred = signal_grabbed.emit(&event->motion); + // _drag_initiated might change during the above signal emission + 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. + signal_dragged.emit(_position, 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, + reinterpret_cast<SPKnot*>(this), &event->motion, + DelayedSnapEvent::CONTROL_POINT_HANDLER); + } + return true; + } + break; + + case GDK_BUTTON_RELEASE: + if (!_event_grab) break; + + // TODO I think snapping on release is wrong, or at least counter-intuitive. + sp_event_context_snap_watchdog_callback(_desktop->event_context->_delayed_snap_event); + sp_event_context_discard_delayed_snap_event(_desktop->event_context); + _desktop->snapindicator->remove_snaptarget(); + + 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 (event->button.button == next_release_doubleclick) { + _drag_initiated = false; + return signal_doubleclicked.emit(&event->button); + } + if (event->button.button == 1) { + if (_drag_initiated) { + // it is the end of a drag + signal_ungrabbed.emit(&event->button); + _drag_initiated = false; + return true; + } else { + // it is the end of a click + return signal_clicked.emit(&event->button); + } + } + _drag_initiated = false; + 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_broken.keyboard && _event_grab) { + { + signal_ungrabbed.emit(0); + 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 + 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; + + signal_grabbed.emit(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); +} + +} // 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..4997c5ef4 --- /dev/null +++ b/src/ui/tool/control-point.h @@ -0,0 +1,169 @@ +/** @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 snapping 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 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..182362259 --- /dev/null +++ b/src/ui/tool/curve-drag-point.cpp @@ -0,0 +1,186 @@ +/** @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); + signal_grabbed.connect( + sigc::bind_return( + sigc::mem_fun(*this, &CurveDragPoint::_grabbedHandler), + false)); + signal_dragged.connect( + sigc::hide( + sigc::mem_fun(*this, &CurveDragPoint::_draggedHandler))); + signal_ungrabbed.connect( + sigc::hide( + sigc::mem_fun(*this, &CurveDragPoint::_ungrabbedHandler))); + signal_clicked.connect( + sigc::mem_fun(*this, &CurveDragPoint::_clickedHandler)); + signal_doubleclicked.connect( + sigc::mem_fun(*this, &CurveDragPoint::_doubleclickedHandler)); +} + +void CurveDragPoint::_grabbedHandler(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); + + signal_update.emit(); + } +} + +void CurveDragPoint::_draggedHandler(Geom::Point const &old_pos, Geom::Point const &new_pos) +{ + if (_drags_stroke) { + // TODO + } else { + 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 - old_pos; + 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); + } + + signal_update.emit(); +} + +void CurveDragPoint::_ungrabbedHandler() +{ + _pm._updateDragPoint(_desktop->d2w(position())); + _pm._commit(_("Drag curve")); + _pm._selection.restoreTransformHandles(); +} + +bool CurveDragPoint::_clickedHandler(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; + + 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::_doubleclickedHandler(GdkEventButton *event) +{ + if (event->button != 1 || !first || !first.next()) return false; + + // 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); + _pm._selection.clear(); + _pm._selection.insert(inserted.ptr()); + + signal_update.emit(); + _pm._commit(_("Add node")); + return true; +} + +Glib::ustring CurveDragPoint::_getTip(unsigned state) +{ + if (!first || !first.next()) return NULL; + bool linear = first->front()->isDegenerate() && first.next()->back()->isDegenerate(); + if (state_held_shift(state)) { + return C_("Path segment statusbar tip", + "<b>Shift:</b> click to toggle segment selection"); + } + if (linear) { + return C_("Path segment statusbar tip", + "<b>Linear segment:</b> drag to convert to a Bezier segment, " + "doubleclick to insert node, click to select this segment"); + } else { + return C_("Path segment statusbar tip", + "<b>Bezier segment:</b> drag to shape the segment, doubleclick to insert node, " + "click to select this segment"); + } +} + +} // 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..c9f32f709 --- /dev/null +++ b/src/ui/tool/curve-drag-point.h @@ -0,0 +1,60 @@ +/** @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; } + sigc::signal<void> signal_update; +protected: + virtual Glib::ustring _getTip(unsigned state); +private: + void _grabbedHandler(GdkEventMotion *); + void _draggedHandler(Geom::Point const &, Geom::Point const &); + bool _clickedHandler(GdkEventButton *); + bool _doubleclickedHandler(GdkEventButton *); + void _ungrabbedHandler(); + 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..6912897da --- /dev/null +++ b/src/ui/tool/event-utils.cpp @@ -0,0 +1,113 @@ +/** @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 <gdk/gdk.h> +#include <gdk/gdkkeysyms.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 consume_same_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; +} + +/** 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..74907d61c --- /dev/null +++ b/src/ui/tool/event-utils.h @@ -0,0 +1,129 @@ +/** @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> + +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 consume_same_key_events(guint keyval, 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/multi-path-manipulator.cpp b/src/ui/tool/multi-path-manipulator.cpp new file mode 100644 index 000000000..3ae7e4d24 --- /dev/null +++ b/src/ui/tool/multi-path-manipulator.cpp @@ -0,0 +1,598 @@ +/** @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 <tr1/unordered_set> +#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" + +namespace std { using namespace tr1; } + +namespace Inkscape { +namespace UI { + +namespace { +typedef std::pair<NodeList::iterator, NodeList::iterator> IterPair; +typedef std::vector<IterPair> IterPairList; +typedef std::unordered_set<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->first); + 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); + _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->first); + 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); + bool mouseover = true; + 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_front, pos_back; + pos_front = *i->second->front(); + pos_back = *i->first->back(); + if (i->first == preserve_pos) { + joined_pos = *i->first; + } else if (i->second == preserve_pos) { + joined_pos = *i->second; + } else { + joined_pos = Geom::middle_point(pos_back, pos_front); + mouseover = false; + } + + // 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_front); + } + if (!i->first->back()->isDegenerate()) { + joined_node->back()->setPosition(pos_back); + } + if (mouseover) { + // Second node could be mouseovered, but it will be deleted, so we must change + // the preserve_pos iterator to the first node. + preserve_pos = i->first; + } + 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() +{ + invokeForAll(&PathManipulator::reverseSubpaths); + _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; +} + +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) +{ + switch (event->type) { + case GDK_KEY_PRESS: + switch (shortcut_key(event->key)) { + case GDK_Insert: + case GDK_KP_Insert: + insertNodes(); + return true; + case GDK_i: + case GDK_I: + if (held_only_shift(event->key)) { + insertNodes(); + return true; + } + break; + case GDK_j: + case GDK_J: + if (held_only_shift(event->key)) { + joinNodes(); + return true; + } + if (held_only_alt(event->key)) { + joinSegments(); + return true; + } + break; + case GDK_b: + case GDK_B: + if (held_only_shift(event->key)) { + 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)) { + deleteSegments(); + } else { + deleteNodes(!held_control(event->key)); + } + return true; + case GDK_c: + case GDK_C: + if (held_only_shift(event->key)) { + setNodeType(NODE_CUSP); + return true; + } + break; + case GDK_s: + case GDK_S: + if (held_only_shift(event->key)) { + setNodeType(NODE_SMOOTH); + return true; + } + break; + case GDK_a: + case GDK_A: + if (held_only_shift(event->key)) { + setNodeType(NODE_AUTO); + return true; + } + break; + case GDK_y: + case GDK_Y: + if (held_only_shift(event->key)) { + setNodeType(NODE_SYMMETRIC); + return true; + } + break; + case GDK_r: + case GDK_R: + if (held_only_shift(event->key)) { + reverseSubpaths(); + break; + } + break; + default: + break; + } + break; + default: break; + } + + for (MapType::iterator i = _mmap.begin(); i != _mmap.end(); ++i) { + if (i->second->event(event)) return true; + } + 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..151e153ec --- /dev/null +++ b/src/ui/tool/multi-path-manipulator.h @@ -0,0 +1,132 @@ +/** @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/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 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; + bool _show_handles; + bool _show_outline; + bool _show_path_direction; + + 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..c1ba3394e --- /dev/null +++ b/src/ui/tool/node-tool.cpp @@ -0,0 +1,616 @@ +/** @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; + // TODO remove this! + 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, "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 == "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 = SHAPE_ROLE_LPE_PARAM; + 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); + } + } + } +} + +struct IsPath { + bool operator()(SPItem *i) const { return SP_IS_PATH(i); } +}; + +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)) { + 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: + // create outline + if (prefs->getBool("/tools/nodes/pathflash_enabled")) { + SPItem *over_item = sp_event_context_find_item (desktop, event_point(event->button), + FALSE, TRUE); + 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(); + } + return true; + 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)) { + if (held_alt(event->key)) { + nt->_selected_nodes->selectAll(); + } else { + // select all nodes in subpaths that have something selected + // if nothing is selected, select everything + nt->_multipath->selectSubpaths(); + } + 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)) { + 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")); + return; + } + } + unsigned sz = nt->_selected_nodes->size(); + if (sz != 0) { + char *dyntip = g_strdup_printf(C_("Node tool tip", + "Selected <b>%d nodes</b>. Drag to select nodes, click to select a single object " + "or unselect all objects"), sz); + nt->_node_message_context->set(Inkscape::NORMAL_MESSAGE, dyntip); + g_free(dyntip); + } else if (nt->_multipath->empty()) { + nt->_node_message_context->set(Inkscape::NORMAL_MESSAGE, + C_("Node tool tip", "Drag or click to select objects to edit")); + } else { + nt->_node_message_context->set(Inkscape::NORMAL_MESSAGE, + C_("Node tool tip", "Drag to select nodes, click to select an object " + "or clear the selection")); + } +} + +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 { + 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(); + } + return; + } + 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..65b16ff72 --- /dev/null +++ b/src/ui/tool/node-tool.h @@ -0,0 +1,85 @@ +/** @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; + + unsigned cursor_drag : 1; + unsigned show_outline : 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..303c0fb75 --- /dev/null +++ b/src/ui/tool/node.cpp @@ -0,0 +1,1204 @@ +/** @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. + */ + +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); + signal_grabbed.connect( + sigc::bind_return( + sigc::hide( + sigc::mem_fun(*this, &Handle::_grabbedHandler)), + false)); + signal_dragged.connect( + sigc::hide<0>( + sigc::mem_fun(*this, &Handle::_draggedHandler))); + signal_ungrabbed.connect( + sigc::hide(sigc::mem_fun(*this, &Handle::_ungrabbedHandler))); +} +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, *towards, *towards_second; + Node *node_towards; // node in direction of this handle + Node *node_away; // node in the opposite direction + if (this == &_parent->_front) { + other = &_parent->_back; + node_towards = _parent->_next(); + node_away = _parent->_prev(); + towards = node_towards ? &node_towards->_back : 0; + towards_second = node_towards ? &node_towards->_front : 0; + } else { + other = &_parent->_front; + node_towards = _parent->_prev(); + node_away = _parent->_next(); + towards = node_towards ? &node_towards->_front : 0; + towards_second = node_towards ? &node_towards->_back : 0; + } + + 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 ""; + } +} + +void Handle::_grabbedHandler() +{ + _saved_length = _drag_out ? 0 : length(); +} + +void Handle::_draggedHandler(Geom::Point &new_pos, GdkEventMotion *event) +{ + Geom::Point parent_pos = _parent->position(); + // with Alt, preserve length + if (held_alt(*event)) { + new_pos = parent_pos + Geom::unit_vector(new_pos - parent_pos) * _saved_length; + } + // with Ctrl, constrain to M_PI/rotationsnapsperpi increments. + if (held_control(*event)) { + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + int snaps = 2 * prefs->getIntLimited("/options/rotationsnapsperpi/value", 12, 1, 1000); + Geom::Point origin = _last_drag_origin(); + Geom::Point rel_origin = origin - parent_pos; + new_pos = parent_pos + Geom::constrain_angle(Geom::Point(0,0), new_pos - parent_pos, snaps, + _drag_out ? Geom::Point(1,0) : Geom::unit_vector(rel_origin)); + } + signal_update.emit(); +} + +void Handle::_ungrabbedHandler() +{ + // hide the handle if it's less than dragtolerance away from the node + 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()); + } + _drag_out = false; +} + +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) +{ + if (state_held_alt(state)) { + if (state_held_control(state)) { + return format_tip(C_("Path handle tip", + "<b>Ctrl+Alt</b>: preserve length and snap rotation angle to %f° increments"), + snap_increment_degrees()); + } else { + return C_("Path handle tip", + "<b>Alt:</b> preserve handle length while dragging"); + } + } else { + if (state_held_control(state)) { + return format_tip(C_("Path handle tip", + "<b>Ctrl:</b> snap rotation angle to %f° increments, click to retract"), + snap_increment_degrees()); + } + } + switch (_parent->type()) { + case NODE_AUTO: + return C_("Path handle tip", + "<b>Auto node handle:</b> drag to convert to smooth node"); + default: + return format_tip(C_("Path handle tip", "<b>%s:</b> drag to shape the curve"), + handle_type_to_localized_string(_parent->type())); + } +} + +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 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 + // connect to own grabbed signal - dragging out handles + signal_grabbed.connect( + sigc::mem_fun(*this, &Node::_grabbedHandler)); + signal_dragged.connect( sigc::hide<0>( + sigc::mem_fun(*this, &Node::_draggedHandler))); +} + +// 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 (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((_prev()->position() - position()).length() / 3 * dir); + } + } else if (_back.isDegenerate()) { + if (prev_line) { + _front.setDirection(*_prev(), *this); + } else if (_next()) { + Geom::Point dir = direction(_front, *this); + _back.setRelativePos((_next()->position() - position()).length() / 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::_grabbedHandler(GdkEventMotion *event) +{ + // 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::_draggedHandler(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; + Inkscape::SnapPreferences::PointType t = Inkscape::SnapPreferences::SNAPPOINT_NODE; + 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, true, &unselected); + } + + if (held_control(*event)) { + Geom::Point origin = _last_drag_origin(); + if (held_alt(*event)) { + // with Ctrl+Alt, constrain to handle lines + // project the new position onto a handle line that is closer + Inkscape::Snapper::ConstraintLine line_front(origin, _front.relativePos()); + Inkscape::Snapper::ConstraintLine line_back(origin, _back.relativePos()); + + // TODO: combine these two branches by modifying snap.h / snap.cpp + if (snap) { + Inkscape::SnappedPoint fp, bp; + fp = sm.constrainedSnap(t, Inkscape::SnapCandidatePoint(position(), _snapSourceType()), line_front); + bp = sm.constrainedSnap(t, Inkscape::SnapCandidatePoint(position(), _snapSourceType()), line_back); + + if (fp.isOtherSnapBetter(bp, false)) { + bp.getPoint(new_pos); + } else { + fp.getPoint(new_pos); + } + } else { + Geom::Point p_front = line_front.projection(new_pos); + Geom::Point p_back = line_back.projection(new_pos); + if (Geom::distance(new_pos, p_front) < Geom::distance(new_pos, p_back)) { + new_pos = p_front; + } else { + new_pos = p_back; + } + } + } else { + // with Ctrl, constrain to axes + // TODO combine the two branches + if (snap) { + Inkscape::SnappedPoint fp, bp; + Inkscape::Snapper::ConstraintLine line_x(origin, Geom::Point(1, 0)); + Inkscape::Snapper::ConstraintLine line_y(origin, Geom::Point(0, 1)); + fp = sm.constrainedSnap(t, Inkscape::SnapCandidatePoint(position(), _snapSourceType()), line_x); + bp = sm.constrainedSnap(t, Inkscape::SnapCandidatePoint(position(), _snapSourceType()), line_y); + + if (fp.isOtherSnapBetter(bp, false)) { + fp = bp; + } + fp.getPoint(new_pos); + } 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) { + sm.freeSnapReturnByRef(Inkscape::SnapPreferences::SNAPPOINT_NODE, new_pos, _snapSourceType()); + } +} + +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; +} + +Glib::ustring Node::_getTip(unsigned state) +{ + if (state_held_shift(state)) { + if ((_next() && _front.isDegenerate()) || (_prev() && _back.isDegenerate())) { + 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"); + } + return C_("Path node tip", + "<b>Ctrl:</b> move along axes, click to change node type"); + } + + // assemble tip from node name + char const *nodetype = node_type_to_localized_string(_type); + return format_tip(C_("Path node tip", + "<b>%s:</b> drag to shape the path, click to select this node"), 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 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->list = this; + this->next = this; + this->prev = this; +} + +NodeList::~NodeList() +{ + clear(); +} + +bool NodeList::empty() +{ + return next == this; +} + +NodeList::size_type NodeList::size() +{ + size_type sz = 0; + for (ListNode *ln = next; ln != this; 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->next = ins; + x->prev = ins->prev; + ins->prev->next = x; + ins->prev = x; + x->ListNode::list = this; + _list.signal_insert_node.emit(x); + 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->next) { + list._list.signal_remove_node.emit(static_cast<Node*>(ln)); + ln->list = this; + _list.signal_insert_node.emit(static_cast<Node*>(ln)); + } + ins_beg->prev->next = ins_end; + ins_end->prev->next = at; + at->prev->next = ins_beg; + + ListNode *atprev = at->prev; + at->prev = ins_end->prev; + ins_end->prev = ins_beg->prev; + ins_beg->prev = atprev; +} + +void NodeList::shift(int n) +{ + // 1. make the list perfectly cyclic + next->prev = prev; + prev->next = next; + // 2. find new begin + ListNode *new_begin = next; + if (n > 0) { + for (; n > 0; --n) new_begin = new_begin->next; + } else { + for (; n < 0; ++n) new_begin = new_begin->prev; + } + // 3. relink begin to list + next = new_begin; + prev = new_begin->prev; + new_begin->prev->next = this; + new_begin->prev = this; +} + +void NodeList::reverse() +{ + for (ListNode *ln = next; ln != this; ln = ln->prev) { + std::swap(ln->next, 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(next, 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->next, *rmprev = rm->prev; + ++i; + _list.signal_remove_node.emit(rm); + delete rm; + rmprev->next = rmnext; + rmnext->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->list()); +} +NodeList &NodeList::get(iterator const &i) { + return *(i._node->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..d822d854f --- /dev/null +++ b/src/ui/tool/node.h @@ -0,0 +1,398 @@ +/** @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 <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 *next; + ListNode *prev; + NodeList *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; } + + static char const *handle_type_to_localized_string(NodeType type); + sigc::signal<void> signal_update; +protected: + Handle(NodeSharedData const &data, Geom::Point const &initial_pos, Node *parent); + virtual Glib::ustring _getTip(unsigned state); + virtual Glib::ustring _getDragTip(GdkEventMotion *event); + virtual bool _hasDragTips() { return true; } +private: + void _grabbedHandler(); + void _draggedHandler(Geom::Point &, GdkEventMotion *); + void _ungrabbedHandler(); + 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 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; } + static NodeType parse_nodetype(char x); + NodeList *list() { return static_cast<ListNode*>(this)->list; } + void sink(); + + static char const *node_type_to_localized_string(NodeType type); + // temporarily public + virtual bool _eventHandler(GdkEvent *event); +protected: + 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 &); + bool _grabbedHandler(GdkEventMotion *); + void _draggedHandler(Geom::Point &, GdkEventMotion *); + 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(); + 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 works like a bidirectional iterator. + * + * Because paths can be cyclic, node iterators have two different ways to + * increment and decrement them. Nodes can be iterated over either in the + * sequence order, which always has a beginning and an end, or in the path order, + * which can be cyclic (moving to the next node never yields the end iterator). + * + * 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 if the path is cyclic; + * - <code>i.prev()</code> returns the previous node with wrap-around if the path is cyclic. + * + * 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 a node iterator is invalid + * (is an end iterator) 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->next; + return *this; + } + self &operator--() { + _node = _node->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 prev() const; +private: + NodeIterator(ListNode const *n) + : _node(const_cast<ListNode*>(n)) + {} + ListNode *_node; + friend class NodeList; + friend class std::tr1::hash<self>; +}; + +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; + typedef std::reverse_iterator<iterator> reverse_iterator; + typedef std::reverse_iterator<const_iterator> const_reverse_iterator; + + // TODO Lame. Make this private and make SubpathList a factory + NodeList(SubpathList &_list); + ~NodeList(); + + // iterators + iterator begin() { return iterator(next); } + iterator end() { return iterator(this); } + const_iterator begin() const { return const_iterator(next); } + const_iterator end() const { return const_iterator(this); } + reverse_iterator rbegin() { return reverse_iterator(end()); } + reverse_iterator rend() { return reverse_iterator(begin()); } + const_reverse_iterator rbegin() const { return const_reverse_iterator(end()); } + const_reverse_iterator rend() const { return const_reverse_iterator(begin()); } + + // 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*>(next); } + Node &back() { return *static_cast<Node*>(prev); } + + // HACK remove this subpath from its path. This will be removed later. + void kill(); + + 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) {} + + sigc::signal<void, Node *> signal_insert_node; + sigc::signal<void, Node *> signal_remove_node; +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(); +} + +// definitions for node iterator +template <typename N> +NodeIterator<N>::operator bool() const { + return _node && static_cast<ListNode*>(_node->list) != _node; +} +template <typename N> +NodeIterator<N> NodeIterator<N>::next() const { + NodeIterator<N> ret(*this); + ++ret; + if (!ret && _node->list->closed()) ++ret; + return ret; +} +template <typename N> +NodeIterator<N> NodeIterator<N>::prev() const { + NodeIterator<N> ret(*this); + --ret; + if (!ret && _node->list->closed()) --ret; + return ret; +} + +} // namespace UI +} // namespace Inkscape + +namespace std { +namespace tr1 { +template <typename N> +struct hash< Inkscape::UI::NodeIterator<N> > : public unary_function<Inkscape::UI::NodeIterator<N>, size_t> { + size_t operator()(Inkscape::UI::NodeIterator<N> const &ni) const { + return reinterpret_cast<size_t>(ni._node); + } +}; +} +} + +#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..9889eb787 --- /dev/null +++ b/src/ui/tool/path-manipulator.cpp @@ -0,0 +1,1318 @@ +/** @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) : _pm(p), _blocked(false) {} + virtual void notifyAttributeChanged(Inkscape::XML::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; + 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(NULL) + , _dragpoint(new CurveDragPoint(*this)) + , _observer(new PathManipulatorObserver(this)) + , _edit_transform(et) + , _show_handles(true) + , _show_outline(false) + , _lpe_key(lpe_key) +{ + /* Because curve drag point is always created first, it does not cover nodes */ + 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); + + _subpaths.signal_insert_node.connect( + sigc::mem_fun(*this, &PathManipulator::_attachNodeHandlers)); + _subpaths.signal_remove_node.connect( + sigc::mem_fun(*this, &PathManipulator::_removeNodeHandlers)); + _selection.signal_update.connect( + sigc::mem_fun(*this, &PathManipulator::update)); + _selection.signal_point_changed.connect( + sigc::mem_fun(*this, &PathManipulator::_selectionChanged)); + _dragpoint->signal_update.connect( + sigc::mem_fun(*this, &PathManipulator::update)); + _desktop->signal_zoom_changed.connect( + sigc::hide( sigc::mem_fun(*this, &PathManipulator::_updateOutlineOnZoomChange))); + + _createControlPointsFromGeometry(); + + _path->repr->addObserver(*_observer); +} + +PathManipulator::~PathManipulator() +{ + delete _dragpoint; + if (_path) _path->repr->removeObserver(*_observer); + delete _observer; + gtk_object_destroy(_outline); + if (_spcurve) _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 (!_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; + // 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! + + 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) 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) return; + _dragpoint->setVisible(false); + + 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) return; + _dragpoint->setVisible(false); + + 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) return; + hideDragPoint(); + + unsigned const samples_per_segment = 10; + double const t_step = 1.0 / samples_per_segment; + + 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->selected()) sel_beg = sel_beg.next(); + sel_end = sel_beg; + unsigned del_len = 0; + while (sel_end && sel_end->selected()) { + ++del_len; + sel_end = sel_end.next(); + } + + // 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 !sel_end then sel_beg.prev() must be valid, otherwise the entire subpath + // would be deleted before we get here + if ((keep_shape || !sel_end) && sel_beg.prev()) sel_beg.prev()->setType(NODE_CUSP, false); + if ((keep_shape || !sel_beg.prev()) && sel_end) sel_end->setType(NODE_CUSP, false); + + if (keep_shape && sel_beg.prev() && sel_end) { + // Fill fit data + 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 = sel_beg.prev(); cur != sel_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] = sel_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; + + sel_beg.prev()->front()->setPosition(result[1]); + sel_end->back()->setPosition(result[2]); + } + // We cannot simply use sp->erase(sel_beg, sel_end), because it would break + // for cases when the selected stretch crosses the beginning of the path + while (sel_beg != sel_end) { + NodeList::iterator next = sel_beg.next(); + sp->erase(sel_beg); + sel_beg = next; + } + num_selected -= del_len; + } + ++i; + } +} + +/** 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 the subpaths that have anything selected. */ +void PathManipulator::reverseSubpaths() +{ + for (SubpathList::iterator i = _subpaths.begin(); i != _subpaths.end(); ++i) { + for (NodeList::iterator j = (*i)->begin(); j != (*i)->end(); ++j) { + if (j->selected()) { + (*i)->reverse(); + break; // continue with the next subpath + } + } + } +} + +/** Make selected segments curves / lines. */ +void PathManipulator::setSegmentType(SegmentType type) +{ + if (!_num_selected) 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; + j->front()->move(j->position() + (k->position() - j->position()) / 3); + k->back()->move(k->position() + (j->position() - k->position()) / 3); + break; + } + } + } +} + +/** 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::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. */ +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(); ) { + 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? + 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()); + _updateOutline(); + _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())); + if (!_spcurve) + _spcurve = new SPCurve(pathparam->get_pathvector()); + else + _spcurve->set_pathvector(pathparam->get_pathvector()); + } + } else { + if (_spcurve) _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, true, 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; +} + +void PathManipulator::_attachNodeHandlers(Node *node) +{ + Handle *handles[2] = { node->front(), node->back() }; + for (int i = 0; i < 2; ++i) { + handles[i]->signal_update.connect( + sigc::mem_fun(*this, &PathManipulator::update)); + handles[i]->signal_ungrabbed.connect( + sigc::hide( + sigc::mem_fun(*this, &PathManipulator::_handleUngrabbed))); + handles[i]->signal_grabbed.connect( + sigc::bind_return( + sigc::hide( + sigc::mem_fun(*this, &PathManipulator::_handleGrabbed)), + false)); + handles[i]->signal_clicked.connect( + sigc::bind<0>( + sigc::mem_fun(*this, &PathManipulator::_handleClicked), + handles[i])); + } + node->signal_clicked.connect( + sigc::bind<0>( + sigc::mem_fun(*this, &PathManipulator::_nodeClicked), + node)); +} +void PathManipulator::_removeNodeHandlers(Node *node) +{ + // It is safe to assume that nobody else connected to handles' signals after us, + // so we pop our slots from the back. This preserves existing connections + // created by Node and Handle constructors. + Handle *handles[2] = { node->front(), node->back() }; + for (int i = 0; i < 2; ++i) { + handles[i]->signal_update.slots().pop_back(); + handles[i]->signal_grabbed.slots().pop_back(); + handles[i]->signal_ungrabbed.slots().pop_back(); + handles[i]->signal_clicked.slots().pop_back(); + } + // Same for this one: CPS only connects to grab, drag, and ungrab + node->signal_clicked.slots().pop_back(); +} + +bool PathManipulator::_nodeClicked(Node *n, GdkEventButton *event) +{ + // cycle between node types on ctrl+click + if (event->button != 1 || !held_control(*event)) return false; + 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; +} + +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) +{ + // 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); + } + } + } + + if (selected) ++_num_selected; + else --_num_selected; +} + +/** 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()); +} + +/** 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) +{ + // TODO find a way to make this faster (no transform required) + Geom::PathVector pv = _spcurve->get_pathvector() * (_edit_transform * _i2d_transform); + boost::optional<Geom::PathVectorPosition> pvp + = Geom::nearestPoint(pv, _desktop->w2d(evp)); + if (!pvp) return; + Geom::Point nearest_point = _desktop->d2w(pv.at(pvp->path_nr).pointAt(pvp->t)); + + 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)->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..99e183b45 --- /dev/null +++ b/src/ui/tool/path-manipulator.h @@ -0,0 +1,154 @@ +/** @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; + +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(); + void setSegmentType(SegmentType); + + void showOutline(bool show); + void showHandles(bool show); + void showPathDirection(bool show); + void setControlsTransform(Geom::Matrix const &); + void hideDragPoint(); + + 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(); + std::string _createTypeString(); + void _updateOutline(); + //void _setOutline(Geom::PathVector const &); + void _getGeometry(); + void _setGeometry(); + Glib::ustring _nodetypesKey(); + Inkscape::XML::Node *_getXMLNode(); + + void _attachNodeHandlers(Node *n); + void _removeNodeHandlers(Node *n); + + 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 _updateDragPoint(Geom::Point const &); + void _updateOutlineOnZoomChange(); + double _getStrokeTolerance(); + + 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; + Glib::ustring _lpe_key; + + friend class PathManipulatorObserver; + friend class CurveDragPoint; + friend class Node; +}; + +} // 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..5b9aa4fc8 --- /dev/null +++ b/src/ui/tool/selectable-control-point.cpp @@ -0,0 +1,137 @@ +/** @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) +{ + _connectHandlers(); +} +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) +{ + _connectHandlers(); +} + +SelectableControlPoint::~SelectableControlPoint() +{ + _selection.erase(this); + _selection.allPoints().erase(this); +} + +void SelectableControlPoint::_connectHandlers() +{ + _selection.allPoints().insert(this); + signal_clicked.connect( + sigc::mem_fun(*this, &SelectableControlPoint::_clickedHandler)); + signal_grabbed.connect( + sigc::bind_return( + sigc::mem_fun(*this, &SelectableControlPoint::_grabbedHandler), + false)); +} + +void SelectableControlPoint::_grabbedHandler(GdkEventMotion *event) +{ + // if a point is dragged while not selected, it should select itself + if (!selected()) { + _takeSelection(); + // HACK!!! invoke the last slot for signal_grabbed (it will be the callback registered + // by ControlPointSelection when adding to selection). + signal_grabbed.slots().back()(event); + } +} +bool SelectableControlPoint::_clickedHandler(GdkEventButton *event) +{ + 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..87e415258 --- /dev/null +++ b/src/ui/tool/selectable-control-point.h @@ -0,0 +1,71 @@ +/** @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); + + ControlPointSelection &_selection; +private: + void _connectHandlers(); + void _takeSelection(); + + bool _clickedHandler(GdkEventButton *); + void _grabbedHandler(GdkEventMotion *); +}; + +} // 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..f95c9e064 --- /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 fed */ +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); + + signal_clicked.connect(sigc::mem_fun(*this, &SelectorPoint::_clicked)); + signal_grabbed.connect( + sigc::bind_return( + sigc::hide( + sigc::mem_fun(*this, &SelectorPoint::_grabbed)), + false)); + signal_dragged.connect( + sigc::hide<0>( sigc::hide( + sigc::mem_fun(*this, &SelectorPoint::_dragged)))); + signal_ungrabbed.connect(sigc::mem_fun(*this, &SelectorPoint::_ungrabbed)); + } + ~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: + bool _clicked(GdkEventButton *event) { + if (event->button != 1) return false; + _selector->signal_point.emit(position(), event); + return true; + } + void _grabbed() { + _cancel = false; + _start = position(); + sp_canvas_item_show(_rubber); + } + void _dragged(Geom::Point &new_pos) { + if (_cancel) return; + Geom::Rect sel(_start, new_pos); + _rubber->setRectangle(sel); + } + void _ungrabbed(GdkEventButton *event) { + if (_cancel) return; + sp_canvas_item_hide(_rubber); + Geom::Rect sel(_start, position()); + _selector->signal_area.emit(sel, event); + } + 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) +{ + switch (event->type) { + case GDK_BUTTON_PRESS: + _dragger->setPosition(_desktop->w2d(event_point(event->motion))); + break; + default: break; + } + return _dragger->event(event); +} + +} // 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..fdb49ac36 --- /dev/null +++ b/src/ui/tool/shape-record.h @@ -0,0 +1,57 @@ +/** @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; } + inline bool operator<(ShapeRecord const &o) const { return 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..cf8907299 --- /dev/null +++ b/src/ui/tool/transform-handle-set.cpp @@ -0,0 +1,644 @@ +/** @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); + signal_grabbed.connect( + sigc::bind_return( + sigc::hide( + sigc::mem_fun(*this, &TransformHandle::_grabbedHandler)), + false)); + signal_dragged.connect( + sigc::hide<0>( + sigc::mem_fun(*this, &TransformHandle::_draggedHandler))); + signal_ungrabbed.connect( + sigc::hide( + sigc::mem_fun(*this, &TransformHandle::_ungrabbedHandler))); + } +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: + void _grabbedHandler() { + _origin = position(); + _last_transform.setIdentity(); + startTransform(); + + _th._setActiveHandle(this); + _cset = &invisible_cset; + _setState(_state); + } + void _draggedHandler(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; + } + void _ungrabbedHandler() { + _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(), + ¢er_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/util/accumulators.h b/src/util/accumulators.h new file mode 100644 index 000000000..c627786b1 --- /dev/null +++ b/src/util/accumulators.h @@ -0,0 +1,113 @@ +/** @file + * Frequently used accumulators for use with libsigc++ + */ +/* 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_UTIL_ACCUMULATORS_H +#define SEEN_UTIL_ACCUMULATORS_H + +#include <iterator> + +namespace Inkscape { +namespace Util { + +/** + * Accumulator which evaluates slots in reverse connection order. + * The slot that was connected last is evaluated first. + */ +struct Reverse { + typedef void result_type; + template <typename T_iterator> + result_type operator()(T_iterator first, T_iterator last) const { + while (first != last) *(--last); + } +}; + +/** + * Accumulator type for interruptible signals. Slots return a boolean value; emission + * is stopped when true is returned from a slot. + */ +struct Interruptible { + typedef bool result_type; + template <typename T_iterator> + result_type operator()(T_iterator first, T_iterator last) const { + for (; first != last; ++first) + if (*first) return true; + return false; + } +}; + +/** + * Same as Interruptible, but the slots are called in reverse order of connection, + * e.g. the slot that was connected last is evaluated first. + */ +struct ReverseInterruptible { + typedef bool result_type; + template <typename T_iterator> + result_type operator()(T_iterator first, T_iterator last) const { + while (first != last) { + if (*(--last)) return true; + } + return false; + } +}; + +/** + * The template parameter specifies how many slots from the beginning of the list + * should be evaluated after other slots. Useful for signals which invoke other signals + * once complete. Undefined results if the signal does not have at least @c num_chained + * slots before first emission. + * + * For example, if template param = 3, the execution order is as follows: + * @verbatim + 8. 1. 2. 3. 4. 5. 6. 7. + S1 S2 S3 S4 S5 S6 S7 S8 @endverbatim + */ +template <unsigned num_chained = 1> +struct Chained { + typedef void result_type; + template <typename T_iterator> + result_type operator()(T_iterator first, T_iterator last) const { + T_iterator save_first = first; + // ARGH, iterator_traits is not defined for slot iterators! + //std::advance(first, num_chained); + for (unsigned i = 0; i < num_chained && first != last; ++i) ++first; + for (; first != last; ++first) *first; + for (unsigned i = 0; i < num_chained && save_first != last; ++i, ++save_first) + *save_first; + } +}; + +/** + * Executes a logical OR on the results from slots. + */ +struct LogicalOr { + typedef bool result_type; + template <typename T_iterator> + result_type operator()(T_iterator first, T_iterator last) const { + bool ret = false; + for (; first != last; ++first) ret |= *first; + return ret; + } +}; + +} // namespace Util +} // 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/util/hash.h b/src/util/hash.h new file mode 100644 index 000000000..d5abeff5a --- /dev/null +++ b/src/util/hash.h @@ -0,0 +1,41 @@ +/** @file + * Hash function for various things + */ +/* 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_UTIL_HASH_H +#define SEEN_UTIL_HASH_H + +#include <boost/shared_ptr.hpp> + +namespace std { +namespace tr1 { + +/** Hash function for Boost shared pointers */ +template <typename T> +struct hash< boost::shared_ptr<T> > : public std::unary_function<boost::shared_ptr<T>, size_t> { + size_t operator()(boost::shared_ptr<T> p) const { + return reinterpret_cast<size_t>(p.get()); + } +}; + +} // namespace tr1 +} // namespace std + +#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/verbs.cpp b/src/verbs.cpp index 6f86c3cce..37f4da4d6 100644 --- a/src/verbs.cpp +++ b/src/verbs.cpp @@ -58,7 +58,6 @@ #include "layer-fns.h" #include "layer-manager.h" #include "message-stack.h" -#include "node-context.h" #include "path-chemistry.h" #include "preferences.h" #include "select-context.h" @@ -80,6 +79,9 @@ #include "ui/dialog/layers.h" #include "ui/dialog/swatches.h" #include "ui/icon-names.h" +#include "ui/tool/control-point-selection.h" +#include "ui/tool/multi-path-manipulator.h" +#include "ui/tool/node-tool.h" //#ifdef WITH_INKBOARD //#include "jabber_whiteboard/session-manager.h" @@ -919,28 +921,32 @@ EditVerb::perform(SPAction *action, void *data, void */*pdata*/) break; case SP_VERB_EDIT_SELECT_ALL: if (tools_isactive(dt, TOOLS_NODES)) { - ec->shape_editor->select_all_from_subpath(false); + InkNodeTool *nt = static_cast<InkNodeTool*>(dt->event_context); + nt->_multipath->selectSubpaths(); } else { sp_edit_select_all(dt); } break; case SP_VERB_EDIT_INVERT: if (tools_isactive(dt, TOOLS_NODES)) { - ec->shape_editor->select_all_from_subpath(true); + InkNodeTool *nt = static_cast<InkNodeTool*>(dt->event_context); + nt->_multipath->invertSelectionInSubpaths(); } else { sp_edit_invert(dt); } break; case SP_VERB_EDIT_SELECT_ALL_IN_ALL_LAYERS: if (tools_isactive(dt, TOOLS_NODES)) { - ec->shape_editor->select_all(false); + InkNodeTool *nt = static_cast<InkNodeTool*>(dt->event_context); + nt->_selected_nodes->selectAll(); } else { sp_edit_select_all_in_all_layers(dt); } break; case SP_VERB_EDIT_INVERT_IN_ALL_LAYERS: if (tools_isactive(dt, TOOLS_NODES)) { - ec->shape_editor->select_all(true); + InkNodeTool *nt = static_cast<InkNodeTool*>(dt->event_context); + nt->_selected_nodes->invertSelection(); } else { sp_edit_invert_in_all_layers(dt); } @@ -948,7 +954,8 @@ EditVerb::perform(SPAction *action, void *data, void */*pdata*/) case SP_VERB_EDIT_SELECT_NEXT: if (tools_isactive(dt, TOOLS_NODES)) { - ec->shape_editor->select_next(); + InkNodeTool *nt = static_cast<InkNodeTool*>(dt->event_context); + nt->_multipath->shiftSelection(1); } else if (tools_isactive(dt, TOOLS_GRADIENT) && ec->_grdrag->isNonEmpty()) { sp_gradient_context_select_next (ec); @@ -958,7 +965,8 @@ EditVerb::perform(SPAction *action, void *data, void */*pdata*/) break; case SP_VERB_EDIT_SELECT_PREV: if (tools_isactive(dt, TOOLS_NODES)) { - ec->shape_editor->select_prev(); + InkNodeTool *nt = static_cast<InkNodeTool*>(dt->event_context); + nt->_multipath->shiftSelection(-1); } else if (tools_isactive(dt, TOOLS_GRADIENT) && ec->_grdrag->isNonEmpty()) { sp_gradient_context_select_prev (ec); @@ -969,7 +977,8 @@ EditVerb::perform(SPAction *action, void *data, void */*pdata*/) case SP_VERB_EDIT_DESELECT: if (tools_isactive(dt, TOOLS_NODES)) { - ec->shape_editor->deselect(); + InkNodeTool *nt = static_cast<InkNodeTool*>(dt->event_context); + nt->_selected_nodes->clear(); } else { sp_desktop_selection(dt)->clear(); } @@ -1086,7 +1095,13 @@ SelectionVerb::perform(SPAction *action, void *data, void */*pdata*/) sp_selected_path_simplify(dt); break; case SP_VERB_SELECTION_REVERSE: - sp_selected_path_reverse(dt); + // TODO make this a virtual method of event context! + if (tools_isactive(dt, TOOLS_NODES)) { + InkNodeTool *nt = static_cast<InkNodeTool*>(dt->event_context); + nt->_multipath->reverseSubpaths(); + } else { + sp_selected_path_reverse(dt); + } break; case SP_VERB_SELECTION_TRACE: inkscape_dialogs_unhide(); @@ -1365,41 +1380,12 @@ ObjectVerb::perform( SPAction *action, void *data, void */*pdata*/ ) flowtext_to_text(); break; case SP_VERB_OBJECT_FLIP_HORIZONTAL: - // When working with the node tool ... - if (tools_isactive(dt, TOOLS_NODES)) { - Inkscape::NodePath::Node *active_node = Inkscape::NodePath::Path::active_node; - - // ... and one of the nodes is currently mouseovered ... - if (active_node) { - - // ... flip the selected nodes about that node - ec->shape_editor->flip(Geom::X, active_node->pos); - } else { - - // ... or else about the center of their bounding box. - ec->shape_editor->flip(Geom::X); - } - - // When working with the selector tool, flip the selection about its rotation center - // (if it is visible) or about the center of the bounding box. - } else { - sp_selection_scale_relative(sel, center, Geom::Scale(-1.0, 1.0)); - } + sp_selection_scale_relative(sel, center, Geom::Scale(-1.0, 1.0)); sp_document_done(sp_desktop_document(dt), SP_VERB_OBJECT_FLIP_HORIZONTAL, _("Flip horizontally")); break; case SP_VERB_OBJECT_FLIP_VERTICAL: - // The behaviour is analogous to flipping horizontally - if (tools_isactive(dt, TOOLS_NODES)) { - Inkscape::NodePath::Node *active_node = Inkscape::NodePath::Path::active_node; - if (active_node) { - ec->shape_editor->flip(Geom::Y, active_node->pos); - } else { - ec->shape_editor->flip(Geom::Y); - } - } else { - sp_selection_scale_relative(sel, center, Geom::Scale(1.0, -1.0)); - } + sp_selection_scale_relative(sel, center, Geom::Scale(1.0, -1.0)); sp_document_done(sp_desktop_document(dt), SP_VERB_OBJECT_FLIP_VERTICAL, _("Flip vertically")); break; diff --git a/src/widgets/toolbox.cpp b/src/widgets/toolbox.cpp index 8b8ea1a01..00f83cdbd 100644 --- a/src/widgets/toolbox.cpp +++ b/src/widgets/toolbox.cpp @@ -9,7 +9,7 @@ * Frank Felfe <innerspace@iname.com> * John Cliff <simarilius@yahoo.com> * David Turner <novalis@gnu.org> - * Josh Andler <scislac@users.sf.net> + * Josh Andler <scislac@scislac.com> * Jon A. Cruz <jon@joncruz.org> * Maximilian Albert <maximilian.albert@gmail.com> * @@ -65,7 +65,6 @@ #include "../live_effects/lpe-line_segment.h" #include "../lpe-tool-context.h" #include "../mod360.h" -#include "../node-context.h" #include "../pen-context.h" #include "../preferences.h" #include "../selection-chemistry.h" @@ -89,6 +88,9 @@ #include "../spray-context.h" #include "../ui/dialog/calligraphic-profile-rename.h" #include "../ui/icon-names.h" +#include "../ui/tool/control-point-selection.h" +#include "../ui/tool/node-tool.h" +#include "../ui/tool/multi-path-manipulator.h" #include "../ui/widget/style-swatch.h" #include "../verbs.h" #include "../widgets/button.h" @@ -171,7 +173,7 @@ static struct { sp_verb_t doubleclick_verb; } const tools[] = { { "SPSelectContext", "select_tool", SP_VERB_CONTEXT_SELECT, SP_VERB_CONTEXT_SELECT_PREFS}, - { "SPNodeContext", "node_tool", SP_VERB_CONTEXT_NODE, SP_VERB_CONTEXT_NODE_PREFS }, + { "InkNodeTool", "node_tool", SP_VERB_CONTEXT_NODE, SP_VERB_CONTEXT_NODE_PREFS }, { "SPTweakContext", "tweak_tool", SP_VERB_CONTEXT_TWEAK, SP_VERB_CONTEXT_TWEAK_PREFS }, { "SPSprayContext", "spray_tool", SP_VERB_CONTEXT_SPRAY, SP_VERB_CONTEXT_SPRAY_PREFS }, { "SPZoomContext", "zoom_tool", SP_VERB_CONTEXT_ZOOM, SP_VERB_CONTEXT_ZOOM_PREFS }, @@ -205,7 +207,7 @@ static struct { } const aux_toolboxes[] = { { "SPSelectContext", "select_toolbox", 0, sp_select_toolbox_prep, "SelectToolbar", SP_VERB_INVALID, 0, 0}, - { "SPNodeContext", "node_toolbox", 0, sp_node_toolbox_prep, "NodeToolbar", + { "InkNodeTool", "node_toolbox", 0, sp_node_toolbox_prep, "NodeToolbar", SP_VERB_INVALID, 0, 0}, { "SPTweakContext", "tweak_toolbox", 0, sp_tweak_toolbox_prep, "TweakToolbar", SP_VERB_CONTEXT_TWEAK_PREFS, "/tools/tweak", N_("Color/opacity used for color tweaking")}, @@ -1015,135 +1017,127 @@ static EgeAdjustmentAction * create_adjustment_action( gchar const *name, //# node editing callbacks //#################################### -/** - * FIXME: Returns current shape_editor in context. // later eliminate this function at all! - */ -static ShapeEditor *get_current_shape_editor() +/** Temporary hack: Returns the node tool in the active desktop. + * Will go away during tool refactoring. */ +static InkNodeTool *get_node_tool() { - if (!SP_ACTIVE_DESKTOP) { - return NULL; - } - - SPEventContext *event_context = (SP_ACTIVE_DESKTOP)->event_context; - - if (!SP_IS_NODE_CONTEXT(event_context)) { - return NULL; - } - - return event_context->shape_editor; + if (!SP_ACTIVE_DESKTOP) return NULL; + SPEventContext *ec = SP_ACTIVE_DESKTOP->event_context; + if (!INK_IS_NODE_TOOL(ec)) return NULL; + return static_cast<InkNodeTool*>(ec); } void sp_node_path_edit_add(void) { - ShapeEditor *shape_editor = get_current_shape_editor(); - if (shape_editor) shape_editor->add_node(); + InkNodeTool *nt = get_node_tool(); + if (nt) nt->_multipath->insertNodes(); } void sp_node_path_edit_delete(void) { - ShapeEditor *shape_editor = get_current_shape_editor(); - if (shape_editor) shape_editor->delete_nodes_preserving_shape(); + InkNodeTool *nt = get_node_tool(); + if (nt) nt->_multipath->deleteNodes(); } void sp_node_path_edit_delete_segment(void) { - ShapeEditor *shape_editor = get_current_shape_editor(); - if (shape_editor) shape_editor->delete_segment(); + InkNodeTool *nt = get_node_tool(); + if (nt) nt->_multipath->deleteSegments(); } void sp_node_path_edit_break(void) { - ShapeEditor *shape_editor = get_current_shape_editor(); - if (shape_editor) shape_editor->break_at_nodes(); + InkNodeTool *nt = get_node_tool(); + if (nt) nt->_multipath->breakNodes(); } void sp_node_path_edit_join(void) { - ShapeEditor *shape_editor = get_current_shape_editor(); - if (shape_editor) shape_editor->join_nodes(); + InkNodeTool *nt = get_node_tool(); + if (nt) nt->_multipath->joinNodes(); } void sp_node_path_edit_join_segment(void) { - ShapeEditor *shape_editor = get_current_shape_editor(); - if (shape_editor) shape_editor->join_segments(); + InkNodeTool *nt = get_node_tool(); + if (nt) nt->_multipath->joinSegments(); } void sp_node_path_edit_toline(void) { - ShapeEditor *shape_editor = get_current_shape_editor(); - if (shape_editor) shape_editor->set_type_of_segments(NR_LINETO); + InkNodeTool *nt = get_node_tool(); + if (nt) nt->_multipath->setSegmentType(Inkscape::UI::SEGMENT_STRAIGHT); } void sp_node_path_edit_tocurve(void) { - ShapeEditor *shape_editor = get_current_shape_editor(); - if (shape_editor) shape_editor->set_type_of_segments(NR_CURVETO); + InkNodeTool *nt = get_node_tool(); + if (nt) nt->_multipath->setSegmentType(Inkscape::UI::SEGMENT_CUBIC_BEZIER); } void sp_node_path_edit_cusp(void) { - ShapeEditor *shape_editor = get_current_shape_editor(); - if (shape_editor) shape_editor->set_node_type(Inkscape::NodePath::NODE_CUSP); + InkNodeTool *nt = get_node_tool(); + if (nt) nt->_multipath->setNodeType(Inkscape::UI::NODE_CUSP); } void sp_node_path_edit_smooth(void) { - ShapeEditor *shape_editor = get_current_shape_editor(); - if (shape_editor) shape_editor->set_node_type(Inkscape::NodePath::NODE_SMOOTH); + InkNodeTool *nt = get_node_tool(); + if (nt) nt->_multipath->setNodeType(Inkscape::UI::NODE_SMOOTH); } void sp_node_path_edit_symmetrical(void) { - ShapeEditor *shape_editor = get_current_shape_editor(); - if (shape_editor) shape_editor->set_node_type(Inkscape::NodePath::NODE_SYMM); + InkNodeTool *nt = get_node_tool(); + if (nt) nt->_multipath->setNodeType(Inkscape::UI::NODE_SYMMETRIC); } void sp_node_path_edit_auto(void) { - ShapeEditor *shape_editor = get_current_shape_editor(); - if (shape_editor) shape_editor->set_node_type(Inkscape::NodePath::NODE_AUTO); + InkNodeTool *nt = get_node_tool(); + if (nt) nt->_multipath->setNodeType(Inkscape::UI::NODE_AUTO); } static void toggle_show_handles (GtkToggleAction *act, gpointer /*data*/) { Inkscape::Preferences *prefs = Inkscape::Preferences::get(); bool show = gtk_toggle_action_get_active( act ); prefs->setBool("/tools/nodes/show_handles", show); - ShapeEditor *shape_editor = get_current_shape_editor(); - if (shape_editor) shape_editor->show_handles(show); } static void toggle_show_helperpath (GtkToggleAction *act, gpointer /*data*/) { Inkscape::Preferences *prefs = Inkscape::Preferences::get(); bool show = gtk_toggle_action_get_active( act ); - prefs->setBool("/tools/nodes/show_helperpath", show); - ShapeEditor *shape_editor = get_current_shape_editor(); - if (shape_editor) shape_editor->show_helperpath(show); + prefs->setBool("/tools/nodes/show_outline", show); } void sp_node_path_edit_nextLPEparam (GtkAction */*act*/, gpointer data) { sp_selection_next_patheffect_param( reinterpret_cast<SPDesktop*>(data) ); } -void sp_node_path_edit_clippath (GtkAction */*act*/, gpointer data) { - sp_selection_edit_clip_or_mask( reinterpret_cast<SPDesktop*>(data), true); +void toggle_edit_clip (GtkToggleAction *act, gpointer data) { + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + bool edit = gtk_toggle_action_get_active( act ); + prefs->setBool("/tools/nodes/edit_clipping_paths", edit); } -void sp_node_path_edit_maskpath (GtkAction */*act*/, gpointer data) { - sp_selection_edit_clip_or_mask( reinterpret_cast<SPDesktop*>(data), false); +void toggle_edit_mask (GtkToggleAction *act, gpointer data) { + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + bool edit = gtk_toggle_action_get_active( act ); + prefs->setBool("/tools/nodes/edit_masks", edit); } /* is called when the node selection is modified */ @@ -1166,54 +1160,29 @@ sp_node_toolbox_coord_changed(gpointer /*shape_editor*/, GObject *tbl) UnitTracker* tracker = reinterpret_cast<UnitTracker*>( g_object_get_data( tbl, "tracker" ) ); SPUnit const *unit = tracker->getActiveUnit(); - ShapeEditor *shape_editor = get_current_shape_editor(); - if (shape_editor && shape_editor->has_nodepath()) { - Inkscape::NodePath::Path *nodepath = shape_editor->get_nodepath(); - int n_selected = 0; - if (nodepath) { - n_selected = nodepath->numSelected(); - } - - if (n_selected == 0) { - gtk_action_set_sensitive(xact, FALSE); - gtk_action_set_sensitive(yact, FALSE); - } else { - gtk_action_set_sensitive(xact, TRUE); - gtk_action_set_sensitive(yact, TRUE); - Geom::Coord oldx = sp_units_get_pixels(gtk_adjustment_get_value(xadj), *unit); - Geom::Coord oldy = sp_units_get_pixels(gtk_adjustment_get_value(xadj), *unit); - - if (n_selected == 1) { - Geom::Point sel_node = nodepath->singleSelectedCoords(); - if (oldx != sel_node[Geom::X] || oldy != sel_node[Geom::Y]) { - gtk_adjustment_set_value(xadj, sp_pixels_get_units(sel_node[Geom::X], *unit)); - gtk_adjustment_set_value(yadj, sp_pixels_get_units(sel_node[Geom::Y], *unit)); - } - } else { - boost::optional<Geom::Coord> x = sp_node_selected_common_coord(nodepath, Geom::X); - boost::optional<Geom::Coord> y = sp_node_selected_common_coord(nodepath, Geom::Y); - if ((x && ((*x) != oldx)) || (y && ((*y) != oldy))) { - /* Note: Currently x and y will always have a value, even if the coordinates of the - selected nodes don't coincide (in this case we use the coordinates of the center - of the bounding box). So the entries are never set to zero. */ - // FIXME: Maybe we should clear the entry if several nodes are selected - // instead of providing a kind of average value - gtk_adjustment_set_value(xadj, sp_pixels_get_units(x ? (*x) : 0.0, *unit)); - gtk_adjustment_set_value(yadj, sp_pixels_get_units(y ? (*y) : 0.0, *unit)); - } - } - } - } else { - // no shape-editor or nodepath yet (when we just switched to the tool); coord entries must be inactive + InkNodeTool *nt = get_node_tool(); + if (!nt || nt->_selected_nodes->empty()) { + // no path selected gtk_action_set_sensitive(xact, FALSE); gtk_action_set_sensitive(yact, FALSE); + } else { + gtk_action_set_sensitive(xact, TRUE); + gtk_action_set_sensitive(yact, TRUE); + Geom::Coord oldx = sp_units_get_pixels(gtk_adjustment_get_value(xadj), *unit); + Geom::Coord oldy = sp_units_get_pixels(gtk_adjustment_get_value(xadj), *unit); + Geom::Point mid = nt->_selected_nodes->pointwiseBounds()->midpoint(); + + if (oldx != mid[Geom::X]) + gtk_adjustment_set_value(xadj, sp_pixels_get_units(mid[Geom::X], *unit)); + if (oldy != mid[Geom::Y]) + gtk_adjustment_set_value(yadj, sp_pixels_get_units(mid[Geom::Y], *unit)); } g_object_set_data( tbl, "freeze", GINT_TO_POINTER(FALSE) ); } static void -sp_node_path_value_changed(GtkAdjustment *adj, GObject *tbl, gchar const *value_name) +sp_node_path_value_changed(GtkAdjustment *adj, GObject *tbl, Geom::Dim2 d) { SPDesktop *desktop = (SPDesktop *) g_object_get_data( tbl, "desktop" ); Inkscape::Preferences *prefs = Inkscape::Preferences::get(); @@ -1222,7 +1191,8 @@ sp_node_path_value_changed(GtkAdjustment *adj, GObject *tbl, gchar const *value_ SPUnit const *unit = tracker->getActiveUnit(); if (sp_document_get_undo_sensitive(sp_desktop_document(desktop))) { - prefs->setDouble(Glib::ustring("/tools/nodes/") + value_name, sp_units_get_pixels(adj->value, *unit)); + prefs->setDouble(Glib::ustring("/tools/nodes/") + (d == Geom::X ? "x" : "y"), + sp_units_get_pixels(adj->value, *unit)); } // quit if run by the attr_changed listener @@ -1233,15 +1203,13 @@ sp_node_path_value_changed(GtkAdjustment *adj, GObject *tbl, gchar const *value_ // in turn, prevent listener from responding g_object_set_data( tbl, "freeze", GINT_TO_POINTER(TRUE)); - ShapeEditor *shape_editor = get_current_shape_editor(); - if (shape_editor && shape_editor->has_nodepath()) { + InkNodeTool *nt = get_node_tool(); + if (nt && !nt->_selected_nodes->empty()) { double val = sp_units_get_pixels(gtk_adjustment_get_value(adj), *unit); - if (!strcmp(value_name, "x")) { - sp_node_selected_move_absolute(shape_editor->get_nodepath(), val, Geom::X); - } - if (!strcmp(value_name, "y")) { - sp_node_selected_move_absolute(shape_editor->get_nodepath(), val, Geom::Y); - } + double oldval = nt->_selected_nodes->pointwiseBounds()->midpoint()[d]; + Geom::Point delta(0,0); + delta[d] = val - oldval; + nt->_multipath->move(delta); } g_object_set_data( tbl, "freeze", GINT_TO_POINTER(FALSE) ); @@ -1250,13 +1218,13 @@ sp_node_path_value_changed(GtkAdjustment *adj, GObject *tbl, gchar const *value_ static void sp_node_path_x_value_changed(GtkAdjustment *adj, GObject *tbl) { - sp_node_path_value_changed(adj, tbl, "x"); + sp_node_path_value_changed(adj, tbl, Geom::X); } static void sp_node_path_y_value_changed(GtkAdjustment *adj, GObject *tbl) { - sp_node_path_value_changed(adj, tbl, "y"); + sp_node_path_value_changed(adj, tbl, Geom::Y); } void @@ -1275,26 +1243,6 @@ sp_node_toolbox_sel_changed (Inkscape::Selection *selection, GObject *tbl) gtk_action_set_sensitive(w, FALSE); } } - - { - GtkAction* w = GTK_ACTION( g_object_get_data( tbl, "nodes_clippathedit" ) ); - SPItem *item = selection->singleItem(); - if (item && item->clip_ref && item->clip_ref->getObject()) { - gtk_action_set_sensitive(w, TRUE); - } else { - gtk_action_set_sensitive(w, FALSE); - } - } - - { - GtkAction* w = GTK_ACTION( g_object_get_data( tbl, "nodes_maskedit" ) ); - SPItem *item = selection->singleItem(); - if (item && item->mask_ref && item->mask_ref->getObject()) { - gtk_action_set_sensitive(w, TRUE); - } else { - gtk_action_set_sensitive(w, FALSE); - } - } } void @@ -1342,8 +1290,8 @@ static void sp_node_toolbox_prep(SPDesktop *desktop, GtkActionGroup* mainActions { InkAction* inky = ink_action_new( "NodeJoinAction", - _("Join endnodes"), - _("Join selected endnodes"), + _("Join nodes"), + _("Join selected nodes"), INKSCAPE_ICON_NODE_JOIN, secondarySize ); g_object_set( inky, "short_label", _("Join"), NULL ); @@ -1461,7 +1409,7 @@ static void sp_node_toolbox_prep(SPDesktop *desktop, GtkActionGroup* mainActions Inkscape::ICON_SIZE_DECORATION ); gtk_action_group_add_action( mainActions, GTK_ACTION( act ) ); g_signal_connect_after( G_OBJECT(act), "toggled", G_CALLBACK(toggle_show_helperpath), desktop ); - gtk_toggle_action_set_active( GTK_TOGGLE_ACTION(act), prefs->getBool("/tools/nodes/show_helperpath", false) ); + gtk_toggle_action_set_active( GTK_TOGGLE_ACTION(act), prefs->getBool("/tools/nodes/show_outline", false) ); } { @@ -1476,25 +1424,25 @@ static void sp_node_toolbox_prep(SPDesktop *desktop, GtkActionGroup* mainActions } { - InkAction* inky = ink_action_new( "ObjectEditClipPathAction", - _("Edit clipping path"), - _("Edit the clipping path of the object"), + InkToggleAction* inky = ink_toggle_action_new( "ObjectEditClipPathAction", + _("Edit clipping paths"), + _("Show editing controls for clipping paths of selected objects"), INKSCAPE_ICON_PATH_CLIP_EDIT, Inkscape::ICON_SIZE_DECORATION ); - g_signal_connect_after( G_OBJECT(inky), "activate", G_CALLBACK(sp_node_path_edit_clippath), desktop ); gtk_action_group_add_action( mainActions, GTK_ACTION(inky) ); - g_object_set_data( holder, "nodes_clippathedit", inky); + g_signal_connect_after( G_OBJECT(inky), "toggled", G_CALLBACK(toggle_edit_clip), desktop ); + gtk_toggle_action_set_active( GTK_TOGGLE_ACTION(inky), prefs->getBool("/tools/nodes/edit_clipping_paths") ); } { - InkAction* inky = ink_action_new( "ObjectEditMaskPathAction", - _("Edit mask path"), - _("Edit the mask of the object"), + InkToggleAction* inky = ink_toggle_action_new( "ObjectEditMaskPathAction", + _("Edit masks"), + _("Show editing controls for masks of selected objects"), INKSCAPE_ICON_PATH_MASK_EDIT, Inkscape::ICON_SIZE_DECORATION ); - g_signal_connect_after( G_OBJECT(inky), "activate", G_CALLBACK(sp_node_path_edit_maskpath), desktop ); gtk_action_group_add_action( mainActions, GTK_ACTION(inky) ); - g_object_set_data( holder, "nodes_maskedit", inky); + g_signal_connect_after( G_OBJECT(inky), "toggled", G_CALLBACK(toggle_edit_mask), desktop ); + gtk_toggle_action_set_active( GTK_TOGGLE_ACTION(inky), prefs->getBool("/tools/nodes/edit_masks") ); } /* X coord of selected node(s) */ @@ -6749,10 +6697,10 @@ sp_text_toolbox_family_keypress (GtkWidget */*w*/, GdkEventKey *event, GObject * case GDK_Return: // unfreeze and update, which will defocus g_object_set_data( tbl, "freeze", GINT_TO_POINTER(FALSE) ); - sp_text_toolbox_family_changed (NULL, tbl); + sp_text_toolbox_family_changed (NULL, tbl); return TRUE; // I consumed the event break; - case GDK_Escape: + case GDK_Escape: // defocus gtk_widget_grab_focus (GTK_WIDGET(desktop->canvas)); return TRUE; // I consumed the event @@ -7028,10 +6976,10 @@ void sp_text_toolbox_family_popnotify(GtkComboBox *widget, } // update - sp_text_toolbox_family_changed (NULL, tbl); + sp_text_toolbox_family_changed (NULL, tbl); break; } - } + } } } @@ -7069,7 +7017,7 @@ GtkWidget *sp_text_toolbox_new (SPDesktop *desktop) g_signal_connect (G_OBJECT (font_sel->gobj()), "key-press-event", G_CALLBACK(sp_text_toolbox_family_list_keypress), tbl); cbe_add_completion(font_sel->gobj(), G_OBJECT(tbl)); - + gtk_toolbar_append_widget( tbl, (GtkWidget*) font_sel->gobj(), "", ""); g_object_set_data (G_OBJECT (tbl), "family-entry-combo", font_sel); @@ -7082,7 +7030,7 @@ GtkWidget *sp_text_toolbox_new (SPDesktop *desktop) g_signal_connect (G_OBJECT (entry), "activate", G_CALLBACK (sp_text_toolbox_family_changed), tbl); g_signal_connect (G_OBJECT (font_sel->gobj()), "changed", G_CALLBACK (sp_text_toolbox_family_changed), tbl); - g_signal_connect (G_OBJECT (font_sel->gobj()), "notify::popup-shown", + g_signal_connect (G_OBJECT (font_sel->gobj()), "notify::popup-shown", G_CALLBACK (sp_text_toolbox_family_popnotify), tbl); g_signal_connect (G_OBJECT (entry), "key-press-event", G_CALLBACK(sp_text_toolbox_family_keypress), tbl); g_signal_connect (G_OBJECT (entry), "focus-in-event", G_CALLBACK (sp_text_toolbox_entry_focus_in), tbl); @@ -7405,7 +7353,7 @@ static void connector_spacing_changed(GtkAdjustment *adj, GObject* tbl) if ( !repr->attribute("inkscape:connector-spacing") && ( adj->value == defaultConnSpacing )) { - // Don't need to update the repr if the attribute doesn't + // Don't need to update the repr if the attribute doesn't // exist and it is being set to the default value -- as will // happen at startup. return; |
