Code

First GSoC node tool commit to Bazaar
authorKrzysztof Kosiński <tweenk.pl@gmail.com>
Sun, 29 Nov 2009 15:33:18 +0000 (16:33 +0100)
committerKrzysztof Kosiński <tweenk.pl@gmail.com>
Sun, 29 Nov 2009 15:33:18 +0000 (16:33 +0100)
69 files changed:
configure.ac
po/POTFILES.in
po/POTFILES.skip
src/2geom/chebyshev.cpp
src/2geom/svg-path-parser.cpp
src/Makefile.am
src/Makefile_insert
src/desktop.cpp
src/desktop.h
src/display/sp-canvas-util.cpp
src/display/sp-canvas.cpp
src/display/sp-canvas.h
src/dom/io/httpclient.cpp
src/dom/odf/SvgOdg.cpp
src/event-context.cpp
src/extension/dxf2svg/entities2elements.cpp
src/extension/dxf2svg/read_dxf.cpp
src/extension/dxf2svg/tables2svg_info.cpp
src/extension/internal/cairo-renderer.cpp
src/gc-allocator.h
src/libnrtype/FontFactory.cpp
src/libnrtype/FontFactory.h
src/libnrtype/FontInstance.cpp
src/libnrtype/font-instance.h
src/libvpsc/pairingheap/.dirstamp [deleted file]
src/live_effects/parameter/path.cpp
src/preferences-skeleton.h
src/preferences.cpp
src/preferences.h
src/selection-chemistry.cpp
src/shape-editor.cpp
src/shape-editor.h
src/sp-lpe-item.cpp
src/tools-switch.cpp
src/ui/dialog/aboutbox.cpp
src/ui/dialog/align-and-distribute.cpp
src/ui/dialog/inkscape-preferences.cpp
src/ui/dialog/inkscape-preferences.h
src/ui/tool/Makefile_insert [new file with mode: 0644]
src/ui/tool/commit-events.h [new file with mode: 0644]
src/ui/tool/control-point-selection.cpp [new file with mode: 0644]
src/ui/tool/control-point-selection.h [new file with mode: 0644]
src/ui/tool/control-point.cpp [new file with mode: 0644]
src/ui/tool/control-point.h [new file with mode: 0644]
src/ui/tool/curve-drag-point.cpp [new file with mode: 0644]
src/ui/tool/curve-drag-point.h [new file with mode: 0644]
src/ui/tool/event-utils.cpp [new file with mode: 0644]
src/ui/tool/event-utils.h [new file with mode: 0644]
src/ui/tool/manipulator.cpp [new file with mode: 0644]
src/ui/tool/manipulator.h [new file with mode: 0644]
src/ui/tool/multi-path-manipulator.cpp [new file with mode: 0644]
src/ui/tool/multi-path-manipulator.h [new file with mode: 0644]
src/ui/tool/node-tool.cpp [new file with mode: 0644]
src/ui/tool/node-tool.h [new file with mode: 0644]
src/ui/tool/node-types.h [new file with mode: 0644]
src/ui/tool/node.cpp [new file with mode: 0644]
src/ui/tool/node.h [new file with mode: 0644]
src/ui/tool/path-manipulator.cpp [new file with mode: 0644]
src/ui/tool/path-manipulator.h [new file with mode: 0644]
src/ui/tool/selectable-control-point.cpp [new file with mode: 0644]
src/ui/tool/selectable-control-point.h [new file with mode: 0644]
src/ui/tool/selector.cpp [new file with mode: 0644]
src/ui/tool/selector.h [new file with mode: 0644]
src/ui/tool/transform-handle-set.cpp [new file with mode: 0644]
src/ui/tool/transform-handle-set.h [new file with mode: 0644]
src/util/accumulators.h [new file with mode: 0644]
src/util/hash.h [new file with mode: 0644]
src/verbs.cpp
src/widgets/toolbox.cpp

index fbfa2e5df6e0a3aa898443042112daeea23f1805..5bf86e096b9e80b49148758361d563f8d1f98901 100644 (file)
@@ -600,8 +600,11 @@ dnl        with_inkboard="no"
 dnl fi
 
 dnl AM_CONDITIONAL(WITH_INKBOARD, test "x$with_inkboard" = "xyes")
-dnl AC_SUBST(INKBOARD_LIBS)
-dnl AC_SUBST(INKBOARD_CFLAGS)
+
+INKBOARD_CFLAGS=""
+INKBOARD_LIBS=""
+AC_SUBST(INKBOARD_LIBS)
+AC_SUBST(INKBOARD_CFLAGS)
 
 dnl ******************************
 dnl Check for libwpg for extension
index 395a4a988051b47d66cb78bfa30e8825fbd8ac0d..df09acc2294a0e6c449abafe0ce90defa17677f2 100644 (file)
+
 # List of source files containing translatable strings.
 # Please keep this file sorted alphabetically.
-# Generated by ./generate_POTFILES.sh at Wed Jun  3 14:29:48 CDT 2009
+# Generated by Waf at Sun Oct  4 01:04:56 2009
+
 [encoding: UTF-8]
 
+[type: gettext/xml] share/extensions/addnodes.inx
+[type: gettext/xml] share/extensions/ai_input.inx
+[type: gettext/xml] share/extensions/aisvg.inx
+[type: gettext/xml] share/extensions/ccx_input.inx
+[type: gettext/xml] share/extensions/cdr_input.inx
+[type: gettext/xml] share/extensions/cdt_input.inx
+[type: gettext/xml] share/extensions/cgm_input.inx
+[type: gettext/xml] share/extensions/cmx_input.inx
+[type: gettext/xml] share/extensions/color_brighter.inx
+[type: gettext/xml] share/extensions/color_custom.inx
+[type: gettext/xml] share/extensions/color_darker.inx
+[type: gettext/xml] share/extensions/color_desaturate.inx
+[type: gettext/xml] share/extensions/color_grayscale.inx
+[type: gettext/xml] share/extensions/color_lesshue.inx
+[type: gettext/xml] share/extensions/color_lesslight.inx
+[type: gettext/xml] share/extensions/color_lesssaturation.inx
+[type: gettext/xml] share/extensions/color_morehue.inx
+[type: gettext/xml] share/extensions/color_morelight.inx
+[type: gettext/xml] share/extensions/color_moresaturation.inx
+[type: gettext/xml] share/extensions/color_negative.inx
+[type: gettext/xml] share/extensions/color_randomize.inx
+[type: gettext/xml] share/extensions/color_removeblue.inx
+[type: gettext/xml] share/extensions/color_removegreen.inx
+[type: gettext/xml] share/extensions/color_removered.inx
+[type: gettext/xml] share/extensions/color_replace.inx
+[type: gettext/xml] share/extensions/color_rgbbarrel.inx
+[type: gettext/xml] share/extensions/convert2dashes.inx
+[type: gettext/xml] share/extensions/dia.inx
+[type: gettext/xml] share/extensions/dimension.inx
+[type: gettext/xml] share/extensions/dots.inx
+[type: gettext/xml] share/extensions/draw_from_triangle.inx
+[type: gettext/xml] share/extensions/dxf_input.inx
+[type: gettext/xml] share/extensions/dxf_outlines.inx
+[type: gettext/xml] share/extensions/dxf_output.inx
+[type: gettext/xml] share/extensions/edge3d.inx
+[type: gettext/xml] share/extensions/embedimage.inx
+[type: gettext/xml] share/extensions/eps_input.inx
+[type: gettext/xml] share/extensions/eqtexsvg.inx
+[type: gettext/xml] share/extensions/export_gimp_palette.inx
+[type: gettext/xml] share/extensions/extractimage.inx
+[type: gettext/xml] share/extensions/extrude.inx
+[type: gettext/xml] share/extensions/fig_input.inx
+[type: gettext/xml] share/extensions/flatten.inx
+[type: gettext/xml] share/extensions/foldablebox.inx
+[type: gettext/xml] share/extensions/fractalize.inx
+[type: gettext/xml] share/extensions/funcplot.inx
+[type: gettext/xml] share/extensions/gears.inx
+[type: gettext/xml] share/extensions/gimp_xcf.inx
+[type: gettext/xml] share/extensions/grid_cartesian.inx
+[type: gettext/xml] share/extensions/grid_polar.inx
+[type: gettext/xml] share/extensions/guides_creator.inx
+[type: gettext/xml] share/extensions/handles.inx
+[type: gettext/xml] share/extensions/hpgl_output.inx
+[type: gettext/xml] share/extensions/inkscape_help_askaquestion.inx
+[type: gettext/xml] share/extensions/inkscape_help_commandline.inx
+[type: gettext/xml] share/extensions/inkscape_help_faq.inx
+[type: gettext/xml] share/extensions/inkscape_help_keys.inx
+[type: gettext/xml] share/extensions/inkscape_help_manual.inx
+[type: gettext/xml] share/extensions/inkscape_help_relnotes.inx
+[type: gettext/xml] share/extensions/inkscape_help_reportabug.inx
+[type: gettext/xml] share/extensions/inkscape_help_svgspec.inx
+[type: gettext/xml] share/extensions/interp.inx
+[type: gettext/xml] share/extensions/interp_att_g.inx
+[type: gettext/xml] share/extensions/lindenmayer.inx
+[type: gettext/xml] share/extensions/lorem_ipsum.inx
+[type: gettext/xml] share/extensions/markers_strokepaint.inx
+[type: gettext/xml] share/extensions/measure.inx
+[type: gettext/xml] share/extensions/motion.inx
+[type: gettext/xml] share/extensions/outline2svg.inx
+[type: gettext/xml] share/extensions/param_curves.inx
+[type: gettext/xml] share/extensions/pathalongpath.inx
+[type: gettext/xml] share/extensions/pathscatter.inx
+[type: gettext/xml] share/extensions/perfectboundcover.inx
+[type: gettext/xml] share/extensions/perspective.inx
+[type: gettext/xml] share/extensions/plt_input.inx
+[type: gettext/xml] share/extensions/plt_output.inx
+[type: gettext/xml] share/extensions/polyhedron_3d.inx
+[type: gettext/xml] share/extensions/printing-marks.inx
+[type: gettext/xml] share/extensions/ps_input.inx
+[type: gettext/xml] share/extensions/radiusrand.inx
+[type: gettext/xml] share/extensions/render_alphabetsoup.inx
+[type: gettext/xml] share/extensions/render_barcode.inx
+[type: gettext/xml] share/extensions/restack.inx
+[type: gettext/xml] share/extensions/rtree.inx
+[type: gettext/xml] share/extensions/rubberstretch.inx
+[type: gettext/xml] share/extensions/scour.inx
+[type: gettext/xml] share/extensions/sk1_input.inx
+[type: gettext/xml] share/extensions/sk1_output.inx
+[type: gettext/xml] share/extensions/sk_input.inx
+[type: gettext/xml] share/extensions/spirograph.inx
+[type: gettext/xml] share/extensions/straightseg.inx
+[type: gettext/xml] share/extensions/summersnight.inx
+[type: gettext/xml] share/extensions/svg2xaml.inx
+[type: gettext/xml] share/extensions/svg_and_media_zip_output.inx
+[type: gettext/xml] share/extensions/svgcalendar.inx
+[type: gettext/xml] share/extensions/text_braille.inx
+[type: gettext/xml] share/extensions/text_flipcase.inx
+[type: gettext/xml] share/extensions/text_lowercase.inx
+[type: gettext/xml] share/extensions/text_randomcase.inx
+[type: gettext/xml] share/extensions/text_replace.inx
+[type: gettext/xml] share/extensions/text_sentencecase.inx
+[type: gettext/xml] share/extensions/text_titlecase.inx
+[type: gettext/xml] share/extensions/text_uppercase.inx
+[type: gettext/xml] share/extensions/triangle.inx
+[type: gettext/xml] share/extensions/txt2svg.inx
+[type: gettext/xml] share/extensions/web-set-att.inx
+[type: gettext/xml] share/extensions/web-transmit-att.inx
+[type: gettext/xml] share/extensions/whirl.inx
+[type: gettext/xml] share/extensions/wmf_input.inx
+[type: gettext/xml] share/extensions/wmf_output.inx
+[type: gettext/xml] share/extensions/xaml2svg.inx
+[type: gettext/xml] src/extension/dxf2svg/dxf_input.inx
+[type: gettext/xml] src/extension/dxf2svg/dxf_input_windows.inx
+cxxtest/cxxtestgen.py
 inkscape.desktop.in
+share/extensions/dimension.py
+share/extensions/draw_from_triangle.py
+share/extensions/dxf_outlines.py
+share/extensions/embedimage.py
+share/extensions/export_gimp_palette.py
+share/extensions/extractimage.py
+share/extensions/guides_creator.py
+share/extensions/inkex.py
+share/extensions/markers_strokepaint.py
+share/extensions/pathalongpath.py
+share/extensions/pathmodifier.py
+share/extensions/pathscatter.py
+share/extensions/perspective.py
+share/extensions/polyhedron_3d.py
+share/extensions/summersnight.py
+share/extensions/svg_and_media_zip_output.py
+share/extensions/uniconv_output.py
+share/extensions/web-set-att.py
+share/extensions/web-transmit-att.py
 share/filters/filters.svg.h
+share/filters/i18n.py
+share/patterns/i18n.py
 share/patterns/patterns.svg.h
 src/arc-context.cpp
 src/box3d-context.cpp
 src/box3d.cpp
 src/connector-context.cpp
 src/context-fns.cpp
-src/desktop.cpp
 src/desktop-events.cpp
+src/desktop.cpp
 src/dialogs/clonetiler.cpp
 src/dialogs/export.cpp
 src/dialogs/find.cpp
@@ -54,8 +191,8 @@ src/extension/internal/bitmap/enhance.cpp
 src/extension/internal/bitmap/equalize.cpp
 src/extension/internal/bitmap/gaussianBlur.cpp
 src/extension/internal/bitmap/implode.cpp
-src/extension/internal/bitmap/levelChannel.cpp
 src/extension/internal/bitmap/level.cpp
+src/extension/internal/bitmap/levelChannel.cpp
 src/extension/internal/bitmap/medianFilter.cpp
 src/extension/internal/bitmap/modulate.cpp
 src/extension/internal/bitmap/negate.cpp
@@ -79,15 +216,15 @@ src/extension/internal/cairo-renderer-pdf-out.cpp
 src/extension/internal/clear-n_.h
 src/extension/internal/emf-win32-inout.cpp
 src/extension/internal/filter/drop-shadow.h
-src/extension/internal/filter/filter.cpp
 src/extension/internal/filter/filter-file.cpp
+src/extension/internal/filter/filter.cpp
 src/extension/internal/filter/snow.h
 src/extension/internal/gdkpixbuf-input.cpp
 src/extension/internal/gimpgrad.cpp
 src/extension/internal/grid.cpp
 src/extension/internal/javafx-out.cpp
-src/extension/internal/latex-pstricks.cpp
 src/extension/internal/latex-pstricks-out.cpp
+src/extension/internal/latex-pstricks.cpp
 src/extension/internal/odf.cpp
 src/extension/internal/pdfinput/pdf-input.cpp
 src/extension/internal/pov-out.cpp
@@ -110,39 +247,62 @@ src/filter-enums.cpp
 src/flood-context.cpp
 src/gradient-context.cpp
 src/gradient-drag.cpp
-src/helper/units.cpp
 src/helper/units-test.h
+src/helper/units.cpp
 src/inkscape.cpp
 src/interface.cpp
 src/io/sys.cpp
+src/jabber_whiteboard/invitation-confirm-dialog.cpp
+src/jabber_whiteboard/message-queue.cpp
+src/jabber_whiteboard/message-utilities.cpp
+src/jabber_whiteboard/pedrogui.cpp
+src/jabber_whiteboard/session-file-selector.cpp
+src/jabber_whiteboard/session-manager.cpp
 src/knot.cpp
 src/knotholder.cpp
 src/libgdl/gdl-dock-bar.c
-src/libgdl/gdl-dock.c
-src/libgdl/gdl-dock-item.c
 src/libgdl/gdl-dock-item-grip.c
+src/libgdl/gdl-dock-item.c
 src/libgdl/gdl-dock-master.c
 src/libgdl/gdl-dock-notebook.c
 src/libgdl/gdl-dock-object.c
 src/libgdl/gdl-dock-paned.c
 src/libgdl/gdl-dock-placeholder.c
 src/libgdl/gdl-dock-tablabel.c
-src/libgdl/gdl-i18n.c
+src/libgdl/gdl-dock.c
 src/libgdl/gdl-i18n.h
 src/libgdl/gdl-switcher.c
 src/libnrtype/FontFactory.cpp
 src/live_effects/effect.cpp
+src/live_effects/lpe-angle_bisector.cpp
 src/live_effects/lpe-bendpath.cpp
+src/live_effects/lpe-boolops.cpp
+src/live_effects/lpe-circle_with_radius.cpp
 src/live_effects/lpe-constructgrid.cpp
+src/live_effects/lpe-copy_rotate.cpp
 src/live_effects/lpe-curvestitch.cpp
+src/live_effects/lpe-dynastroke.cpp
 src/live_effects/lpe-envelope.cpp
 src/live_effects/lpe-gears.cpp
 src/live_effects/lpe-interpolate.cpp
 src/live_effects/lpe-knot.cpp
+src/live_effects/lpe-lattice.cpp
+src/live_effects/lpe-line_segment.cpp
+src/live_effects/lpe-mirror_symmetry.cpp
+src/live_effects/lpe-offset.cpp
+src/live_effects/lpe-parallel.cpp
+src/live_effects/lpe-path_length.cpp
 src/live_effects/lpe-patternalongpath.cpp
+src/live_effects/lpe-perp_bisector.cpp
+src/live_effects/lpe-perspective_path.cpp
+src/live_effects/lpe-recursiveskeleton.cpp
 src/live_effects/lpe-rough-hatches.cpp
 src/live_effects/lpe-ruler.cpp
+src/live_effects/lpe-skeleton.cpp
 src/live_effects/lpe-sketch.cpp
+src/live_effects/lpe-tangent_to_curve.cpp
+src/live_effects/lpe-test-doEffect-stack.cpp
+src/live_effects/lpe-text_label.cpp
 src/live_effects/lpe-vonkoch.cpp
 src/live_effects/parameter/bool.cpp
 src/live_effects/parameter/enum.h
@@ -153,6 +313,7 @@ src/live_effects/parameter/random.cpp
 src/live_effects/parameter/text.cpp
 src/live_effects/parameter/unit.cpp
 src/live_effects/parameter/vector.cpp
+src/lpe-tool-context.cpp
 src/main-cmdlineact.cpp
 src/main.cpp
 src/menus-skeleton.h
@@ -160,11 +321,11 @@ src/node-context.cpp
 src/nodepath.cpp
 src/object-edit.cpp
 src/path-chemistry.cpp
-src/pencil-context.cpp
 src/pen-context.cpp
+src/pencil-context.cpp
 src/persp3d.cpp
-src/preferences.cpp
 src/preferences-skeleton.h
+src/preferences.cpp
 src/rdf.cpp
 src/rect-context.cpp
 src/select-context.cpp
@@ -175,28 +336,24 @@ src/shape-editor.cpp
 src/sp-anchor.cpp
 src/sp-ellipse.cpp
 src/sp-flowregion.cpp
-src/sp-flowtext.cpp
 src/sp-guide.cpp
 src/sp-image.cpp
-src/spiral-context.cpp
-src/sp-item.cpp
 src/sp-item-group.cpp
+src/sp-item.cpp
 src/sp-line.cpp
-src/splivarot.cpp
 src/sp-lpe-item.cpp
 src/sp-namedview.cpp
 src/sp-offset.cpp
-src/sp-path.cpp
 src/sp-polygon.cpp
 src/sp-polyline.cpp
 src/sp-rect.cpp
 src/sp-spiral.cpp
-src/sp-star.cpp
-src/sp-switch.cpp
 src/sp-text.cpp
 src/sp-tref.cpp
 src/sp-tspan.cpp
 src/sp-use.cpp
+src/spiral-context.cpp
+src/splivarot.cpp
 src/star-context.cpp
 src/text-chemistry.cpp
 src/text-context.cpp
@@ -230,11 +387,21 @@ src/ui/dialog/messages.cpp
 src/ui/dialog/ocaldialogs.cpp
 src/ui/dialog/print.cpp
 src/ui/dialog/scriptdialog.cpp
+src/ui/dialog/session-player.cpp
 src/ui/dialog/svg-fonts-dialog.cpp
 src/ui/dialog/swatches.cpp
 src/ui/dialog/tile.cpp
 src/ui/dialog/tracedialog.cpp
 src/ui/dialog/transformation.cpp
+src/ui/dialog/whiteboard-connect.cpp
+src/ui/dialog/whiteboard-sharewithchat.cpp
+src/ui/dialog/whiteboard-sharewithuser.cpp
+src/ui/tool/curve-drag-point.cpp
+src/ui/tool/multi-path-manipulator.cpp
+src/ui/tool/node-tool.cpp
+src/ui/tool/node.cpp
+src/ui/tool/path-manipulator.cpp
+src/ui/tool/transform-handle-set.cpp
 src/ui/view/edit-widget.cpp
 src/ui/widget/combo-enums.h
 src/ui/widget/entity-entry.cpp
@@ -275,133 +442,3 @@ src/widgets/sp-xmlview-attr-list.cpp
 src/widgets/sp-xmlview-content.cpp
 src/widgets/stroke-style.cpp
 src/widgets/toolbox.cpp
-share/extensions/dimension.py
-share/extensions/draw_from_triangle.py
-share/extensions/dxf_outlines.py
-share/extensions/embedimage.py
-share/extensions/export_gimp_palette.py
-share/extensions/extractimage.py
-share/extensions/guides_creator.py
-share/extensions/inkex.py
-share/extensions/markers_strokepaint.py
-share/extensions/pathalongpath.py
-share/extensions/pathmodifier.py
-share/extensions/pathscatter.py
-share/extensions/perspective.py
-share/extensions/polyhedron_3d.py
-share/extensions/summersnight.py
-share/extensions/svg_and_media_zip_output.py
-share/extensions/uniconv_output.py
-share/extensions/web-set-att.py
-share/extensions/web-transmit-att.py
-[type: gettext/xml] share/extensions/addnodes.inx
-[type: gettext/xml] share/extensions/ai_input.inx
-[type: gettext/xml] share/extensions/aisvg.inx
-[type: gettext/xml] share/extensions/ccx_input.inx
-[type: gettext/xml] share/extensions/cdr_input.inx
-[type: gettext/xml] share/extensions/cdt_input.inx
-[type: gettext/xml] share/extensions/cgm_input.inx
-[type: gettext/xml] share/extensions/cmx_input.inx
-[type: gettext/xml] share/extensions/color_brighter.inx
-[type: gettext/xml] share/extensions/color_custom.inx
-[type: gettext/xml] share/extensions/color_darker.inx
-[type: gettext/xml] share/extensions/color_desaturate.inx
-[type: gettext/xml] share/extensions/color_grayscale.inx
-[type: gettext/xml] share/extensions/color_lesshue.inx
-[type: gettext/xml] share/extensions/color_lesslight.inx
-[type: gettext/xml] share/extensions/color_lesssaturation.inx
-[type: gettext/xml] share/extensions/color_morehue.inx
-[type: gettext/xml] share/extensions/color_morelight.inx
-[type: gettext/xml] share/extensions/color_moresaturation.inx
-[type: gettext/xml] share/extensions/color_negative.inx
-[type: gettext/xml] share/extensions/color_randomize.inx
-[type: gettext/xml] share/extensions/color_removeblue.inx
-[type: gettext/xml] share/extensions/color_removegreen.inx
-[type: gettext/xml] share/extensions/color_removered.inx
-[type: gettext/xml] share/extensions/color_replace.inx
-[type: gettext/xml] share/extensions/color_rgbbarrel.inx
-[type: gettext/xml] share/extensions/convert2dashes.inx
-[type: gettext/xml] share/extensions/dia.inx
-[type: gettext/xml] share/extensions/dimension.inx
-[type: gettext/xml] share/extensions/dots.inx
-[type: gettext/xml] share/extensions/draw_from_triangle.inx
-[type: gettext/xml] share/extensions/dxf_input.inx
-[type: gettext/xml] share/extensions/dxf_outlines.inx
-[type: gettext/xml] share/extensions/dxf_output.inx
-[type: gettext/xml] share/extensions/edge3d.inx
-[type: gettext/xml] share/extensions/embedimage.inx
-[type: gettext/xml] share/extensions/eps_input.inx
-[type: gettext/xml] share/extensions/eqtexsvg.inx
-[type: gettext/xml] share/extensions/export_gimp_palette.inx
-[type: gettext/xml] share/extensions/extractimage.inx
-[type: gettext/xml] share/extensions/extrude.inx
-[type: gettext/xml] share/extensions/fig_input.inx
-[type: gettext/xml] share/extensions/flatten.inx
-[type: gettext/xml] share/extensions/foldablebox.inx
-[type: gettext/xml] share/extensions/fractalize.inx
-[type: gettext/xml] share/extensions/funcplot.inx
-[type: gettext/xml] share/extensions/gears.inx
-[type: gettext/xml] share/extensions/gimp_xcf.inx
-[type: gettext/xml] share/extensions/grid_cartesian.inx
-[type: gettext/xml] share/extensions/grid_polar.inx
-[type: gettext/xml] share/extensions/guides_creator.inx
-[type: gettext/xml] share/extensions/handles.inx
-[type: gettext/xml] share/extensions/hpgl_output.inx
-[type: gettext/xml] share/extensions/inkscape_help_askaquestion.inx
-[type: gettext/xml] share/extensions/inkscape_help_commandline.inx
-[type: gettext/xml] share/extensions/inkscape_help_faq.inx
-[type: gettext/xml] share/extensions/inkscape_help_keys.inx
-[type: gettext/xml] share/extensions/inkscape_help_manual.inx
-[type: gettext/xml] share/extensions/inkscape_help_relnotes.inx
-[type: gettext/xml] share/extensions/inkscape_help_reportabug.inx
-[type: gettext/xml] share/extensions/inkscape_help_svgspec.inx
-[type: gettext/xml] share/extensions/interp_att_g.inx
-[type: gettext/xml] share/extensions/interp.inx
-[type: gettext/xml] share/extensions/lindenmayer.inx
-[type: gettext/xml] share/extensions/lorem_ipsum.inx
-[type: gettext/xml] share/extensions/markers_strokepaint.inx
-[type: gettext/xml] share/extensions/measure.inx
-[type: gettext/xml] share/extensions/motion.inx
-[type: gettext/xml] share/extensions/outline2svg.inx
-[type: gettext/xml] share/extensions/param_curves.inx
-[type: gettext/xml] share/extensions/pathalongpath.inx
-[type: gettext/xml] share/extensions/pathscatter.inx
-[type: gettext/xml] share/extensions/perfectboundcover.inx
-[type: gettext/xml] share/extensions/perspective.inx
-[type: gettext/xml] share/extensions/plt_input.inx
-[type: gettext/xml] share/extensions/plt_output.inx
-[type: gettext/xml] share/extensions/polyhedron_3d.inx
-[type: gettext/xml] share/extensions/printing-marks.inx
-[type: gettext/xml] share/extensions/ps_input.inx
-[type: gettext/xml] share/extensions/radiusrand.inx
-[type: gettext/xml] share/extensions/render_alphabetsoup.inx
-[type: gettext/xml] share/extensions/render_barcode.inx
-[type: gettext/xml] share/extensions/restack.inx
-[type: gettext/xml] share/extensions/rtree.inx
-[type: gettext/xml] share/extensions/rubberstretch.inx
-[type: gettext/xml] share/extensions/scour.inx
-[type: gettext/xml] share/extensions/sk1_input.inx
-[type: gettext/xml] share/extensions/sk1_output.inx
-[type: gettext/xml] share/extensions/sk_input.inx
-[type: gettext/xml] share/extensions/spirograph.inx
-[type: gettext/xml] share/extensions/straightseg.inx
-[type: gettext/xml] share/extensions/summersnight.inx
-[type: gettext/xml] share/extensions/svg2xaml.inx
-[type: gettext/xml] share/extensions/svg_and_media_zip_output.inx
-[type: gettext/xml] share/extensions/svgcalendar.inx
-[type: gettext/xml] share/extensions/text_braille.inx
-[type: gettext/xml] share/extensions/text_flipcase.inx
-[type: gettext/xml] share/extensions/text_lowercase.inx
-[type: gettext/xml] share/extensions/text_randomcase.inx
-[type: gettext/xml] share/extensions/text_replace.inx
-[type: gettext/xml] share/extensions/text_sentencecase.inx
-[type: gettext/xml] share/extensions/text_titlecase.inx
-[type: gettext/xml] share/extensions/text_uppercase.inx
-[type: gettext/xml] share/extensions/triangle.inx
-[type: gettext/xml] share/extensions/txt2svg.inx
-[type: gettext/xml] share/extensions/web-set-att.inx
-[type: gettext/xml] share/extensions/web-transmit-att.inx
-[type: gettext/xml] share/extensions/whirl.inx
-[type: gettext/xml] share/extensions/wmf_input.inx
-[type: gettext/xml] share/extensions/wmf_output.inx
-[type: gettext/xml] share/extensions/xaml2svg.inx
index 1aaf436dc85177ad7d97a821518df03d16accd31..f287fd6a1536b0f9c77b2322d7b80f23047912f1 100644 (file)
@@ -1,5 +1,6 @@
 # All source files whose translatable messages should not be seen as of now
 
+cxxtest/cxxtest.py
 src/jabber_whiteboard/invitation-confirm-dialog.cpp
 src/jabber_whiteboard/message-queue.cpp
 src/jabber_whiteboard/message-utilities.cpp
index 447c5183f920aeab0913118741f7b20b5dc84d77..73baf7b6b4420c788ef42b8307d02dd46003c2fb 100644 (file)
@@ -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);
index 071b171b39cb1a2d5273b3cb1be0cc338fea95a0..691ddf022a80ea23db576cb86dcd5c1a1fc64f04 100644 (file)
@@ -1,4 +1,3 @@
-#line 1 "/home/njh/svn/lib2geom/src/2geom/svg-path-parser.rl"
 /**
  * \file
  * \brief parse SVG path specifications
index 63b27398afd27db6e97dd9ce2081d98ed472d941..ecc0f522b0afe8e58a29f2c81fe9508c6c91a785 100644 (file)
@@ -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
index de986ca16e3a19fec2f3f1943ede0926a82c766e..dfc9daf96695a3687a4400043e0c8fbd88106248 100644 (file)
@@ -56,7 +56,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                                                       \
index 319a0d407e35d252e31d96fc97f3c05e8f45ac62..b1b9a29558733ebb207aab2f691af3950814d2f5 100644 (file)
 #include "widgets/desktop-widget.h"
 #include "box3d-context.h"
 
+// TODO those includes are only for the braindead quick zoom implementation.
+// Remove them after fixing this.
+#include "ui/tool/node-tool.h"
+#include "ui/tool/control-point-selection.h"
+
 #include "display/sp-canvas.h"
 
 namespace Inkscape { namespace XML { class Node; }}
@@ -619,9 +624,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);
@@ -780,11 +788,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) */
@@ -870,11 +879,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
 
@@ -890,67 +894,27 @@ 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 is brain damage. This needs to migrate into the node tool,
+        // but currently the design of this method is sufficiently broken
+        // 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;
             }
         }
@@ -960,7 +924,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;
@@ -1801,7 +1765,7 @@ Geom::Point SPDesktop::dt2doc(Geom::Point const &p) const
 }
 
 
-/**
+/*
  * Pop event context from desktop's context stack. Never used.
  */
 // void
@@ -1843,4 +1807,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 :
index cfb977425318cdb4ccb371a265dd09055572eae6..2d3c9b6e415c558276a39f17ad5c25ee9d3b7b39 100644 (file)
@@ -143,6 +143,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);
index 30cd0dfa0b31f5e99debf16a6a8704cb0dbb4934..a23b157df6a7150aff20bae8cfe9c670c587ba9c 100644 (file)
@@ -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
index aee53838f45275e72daf006ea3f036d6fabe7a3c..6d996fbe22a93fd9a495ba1b50b25b1c607e6581 100644 (file)
@@ -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;
index 35e3fb5de863d56f4de46a597f28f061ea92f679..a2af080ef8d92e5efeef34bc8f7bf0a61bec3383 100644 (file)
@@ -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);
index 4245d71f2cefc170275f99d29fc911db212e8be5..97c8575cfaf5b18a03b27343491ad1fcee06c974 100644 (file)
@@ -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;
         }
index 2b1161310727b3c861e5b6665c28eecf4a0eb9a7..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 100644 (file)
-/**
- *
- * 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
-//########################################################################
-
index 9b846f2b7e51a5f54e88c6e15363e33be52dcae8..ec0169573db6a8aa81d13ae9168ae7753d9eade4 100644 (file)
@@ -56,7 +56,6 @@
 #include "attributes.h"
 #include "rubberband.h"
 #include "selcue.h"
-#include "node-context.h"
 #include "lpe-tool-context.h"
 
 static void sp_event_context_class_init(SPEventContextClass *klass);
index cf42dac32f406483177b29fb8f81dedeb6bf912f..15d3e76aef772b6f81ccf6c480d70556bd1d5633 100644 (file)
@@ -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
index 8a6a6d6acebdf18ef5593a5fbce5cba0886e5052..ecda343c64d87c942bb2d1743467c004f3f6606c 100644 (file)
 
 
 
-#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;
 
 
index 17bc47beb003a4fc6cd20f98e57adf940626f788..3b27a9c382b65b6893dad80e16ee17b5e28fb36b 100644 (file)
  */
 
 
-#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();
index 8cc38613538c1371b212c423750722649c230340..0e68ae130a368c3013d97c0f56a2bf666f4d55de 100644 (file)
@@ -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>
 
index 4d809cfe4e36bd69db4250d4f9e703720826d152..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 100644 (file)
@@ -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 :
index fec9316b98a7aa3e3493e0fd9ca77133aa5c02cd..db7cd9747846e3421de10d8a3e89dc391abda12d 100644 (file)
@@ -43,7 +43,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);
index 9f4b31a2e5c776f967d85ef1e7ccec5412a24f4a..5253f6bbd2a186c70b87c17a6ef7ad5e18ec27a3 100644 (file)
@@ -11,7 +11,7 @@
 
 #include <functional>
 #include <algorithm>
-#include <ext/hash_map>
+#include <tr1/unordered_map>
 
 #ifdef HAVE_CONFIG_H
 # include <config.h>
@@ -46,7 +46,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
@@ -84,7 +84,7 @@ public:
     double fontSize; /**< The huge fontsize used as workaround for hinting.
                       *   Different between freetype and win32. */
 
-    __gnu_cxx::hash_map<PangoFontDescription*, font_instance*, font_descr_hash, font_descr_equal> loadedFaces;
+    std::tr1::unordered_map<PangoFontDescription*, font_instance*, font_descr_hash, font_descr_equal> loadedFaces;
 
     font_factory();
     virtual ~font_factory();
index e1413b46e788a87f9f90832c551aeec169d4e614..d9a0c43e689413d02bec8e8666b4651ad987659c 100644 (file)
@@ -60,7 +60,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;
     }
index 4209a20af68c723c41daabd6867857cb0b62ee8b..ce3f6cf1b79a2a1e6d044affc5532108a9231493 100644 (file)
@@ -1,7 +1,7 @@
 #ifndef SEEN_LIBNRTYPE_FONT_INSTANCE_H
 #define SEEN_LIBNRTYPE_FONT_INSTANCE_H
 
-#include <ext/hash_map>
+#include <tr1/unordered_map>
 #include <map>
 #include <pango/pango-types.h>
 #include <pango/pango-font.h>
@@ -26,13 +26,13 @@ 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;
 };
 
 class font_instance {
 public:
        // hashmap to get the raster_font for a given style
-    __gnu_cxx::hash_map<font_style, raster_font*, font_style_hash, font_style_equal>     loadedStyles;
+    std::tr1::unordered_map<font_style, raster_font*, font_style_hash, font_style_equal>     loadedStyles;
        // the real source of the font
     PangoFont*            pFont;
                // depending on the rendering backend, different temporary data
diff --git a/src/libvpsc/pairingheap/.dirstamp b/src/libvpsc/pairingheap/.dirstamp
deleted file mode 100644 (file)
index e69de29..0000000
index 33e50155cfc467b502a61ff9e1ef5bc195948c76..93dfd2667a68da7ed3311b9f8a87217317918781 100644 (file)
@@ -195,23 +195,13 @@ PathParam::param_newWidget(Gtk::Tooltips * tooltips)
 void
 PathParam::param_editOncanvas(SPItem * item, SPDesktop * dt)
 {
-    // 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;
-    if (!href) {
-        shape_editor->set_item_lpe_path_parameter(item, param_effect->getLPEObj(), param_key.c_str());
-    } else {
-        // set referred item for editing
-        shape_editor->set_item(ref.getObject(), SH_NODEPATH);
-    }
+    // TODO this whole method is broken!
 }
 
 void
 PathParam::param_setup_nodepath(Inkscape::NodePath::Path *np)
-{
+{   
+    // TODO this too!
     np->show_helperpath = true;
     np->helperpath_rgba = 0x009000ff;
     np->helperpath_width = 1.0;
index 6185ff729230a68ca568c52772d951ec2d756056..075d2b0313ec9736c797f2303e91e7a70bddfd11 100644 (file)
@@ -112,7 +112,7 @@ static char const preferences_skeleton[] =
 "    <eventcontext id=\"text\"  usecurrent=\"0\" gradientdrag=\"1\"\n"
 "                       font_sample=\"AaBbCcIiPpQq12369$\342\202\254\302\242?.;/()\"\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=\"gradient\" selcue=\"1\"/>\n"
 "    <eventcontext id=\"zoom\" selcue=\"1\" gradientdrag=\"0\"/>\n"
index 39a9e4d69aafe98f292926346439a49d51a3bcc1..315c668b4ab08d5d305bf154884e7caf92408c6d 100644 (file)
@@ -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();
index a7be080094ae91f9267f9587e0a90ece8c462e38..5e1ccf9d6f7a451f48fd3d97b328e618035cc1a3 100644 (file)
@@ -177,6 +177,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
@@ -383,6 +392,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
      */
@@ -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()) {
index e55bba2a5926079ef2e3a2fd99f300d7b8e791c2..a9736e9910823e048e5345a23055709c9003fdf7 100644 (file)
@@ -88,9 +88,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"
 
@@ -1716,53 +1714,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."));
+    }*/
 }
 
 
index 44ad9dc9e9cf32addeff77e79348da8bfe0abffd..d416e0c9263777f95194d78ddb245deef950688a 100644 (file)
 
 #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;
index 98dbb35d7f94c266d5f4ac7899e963971f40c4be..6f0907fb93bc2d36752bfad117a5ec794f7d8193 100644 (file)
@@ -147,26 +147,6 @@ private:
     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
 
 
index 6b71541e64c1013ed19d25469ea66f3fa28963a5..1bb500dd2beb7029f5c7a459543fa4c53c44cb06 100644 (file)
@@ -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();
                 }
             }
         }
index 7902a79883f9da6e6231c7adfac3056ac54d79eb..380267408d4385cdd8062dbef4fe5da53279c253 100644 (file)
@@ -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 "sp-path.h"
 #include "rect-context.h"
@@ -124,10 +124,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]);
index 025bec37a074018d8be728db097d9fe636e5df49..bbd02fa5d3c43816a34741ae055add505bb664ce 100644 (file)
@@ -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);
index a54f83758db8fd316ed4647d721341fdd9a384a2..f38d674fdd3576c6ecf90d1b91f5565ca13b6e8a 100644 (file)
 #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);
 
     }
 };
index c7dc789ca7fbbc876dd2924bc139c1127515b050..1d93eab6b0ba5cdfd9eb9d6493be827413744e63 100644 (file)
@@ -436,12 +436,19 @@ void InkscapePreferences::initPageTools()
     _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);
+    _t_node_show_path_direction.init(_("Show path direction"), "/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 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_unselected.init ( _("Show temporary outline for selected paths"), "/tools/nodes/pathflash_unselected", false);
+    _page_node.add_line( true, "", _t_node_pathflash_unselected, "", _("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);
index 364b0eb1da2195f141946b94a600e815fb757063..0fc1be21ee4baa4b9afb2b9617a5b799fac065d8 100644 (file)
@@ -143,6 +143,9 @@ protected:
     PrefCheckButton _t_node_pathflash_enabled;
     PrefCheckButton _t_node_pathflash_unselected;
     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 (file)
index 0000000..e149430
--- /dev/null
@@ -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 (file)
index 0000000..d998727
--- /dev/null
@@ -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 (file)
index 0000000..d10045c
--- /dev/null
@@ -0,0 +1,530 @@
+/** @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++);
+}
+
+/** Transform all selected control points by the supplied 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 (file)
index 0000000..0f0daff
--- /dev/null
@@ -0,0 +1,140 @@
+/** @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 <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;
+
+    // boilerplate typedefs
+    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); }
+
+    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;
+    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 (file)
index 0000000..74dd6e3
--- /dev/null
@@ -0,0 +1,619 @@
+/** @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 "ui/tool/control-point.h"
+#include "ui/tool/event-utils.h"
+#include "preferences.h"
+#include "desktop.h"
+#include "desktop-handles.h"
+#include "event-context.h"
+#include "message-context.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) {
+            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) return true;
+            // the point was moved beyond the drag tolerance
+            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);
+            return true;
+        }
+        break;
+        
+    case GDK_BUTTON_RELEASE:
+        if (_event_grab) {
+            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 (file)
index 0000000..c4b0a42
--- /dev/null
@@ -0,0 +1,167 @@
+/** @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);
+
+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 bool _eventHandler(GdkEvent *event);
+    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; ///< Describes the 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 (file)
index 0000000..889e245
--- /dev/null
@@ -0,0 +1,185 @@
+/** @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._path_data.node_data.desktop, Geom::Point(), Gtk::ANCHOR_CENTER,
+        SP_CTRL_SHAPE_CIRCLE, 1.0, &invisible_cset, pm._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 (file)
index 0000000..c9f32f7
--- /dev/null
@@ -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 (file)
index 0000000..6912897
--- /dev/null
@@ -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 (file)
index 0000000..74907d6
--- /dev/null
@@ -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 (file)
index 0000000..b532fca
--- /dev/null
@@ -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 (file)
index 0000000..c441f70
--- /dev/null
@@ -0,0 +1,184 @@
+/** @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.
+ */
+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;
+    /// Commits changes to XML (repr).
+    //virtual void commitToXML();
+    //Manipulator *_parent;
+protected:
+    SPDesktop *const _desktop;
+};
+
+/**
+ * @brief Tool component that edits something on the canvas using selectable control points.
+ */
+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. */
+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;
+};
+
+/*
+ * @brief Set of manipulators. Takes care of routing events to appropriate manipulators.
+ */
+/*class ManipulatorGroup : private std::list<boost::shared_ptr<Manipulator> > {
+friend class Manipulator;
+public:
+    ManipulatorGroup(SPDesktop *d);
+    ~ManipulatorGroup();
+    void add(boost::shared_ptr<Manipulator> m);
+    void remove(boost::shared_ptr<Manipulator> m);
+    void clear();
+    bool event(GdkEvent *);
+private:
+    void _grabEvents(boost::shared_ptr<Manipulator> m);
+    void _ungrabEvents(boost::shared_ptr<Manipulator> m);
+
+    SPDesktop *_desktop;
+    boost::shared_ptr<Manipulator> _grab;
+};*/
+
+} // 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 (file)
index 0000000..6b24570
--- /dev/null
@@ -0,0 +1,568 @@
+/** @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 "message-stack.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 two selected endnodes.
+ * @returns -1 if not enough endnodes selected, 1 if too many, 0 if OK */
+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 const &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;
+    }
+}
+
+void MultiPathManipulator::setItems(std::map<SPPath*,
+    std::pair<Geom::Matrix, guint32> > const &items)
+{
+    typedef std::map<SPPath*, std::pair<Geom::Matrix, guint32> > TransMap;
+    typedef std::set<SPPath*> ItemSet;
+    ItemSet to_remove, to_add, current, new_items;
+
+    for (MapType::iterator i = _mmap.begin(); i != _mmap.end(); ++i) {
+        current.insert(i->first);
+    }
+    for (TransMap::const_iterator i = items.begin(); i != items.end(); ++i) {
+        new_items.insert(i->first);
+    }
+
+    std::set_difference(current.begin(), current.end(), new_items.begin(), new_items.end(),
+        std::inserter(to_remove, to_remove.end()));
+    std::set_difference(new_items.begin(), new_items.end(), current.begin(), current.end(),
+        std::inserter(to_add, to_add.end()));
+
+    for (ItemSet::iterator i = to_remove.begin(); i != to_remove.end(); ++i) {
+        _mmap.erase(*i);
+    }
+    for (ItemSet::iterator i = to_add.begin(); i != to_add.end(); ++i) {
+        boost::shared_ptr<PathManipulator> pm;
+        TransMap::const_iterator f = items.find(*i);
+        pm.reset(new PathManipulator(_path_data, *i, f->second.first, f->second.second));
+        pm->showHandles(_show_handles);
+        pm->showOutline(_show_outline);
+        pm->showPathDirection(_show_path_direction);
+        _mmap.insert(std::make_pair(*i, pm));
+    }
+}
+
+void MultiPathManipulator::selectSubpaths()
+{
+    if (_selection.empty()) {
+        invokeForAll(&PathManipulator::selectAll);
+    } else {
+        invokeForAll(&PathManipulator::selectSubpaths);
+    }
+}
+void MultiPathManipulator::selectAll()
+{
+    invokeForAll(&PathManipulator::selectAll);
+}
+
+void MultiPathManipulator::selectArea(Geom::Rect const &area, bool take)
+{
+    if (take) _selection.clear();
+    invokeForAll(&PathManipulator::selectArea, area);
+}
+
+void MultiPathManipulator::shiftSelection(int dir)
+{
+    invokeForAll(&PathManipulator::shiftSelection, dir);
+}
+void MultiPathManipulator::invertSelection()
+{
+    invokeForAll(&PathManipulator::invertSelection);
+}
+void MultiPathManipulator::invertSelectionInSubpaths()
+{
+    invokeForAll(&PathManipulator::invertSelectionInSubpaths);
+}
+void MultiPathManipulator::deselect()
+{
+    _selection.clear();
+}
+
+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()
+{
+    // 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());
+    }
+    // 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::joinSegment()
+{
+    IterPairList joins;
+    find_join_iterators(_selection, joins);
+    if (joins.empty()) {
+        _desktop->messageStack()->flash(Inkscape::WARNING_MESSAGE,
+            _("There must be at least 2 endnodes in selection"));
+        return;
+    }
+
+    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();
+        }
+    }
+
+    _doneWithCleanup("Join segment");
+}
+
+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)
+{
+    invokeForAll(&PathManipulator::showOutline, show);
+    _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;
+}
+
+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)) {
+                joinSegment();
+                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;
+}
+
+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();
+}
+
+} // 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 (file)
index 0000000..89c86b0
--- /dev/null
@@ -0,0 +1,133 @@
+/** @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/manipulator.h"
+#include "ui/tool/node-types.h"
+#include "ui/tool/commit-events.h"
+
+struct SPCanvasGroup;
+
+namespace Inkscape {
+namespace UI {
+
+class PathManipulator;
+class MultiPathManipulator;
+struct PathSharedData;
+
+/**
+ * Manipulator that manages multiple path manipulators active at the same time.
+ * It functions like a boost::ptr_set - manipulators added via insert() are retained.
+ */
+class MultiPathManipulator : public PointManipulator {
+public:
+    MultiPathManipulator(PathSharedData const &data, sigc::connection &chg);
+    virtual ~MultiPathManipulator();
+    virtual bool event(GdkEvent *event);
+
+    bool empty() { return _mmap.empty(); }
+    unsigned size() { return _mmap.empty(); }
+    // TODO fix this garbage!
+    void setItems(std::map<SPPath*, std::pair<Geom::Matrix, guint32> > const &items);
+    void clear() { _mmap.clear(); }
+    void cleanup();
+
+    void selectSubpaths();
+    void selectAll();
+    void selectArea(Geom::Rect const &area, bool take);
+    void shiftSelection(int dir);
+    void linearGrow(int dir);
+    void spatialGrow(int dir);
+    void invertSelection();
+    void invertSelectionInSubpaths();
+    void deselect();
+
+    void setNodeType(NodeType t);
+    void setSegmentType(SegmentType t);
+
+    void insertNodes();
+    void joinNodes();
+    void breakNodes();
+    void deleteNodes(bool keep_shape = true);
+    void joinSegment();
+    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 setOutlineTransform(SPPath *item, Geom::Matrix const &t);
+    
+    sigc::signal<void> signal_coords_changed;
+private:
+    typedef std::pair<SPPath*, boost::shared_ptr<PathManipulator> > MapPair;
+    typedef std::map<SPPath*, 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 *);
+    void _storeClipMaskItems(SPObject *obj, std::set<SPPath*> &, bool);
+
+    MapType _mmap;
+    PathSharedData const &_path_data;
+    sigc::connection &_changed;
+    bool _show_handles;
+    bool _show_outline;
+    bool _show_path_direction;
+};
+
+} // 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 (file)
index 0000000..a57057c
--- /dev/null
@@ -0,0 +1,563 @@
+/** @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 "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 "pixmaps/cursor-node.xpm"
+#include "pixmaps/cursor-node-d.xpm"
+
+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->_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->_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->_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->_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);
+    }
+}
+
+void store_clip_mask_items(SPItem *clipped, SPObject *obj, std::map<SPItem*,
+    std::pair<Geom::Matrix, guint32> > &s, Geom::Matrix const &postm, guint32 color)
+{
+    if (!obj) return;
+    if (SP_IS_GROUP(obj) || SP_IS_OBJECTGROUP(obj)) {
+        //TODO is checking for obj->children != NULL above better?
+        for (SPObject *c = obj->children; c; c = c->next) {
+            store_clip_mask_items(clipped, c, s, postm, color);
+        }
+    } else if (SP_IS_ITEM(obj)) {
+        s.insert(std::make_pair(SP_ITEM(obj),
+            std::make_pair(sp_item_i2d_affine(clipped) * postm, color)));
+    }
+}
+
+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;
+    // TODO this is ugly!!!
+    typedef std::map<SPItem*, std::pair<Geom::Matrix, guint32> > TransMap;
+    typedef std::map<SPPath*, std::pair<Geom::Matrix, guint32> > PathMap;
+    GSList const *ilist = sel->itemList();
+    TransMap items;
+    Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+
+    for (GSList *i = const_cast<GSList*>(ilist); i; i = i->next) {
+        SPObject *obj = static_cast<SPObject*>(i->data);
+        if (SP_IS_ITEM(obj)) {
+            items.insert(std::make_pair(SP_ITEM(obj),
+                std::make_pair(Geom::identity(),
+                prefs->getColor("/tools/nodes/outline_color", 0xff0000ff))));
+            if (nt->edit_clipping_paths && SP_ITEM(i->data)->clip_ref) {
+                store_clip_mask_items(SP_ITEM(i->data),
+                    SP_OBJECT(SP_ITEM(i->data)->clip_ref->getObject()), items,
+                    nt->desktop->dt2doc(),
+                    prefs->getColor("/tools/nodes/clipping_path_color", 0x00ff00ff));
+            }
+            if (nt->edit_masks && SP_ITEM(i->data)->mask_ref) {
+                store_clip_mask_items(SP_ITEM(i->data),
+                    SP_OBJECT(SP_ITEM(i->data)->mask_ref->getObject()), items,
+                    nt->desktop->dt2doc(),
+                    prefs->getColor("/tools/nodes/mask_color", 0x0000ffff));
+            }
+        }
+    }
+
+    // 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 (TransMap::iterator i = items.begin(); i != items.end(); ++i) {
+        SPItem *obj = i->first;
+        if (SP_IS_SHAPE(obj) && !SP_IS_PATH(obj)) {
+            nt->shape_editor->set_item(obj, SH_KNOTHOLDER);
+            something_set = true;
+            break;
+        }
+    }
+    if (!something_set) {
+        nt->shape_editor->unset_item(SH_KNOTHOLDER);
+    }
+    
+    PathMap p;
+    for (TransMap::iterator i = items.begin(); i != items.end(); ++i) {
+        if (SP_IS_PATH(i->first)) {
+            p.insert(std::make_pair(SP_PATH(i->first),
+                std::make_pair(i->second.first, i->second.second)));
+        }
+    }
+
+    nt->_multipath->setItems(p);
+    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")) {
+            if (prefs->getBool("/tools/nodes/pathflash_unselected") && !nt->_multipath->empty())
+                break;
+
+            SPItem *over_item = sp_event_context_find_item (desktop, event_point(event->button),
+                FALSE, TRUE);
+            if (over_item == nt->flashed_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->_multipath->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->_multipath->selectArea(sel, !held_shift(*event));
+    }
+}
+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 (file)
index 0000000..f47ea0c
--- /dev/null
@@ -0,0 +1,84 @@
+/** @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;
+    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 (file)
index 0000000..80eaf4f
--- /dev/null
@@ -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 (file)
index 0000000..5f792cf
--- /dev/null
@@ -0,0 +1,965 @@
+/** @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/transforms.h>
+#include "ui/tool/event-utils.h"
+#include "ui/tool/node.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 "sp-metrics.h"
+#include "sp-namedview.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
+ * Represents a control point of a cubic Bezier curve in a path.
+ */
+
+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
+ * Represents a curve endpoint in an editable path.
+ */
+
+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);
+        /*Geom::Point handle_delta = handle->position() - position();
+        Geom::Point new_delta = Geom::unit_vector(new_direction) * handle_delta.length();
+        handle->setPosition(position() + new_delta);*/
+    }
+    // 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());
+        /*
+        Geom::Point handle_delta2 = other_handle->position() - other->position();
+        Geom::Point new_delta2 = Geom::unit_vector(new_direction) * handle_delta2.length();
+        other_handle->setPosition(other->position() + new_delta2);*/
+    }
+}
+
+void Node::_updateAutoHandles()
+{
+    // Recompute the position of automatic handles.
+    if (!_prev() || !_next()) {
+        _front.retract();
+        _back.retract();
+        return;
+    }
+    // TODO describe in detail what the code below does
+    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) {
+        Geom::Point dir = Geom::unit_vector((len_prev / len_next) * vec_next - vec_prev);
+        _back.setRelativePos(-dir * (len_prev / 3));
+        _front.setRelativePos(dir * (len_next / 3));
+    } else {
+        _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();
+}
+
+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;
+    }
+}
+
+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
+    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)
+{
+    if (!held_control(*event)) return;
+    if (held_alt(*event)) {
+        // with Ctrl+Alt, constrain to handle lines
+        // project the new position onto a handle line that is closer
+        Geom::Point origin = _last_drag_origin();
+        Geom::Line line_front(origin, origin + _front.relativePos());
+        Geom::Line line_back(origin, origin + _back.relativePos());
+        double dist_front, dist_back;
+        dist_front = Geom::distance(new_pos, line_front);
+        dist_back = Geom::distance(new_pos, line_back);
+        if (dist_front < dist_back) {
+            new_pos = Geom::projection(new_pos, line_front);
+        } else {
+            new_pos = Geom::projection(new_pos, line_back);
+        }
+    } else {
+        // with Ctrl, constrain to axes
+        // TODO this probably has to be separated into an AxisConstrainablePoint class
+        // TODO maybe add diagonals when the distance from origin is large enough?
+        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];
+    }
+}
+
+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 statusbar 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 statusbar 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.
+ *
+ * @todo Manage geometric representation to improve speed
+ */
+
+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 acrobatics 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;
+}
+
+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 (file)
index 0000000..9a36642
--- /dev/null
@@ -0,0 +1,366 @@
+/** @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 "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 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);
+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();
+    Node *_next();
+    Node *_prev();
+    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>;
+};
+
+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
+    N *get_pointer() const { return static_cast<N*>(_node); }
+    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() {}
+
+    sigc::signal<void, Node *> signal_insert_node;
+    sigc::signal<void, Node *> signal_remove_node;
+private:
+    list_type _nodelists;
+    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 (file)
index 0000000..ef85723
--- /dev/null
@@ -0,0 +1,1183 @@
+/** @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 "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>)
+    {
+        GQuark path_d = g_quark_from_static_string("d");
+        GQuark path_transform = g_quark_from_static_string("transform");
+        // do nothing if blocked
+        if (_blocked) return;
+
+        // only react to "d" (path data) and "transform" attribute changes
+        if (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(PathSharedData const &data, SPPath *path,
+        Geom::Matrix const &et, guint32 outline_color)
+    : PointManipulator(data.node_data.desktop, *data.node_data.selection)
+    , _path_data(data)
+    , _path(path)
+    , _spcurve(sp_path_get_curve_for_edit(path))
+    , _dragpoint(new CurveDragPoint(*this))
+    , _observer(new PathManipulatorObserver(this))
+    , _edit_transform(et)
+    , _show_handles(true)
+    , _show_outline(false)
+{
+    /* Because curve drag point is always created first, it does not cover nodes */
+    _i2d_transform = sp_item_i2d_affine(SP_ITEM(path));
+    _d2i_transform = _i2d_transform.inverse();
+    _dragpoint->setVisible(false);
+
+    _outline = sp_canvas_bpath_new(_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);
+    _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()) {
+        _path->updateRepr();
+        _path->repr->setAttribute("sodipodi:nodetypes", _createTypeString().data());
+    } else {
+        // this manipulator will have to be destroyed right after this call
+        _path->repr->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;
+            }
+        }
+    }
+}
+
+/** Select all nodes in the path. */
+void PathManipulator::selectAll()
+{
+    for (SubpathList::iterator i = _subpaths.begin(); i != _subpaths.end(); ++i) {
+        for (NodeList::iterator j = (*i)->begin(); j != (*i)->end(); ++j) {
+            _selection.insert(j.ptr());
+        }
+    }
+}
+
+/** Select points inside the given rectangle. If all points inside it are already selected,
+ * they will be deselected.
+ * @param area Area to select
+ */
+void PathManipulator::selectArea(Geom::Rect const &area)
+{
+    bool nothing_selected = true;
+    std::vector<Node*> in_area;
+    for (SubpathList::iterator i = _subpaths.begin(); i != _subpaths.end(); ++i) {
+        for (NodeList::iterator j = (*i)->begin(); j != (*i)->end(); ++j) {
+            if (area.contains(j->position())) {
+                in_area.push_back(j.ptr());
+                if (!j->selected()) {
+                    _selection.insert(j.ptr());
+                    nothing_selected = false;
+                }
+            }
+        }
+    }
+    if (nothing_selected) {
+        for (std::vector<Node*>::iterator i = in_area.begin(); i != in_area.end(); ++i) {
+            _selection.erase(*i);
+        }
+    }
+}
+
+/** 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 entire path. */
+void PathManipulator::invertSelection()
+{
+    for (SubpathList::iterator i = _subpaths.begin(); i != _subpaths.end(); ++i) {
+        for (NodeList::iterator j = (*i)->begin(); j != (*i)->end(); ++j) {
+            if (j->selected()) _selection.erase(j.ptr());
+            else _selection.insert(j.ptr());
+        }
+    }
+}
+
+/** 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 const &preserve_pos)
+{
+    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 contiguous selection
+        NodeList::iterator sel_beg = sp->begin(), sel_end;
+        if (sp->closed()) {
+            while (sel_beg->selected()) ++sel_beg;
+        }
+
+        // Main 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!");
+
+            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()
+{
+    // TODO
+}
+
+/** 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(_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;
+
+    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) 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()->setType(NODE_CUSP, false);
+            if (keep_shape || !sel_beg.prev()) 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 find out optimal error value
+                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;
+    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();
+}
+
+/** 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(_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(_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;
+}
+
+/** Called by the XML observer when something else than us modifies the path. */
+void PathManipulator::_externalChange(unsigned type)
+{
+    switch (type) {
+    case PATH_CHANGE_D: {
+        _spcurve->unref();
+        _spcurve = sp_path_get_curve_for_edit(_path);
+
+        // 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(_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(_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("sodipodi:nodetypes") : 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();
+    if (!empty()) sp_shape_set_curve(SP_SHAPE(_path), _spcurve, false);
+}
+
+/** 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();
+}
+
+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 (file)
index 0000000..9ed9e4f
--- /dev/null
@@ -0,0 +1,146 @@
+/** @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 UI {
+
+class PathManipulator;
+class ControlPointSelection;
+class PathManipulatorObserver;
+class CurveDragPoint;
+class PathCanvasGroups;
+
+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(PathSharedData const &data, SPPath *path,
+        Geom::Matrix const &edit_trans, guint32 outline_color);
+    ~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 selectAll();
+    void selectArea(Geom::Rect const &);
+    void shiftSelection(int dir);
+    void linearGrow(int dir);
+    void spatialGrow(int dir);
+    void invertSelection();
+    void invertSelectionInSubpaths();
+
+    void insertNodes();
+    void weldNodes(NodeList::iterator const &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 setOutlineTransform(Geom::Matrix const &);
+
+    NodeList::iterator subdivideSegment(NodeList::iterator after, double t);
+
+    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 _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;
+    PathSharedData const &_path_data;
+    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;
+
+    friend class PathManipulatorObserver;
+    friend class CurveDragPoint;
+};
+
+} // 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 (file)
index 0000000..b189a71
--- /dev/null
@@ -0,0 +1,135 @@
+/** @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);
+}
+
+void SelectableControlPoint::_connectHandlers()
+{
+    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 (file)
index 0000000..a432b68
--- /dev/null
@@ -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);
+private:
+    void _connectHandlers();
+    void _takeSelection();
+    
+    bool _clickedHandler(GdkEventButton *);
+    void _grabbedHandler(GdkEventMotion *);
+    
+    ControlPointSelection &_selection;
+};
+
+} // 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 (file)
index 0000000..f95c9e0
--- /dev/null
@@ -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 (file)
index 0000000..f7c00ea
--- /dev/null
@@ -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/transform-handle-set.cpp b/src/ui/tool/transform-handle-set.cpp
new file mode 100644 (file)
index 0000000..f3e2847
--- /dev/null
@@ -0,0 +1,653 @@
+/** @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
+// Should be moved to a location where it can be accessed globally
+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 nodes */
+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);
+        }
+    }
+    /*
+    static Geom::Point _corner_to_offset_unit(unsigned c) {
+        switch (c % 4) {
+        case 0: return Geom::Point(-1, 1);
+        case 1: return Geom::Point(1, 1);
+        case 2: return Geom::Point(1, -1);
+        default: return Geom::Point(-1, -1);
+        }
+    }*/
+    Geom::Point _rot_center;
+    Geom::Point _rot_opposite;
+    unsigned _corner;
+    static double _last_angle;
+};
+double RotateHandle::_last_angle = 0;
+
+class SkewHandle : public TransformHandle {
+public:
+    SkewHandle(TransformHandleSet &th, unsigned side)
+        : TransformHandle(th, side_to_anchor(side), _side_to_pixbuf(side))
+        , _side(side)
+    {}
+protected:
+    virtual void startTransform() {
+        _skew_center = _th.rotationCenter();
+        Geom::Rect b = _th.bounds();
+        _skew_opposite = Geom::middle_point(b.corner(_side + 2), b.corner(_side + 3));
+        _last_angle = 0;
+        _last_horizontal = _side % 2;
+    }
+    virtual Geom::Matrix computeTransform(Geom::Point const &new_pos, GdkEventMotion *event)
+    {
+        Geom::Point scc = held_shift(*event) ? _skew_center : _skew_opposite;
+        // d1 and d2 are reversed with respect to ScaleSideHandle
+        Geom::Dim2 d1 = static_cast<Geom::Dim2>(_side % 2);
+        Geom::Dim2 d2 = static_cast<Geom::Dim2>((_side + 1) % 2);
+        Geom::Point proj, scale(1.0, 1.0);
+
+        // Skew handles allow scaling up to integer multiples of the original size
+        // in the second direction; prevent explosions
+        // TODO should the scaling part be only active with Alt?
+        if (!Geom::are_near(_origin[d2], scc[d2])) {
+            scale[d2] = (new_pos - scc)[d2] / (_origin - scc)[d2];
+        }
+
+        if (scale[d2] < 1.0) {
+            scale[d2] = copysign(1.0, scale[d2]);
+        } else {
+            scale[d2] = floor(scale[d2]);
+        }
+
+        // Calculate skew angle. The angle is calculated with regards to the point obtained
+        // by projecting the handle position on the relevant side of the bounding box.
+        // This avoids degeneracies when moving the skew angle over the rotation center
+        proj[d1] = new_pos[d1];
+        proj[d2] = scc[d2] + (_origin[d2] - scc[d2]) * scale[d2];
+        double angle = 0;
+        if (!Geom::are_near(proj[d2], scc[d2]))
+            angle = Geom::angle_between(_origin - scc, proj - scc);
+        if (held_control(*event)) angle = snap_angle(angle);
+
+        // skew matrix has the from [[1, k],[0, 1]] for horizontal skew
+        // and [[1,0],[k,1]] for vertical skew.
+        Geom::Matrix skew = Geom::identity();
+        // correct the sign of the tangent
+        skew[d2 + 1] = (d1 == Geom::X ? -1.0 : 1.0) * tan(angle);
+
+        _last_angle = angle;
+        Geom::Matrix t = Geom::Translate(-scc)
+            * Geom::Scale(scale) * skew
+            * Geom::Translate(scc);
+        return t;
+    }
+    virtual CommitEvent getCommitEvent() {
+        return _side % 2
+            ? COMMIT_MOUSE_SKEW_Y
+            : COMMIT_MOUSE_SKEW_X;
+    }
+    virtual Glib::ustring _getTip(unsigned state) {
+        if (state_held_shift(state)) {
+            if (state_held_control(state)) {
+                return format_tip(C_("Transform handle tip",
+                    "<b>Shift+Ctrl:</b> skew about the rotation center with snapping "
+                    "to %f° increments"), snap_increment_degrees());
+            }
+            return C_("Transform handle tip", "<b>Shift:</b> skew about the rotation center");
+        }
+        if (state_held_control(state)) {
+            return format_tip(C_("Transform handle tip",
+                "<b>Ctrl:</b> snap skew angle to %f° increments"), snap_increment_degrees());
+        }
+        return C_("Transform handle tip",
+            "<b>Skew handle:</b> drag to skew (shear) selection about "
+            "the opposite handle");
+    }
+    virtual Glib::ustring _getDragTip(GdkEventMotion *event) {
+        if (_last_horizontal) {
+            return format_tip(C_("Transform handle tip", "Skew horizontally by %.2f°"),
+                _last_angle * 360.0);
+        } else {
+            return format_tip(C_("Transform handle tip", "Skew vertically by %.2f°"),
+                _last_angle * 360.0);
+        }
+    }
+    virtual bool _hasDragTips() { return true; }
+private:
+    static Glib::RefPtr<Gdk::Pixbuf> _side_to_pixbuf(unsigned s) {
+        sp_select_context_get_type();
+        switch (s % 4) {
+        case 0: return Glib::wrap(handles[9], true);
+        case 1: return Glib::wrap(handles[7], true);
+        case 2: return Glib::wrap(handles[5], true);
+        default: return Glib::wrap(handles[11], true);
+        }
+    }
+    Geom::Point _skew_center;
+    Geom::Point _skew_opposite;
+    unsigned _side;
+    static bool _last_horizontal;
+    static double _last_angle;
+};
+bool SkewHandle::_last_horizontal = false;
+double SkewHandle::_last_angle = 0;
+
+class RotationCenter : public ControlPoint {
+public:
+    RotationCenter(TransformHandleSet &th)
+        : ControlPoint(th._desktop, Geom::Point(), Gtk::ANCHOR_CENTER, _get_pixbuf(),
+            &center_cset, th._transform_handle_group)
+        , _th(th)
+    {
+        setVisible(false);
+    }
+protected:
+    virtual Glib::ustring _getTip(unsigned state) {
+        return C_("Transform handle tip",
+            "<b>Rotation center:</b> drag to change the origin of transforms");
+    }
+private:
+    static Glib::RefPtr<Gdk::Pixbuf> _get_pixbuf() {
+        sp_select_context_get_type();
+        return Glib::wrap(handles[12], true);
+    }
+    TransformHandleSet &_th;
+};
+
+TransformHandleSet::TransformHandleSet(SPDesktop *d, SPCanvasGroup *th_group)
+    : Manipulator(d)
+    , _active(0)
+    , _transform_handle_group(th_group)
+    , _mode(MODE_SCALE)
+    , _in_transform(false)
+    , _visible(true)
+{
+    _trans_outline = static_cast<CtrlRect*>(sp_canvas_item_new(sp_desktop_controls(_desktop),
+        SP_TYPE_CTRLRECT, NULL));
+    sp_canvas_item_hide(_trans_outline);
+    _trans_outline->setDashed(true);
+
+    for (unsigned i = 0; i < 4; ++i) {
+        _scale_corners[i] = new ScaleCornerHandle(*this, i);
+        _scale_sides[i] = new ScaleSideHandle(*this, i);
+        _rot_corners[i] = new RotateHandle(*this, i);
+        _skew_sides[i] = new SkewHandle(*this, i);
+    }
+    _center = new RotationCenter(*this);
+    // when transforming, update rotation center position
+    signal_transform.connect(sigc::mem_fun(*_center, &RotationCenter::transform));
+}
+
+TransformHandleSet::~TransformHandleSet()
+{
+    for (unsigned i = 0; i < 17; ++i) {
+        delete _handles[i];
+    }
+}
+
+/** Sets the mode of transform handles (scale or rotate). */
+void TransformHandleSet::setMode(Mode m)
+{
+    _mode = m;
+    _updateVisibility(_visible);
+}
+
+Geom::Rect TransformHandleSet::bounds()
+{
+    return Geom::Rect(*_scale_corners[0], *_scale_corners[2]);
+}
+
+ControlPoint &TransformHandleSet::rotationCenter()
+{
+    return *_center;
+}
+
+void TransformHandleSet::setVisible(bool v)
+{
+    if (_visible != v) {
+        _visible = v;
+        _updateVisibility(_visible);
+    }
+}
+
+void TransformHandleSet::setBounds(Geom::Rect const &r, bool preserve_center)
+{
+    if (_in_transform) {
+        _trans_outline->setRectangle(r);
+    } else {
+        for (unsigned i = 0; i < 4; ++i) {
+            _scale_corners[i]->move(r.corner(i));
+            _scale_sides[i]->move(Geom::middle_point(r.corner(i), r.corner(i+1)));
+            _rot_corners[i]->move(r.corner(i));
+            _skew_sides[i]->move(Geom::middle_point(r.corner(i), r.corner(i+1)));
+        }
+        if (!preserve_center) _center->move(r.midpoint());
+        if (_visible) _updateVisibility(true);
+    }
+}
+
+bool TransformHandleSet::event(GdkEvent*)
+{
+    return false;
+}
+
+void TransformHandleSet::_emitTransform(Geom::Matrix const &t)
+{
+    signal_transform.emit(t);
+    _center->transform(t);
+}
+
+void TransformHandleSet::_setActiveHandle(ControlPoint *th)
+{
+    _active = th;
+    if (_in_transform)
+        throw std::logic_error("Transform initiated when another transform in progress");
+    _in_transform = true;
+    // hide all handles except the active one
+    _updateVisibility(false);
+    sp_canvas_item_show(_trans_outline);
+}
+
+void TransformHandleSet::_clearActiveHandle()
+{
+    // This can only be called from handles, so they had to be visible before _setActiveHandle
+    sp_canvas_item_hide(_trans_outline);
+    _active = 0;
+    _in_transform = false;
+    _updateVisibility(_visible);
+}
+
+/** Update the visibility of transformation handles according to settings and the dimensions
+ * of the bounding box. It hides the handles that would have no effect or lead to
+ * discontinuities. Additionally, side handles for which there is no space are not shown. */
+void TransformHandleSet::_updateVisibility(bool v)
+{
+    if (v) {
+        Geom::Rect b = bounds();
+        Geom::Point handle_size(
+            gdk_pixbuf_get_width(handles[0]) / _desktop->current_zoom(),
+            gdk_pixbuf_get_height(handles[0]) / _desktop->current_zoom());
+        Geom::Point bp = b.dimensions();
+
+        // do not scale when the bounding rectangle has zero width or height
+        bool show_scale = (_mode == MODE_SCALE) && !Geom::are_near(b.minExtent(), 0);
+        // do not rotate if the bounding rectangle is degenerate
+        bool show_rotate = (_mode == MODE_ROTATE_SKEW) && !Geom::are_near(b.maxExtent(), 0);
+        bool show_scale_side[2], show_skew[2];
+
+        // show sides if:
+        // a) there is enough space between corner handles, or
+        // b) corner handles are not shown, but side handles make sense
+        // this affects horizontal and vertical scale handles; skew handles never
+        // make sense if rotate handles are not shown
+        for (unsigned i = 0; i < 2; ++i) {
+            Geom::Dim2 d = static_cast<Geom::Dim2>(i);
+            Geom::Dim2 otherd = static_cast<Geom::Dim2>((i+1)%2);
+            show_scale_side[i] = (_mode == MODE_SCALE);
+            show_scale_side[i] &= (show_scale ? bp[d] >= handle_size[d]
+                : !Geom::are_near(bp[otherd], 0));
+            show_skew[i] = (show_rotate && bp[d] >= handle_size[d]
+                && !Geom::are_near(bp[otherd], 0));
+        }
+        for (unsigned i = 0; i < 4; ++i) {
+            _scale_corners[i]->setVisible(show_scale);
+            _rot_corners[i]->setVisible(show_rotate);
+            _scale_sides[i]->setVisible(show_scale_side[i%2]);
+            _skew_sides[i]->setVisible(show_skew[i%2]);
+        }
+        // show rotation center if there is enough space (?)
+        _center->setVisible(show_rotate /*&& bp[Geom::X] > handle_size[Geom::X]
+            && bp[Geom::Y] > handle_size[Geom::Y]*/);
+    } else {
+        for (unsigned i = 0; i < 17; ++i) {
+            if (_handles[i] != _active)
+                _handles[i]->setVisible(false);
+        }
+    }
+    
+}
+
+} // namespace UI
+} // namespace Inkscape
+
+/*
+  Local Variables:
+  mode:c++
+  c-file-style:"stroustrup"
+  c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+  indent-tabs-mode:nil
+  fill-column:99
+  End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :
diff --git a/src/ui/tool/transform-handle-set.h b/src/ui/tool/transform-handle-set.h
new file mode 100644 (file)
index 0000000..48ad3af
--- /dev/null
@@ -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 (file)
index 0000000..c627786
--- /dev/null
@@ -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 (file)
index 0000000..d5abeff
--- /dev/null
@@ -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 :
index 29d24c10143690aa54f7a097ad73e5f5bbb5f7f7..d26a4e6d5cf87c018e7c57f152c2986edef1a747 100644 (file)
@@ -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,8 @@
 #include "ui/dialog/layers.h"
 #include "ui/dialog/swatches.h"
 #include "ui/icon-names.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 +920,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->_multipath->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->_multipath->invertSelection();
             } else {
                 sp_edit_invert_in_all_layers(dt);
             }
@@ -948,7 +953,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 +964,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 +976,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->_multipath->deselect();
             } else {
                 sp_desktop_selection(dt)->clear();
             }
@@ -1086,7 +1094,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 +1379,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;
index e0fe9bfd100c681617843ce0c865ac83bb1e8075..ee45665f3d5d02d199365651624b15dd0a7a169a 100644 (file)
@@ -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"
@@ -88,6 +87,9 @@
 #include "../tweak-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"
@@ -144,7 +146,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 },
     { "SPZoomContext",     "zoom_tool",      SP_VERB_CONTEXT_ZOOM, SP_VERB_CONTEXT_ZOOM_PREFS },
     { "SPRectContext",     "rect_tool",      SP_VERB_CONTEXT_RECT, SP_VERB_CONTEXT_RECT_PREFS },
@@ -177,7 +179,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")},
@@ -981,135 +983,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->joinSegment();
 }
 
 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 */
@@ -1132,54 +1126,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();
@@ -1188,7 +1157,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
@@ -1199,15 +1169,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) );
@@ -1216,13 +1184,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
@@ -1241,26 +1209,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
@@ -1427,7 +1375,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) );
     }
 
     {
@@ -1442,25 +1390,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) */
@@ -6339,10 +6287,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
@@ -6535,7 +6483,7 @@ cell_data_func  (GtkCellLayout */*cell_layout*/,
 gboolean            text_toolbox_completion_match_selected    (GtkEntryCompletion *widget,
                                                         GtkTreeModel       *model,
                                                         GtkTreeIter        *iter,
-                                                        GObject *tbl)
+                                                        GObject *tbl) 
 {
     // We intercept this signal so as to fire family_changed at once (without it, you'd have to
     // press Enter again after choosing a completion)
@@ -6610,10 +6558,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;
                      }
-                 }
+                 } 
              }
          }
 
@@ -6652,7 +6600,7 @@ 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);
 
@@ -6665,7 +6613,7 @@ 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);
@@ -6881,7 +6829,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;
@@ -6972,7 +6920,7 @@ static void connector_tb_event_attr_changed(Inkscape::XML::Node *repr,
 
     gtk_adjustment_set_value(adj, spacing);
     gtk_adjustment_value_changed(adj);
-
+    
     spinbutton_defocus(GTK_OBJECT(tbl));
 }