From c04e30df241a3ee039077425bab9b9c37abe2854 Mon Sep 17 00:00:00 2001 From: Markus Engel Date: Sat, 9 Nov 2013 23:36:13 +0100 Subject: Moved and renamed some tool-related files. (bzr r12785) --- src/CMakeLists.txt | 49 - src/Makefile.am | 1 + src/Makefile_insert | 26 +- src/arc-context.cpp | 503 --------- src/arc-context.h | 76 -- src/box3d-context.cpp | 637 ------------ src/box3d-context.h | 95 -- src/box3d-side.cpp | 2 +- src/box3d.cpp | 2 +- src/common-context.cpp | 162 --- src/common-context.h | 123 --- src/connector-context.cpp | 1496 --------------------------- src/connector-context.h | 135 --- src/context-fns.cpp | 2 +- src/desktop-events.cpp | 2 +- src/desktop-style.cpp | 2 +- src/desktop.cpp | 6 +- src/dialogs/dialog-events.cpp | 2 +- src/draw-anchor.cpp | 6 +- src/draw-anchor.h | 6 +- src/draw-context.cpp | 785 -------------- src/draw-context.h | 144 --- src/dropper-context.cpp | 414 -------- src/dropper-context.h | 73 -- src/dyna-draw-context.cpp | 1216 ---------------------- src/dyna-draw-context.h | 94 -- src/eraser-context.cpp | 1019 ------------------ src/eraser-context.h | 76 -- src/event-context.cpp | 1541 ---------------------------- src/event-context.h | 244 ----- src/file.cpp | 2 +- src/flood-context.cpp | 1263 ----------------------- src/flood-context.h | 74 -- src/gradient-chemistry.cpp | 2 +- src/gradient-context.cpp | 977 ------------------ src/gradient-context.h | 76 -- src/helper/window.cpp | 2 +- src/inkscape.cpp | 2 +- src/interface.cpp | 4 +- src/knot.cpp | 2 +- src/knotholder.cpp | 8 +- src/live_effects/effect.cpp | 2 +- src/live_effects/lpe-line_segment.cpp | 2 +- src/live_effects/parameter/path.cpp | 2 +- src/lpe-tool-context.cpp | 503 --------- src/lpe-tool-context.h | 99 -- src/measure-context.cpp | 777 -------------- src/measure-context.h | 50 - src/mesh-context.cpp | 1017 ------------------ src/mesh-context.h | 77 -- src/pen-context.cpp | 1404 ------------------------- src/pen-context.h | 105 -- src/pencil-context.cpp | 916 ----------------- src/pencil-context.h | 68 -- src/persp3d.cpp | 2 +- src/rect-context.cpp | 527 ---------- src/rect-context.h | 65 -- src/select-context.cpp | 1252 ---------------------- src/select-context.h | 73 -- src/selection-chemistry.cpp | 10 +- src/seltrans.cpp | 2 +- src/shortcuts.cpp | 2 +- src/snap.cpp | 2 +- src/sp-path.cpp | 2 +- src/spiral-context.cpp | 467 --------- src/spiral-context.h | 66 -- src/spray-context.cpp | 890 ---------------- src/spray-context.h | 121 --- src/star-context.cpp | 494 --------- src/star-context.h | 74 -- src/text-context.cpp | 1765 -------------------------------- src/text-context.h | 101 -- src/tools-switch.cpp | 44 +- src/tweak-context.cpp | 1524 --------------------------- src/tweak-context.h | 106 -- src/ui/CMakeLists.txt | 54 +- src/ui/clipboard.cpp | 4 +- src/ui/dialog/align-and-distribute.cpp | 2 +- src/ui/dialog/dialog.cpp | 2 +- src/ui/dialog/guides.cpp | 2 +- src/ui/dialog/layer-properties.cpp | 2 +- src/ui/dialog/layers.cpp | 2 +- src/ui/dialog/spellcheck.cpp | 2 +- src/ui/dialog/xml-tree.cpp | 2 +- src/ui/tool/Makefile_insert | 2 - src/ui/tool/control-point.cpp | 2 +- src/ui/tool/manipulator.h | 2 +- src/ui/tool/node-tool.cpp | 705 ------------- src/ui/tool/node-tool.h | 105 -- src/ui/tool/selector.cpp | 2 +- src/ui/tool/transform-handle-set.cpp | 2 +- src/ui/tools/Makefile_insert | 28 + src/ui/tools/arc-tool.cpp | 503 +++++++++ src/ui/tools/arc-tool.h | 76 ++ src/ui/tools/box3d-tool.cpp | 637 ++++++++++++ src/ui/tools/box3d-tool.h | 95 ++ src/ui/tools/calligraphic-tool.cpp | 1216 ++++++++++++++++++++++ src/ui/tools/calligraphic-tool.h | 94 ++ src/ui/tools/connector-tool.cpp | 1496 +++++++++++++++++++++++++++ src/ui/tools/connector-tool.h | 135 +++ src/ui/tools/dropper-tool.cpp | 414 ++++++++ src/ui/tools/dropper-tool.h | 73 ++ src/ui/tools/dynamic-base.cpp | 162 +++ src/ui/tools/dynamic-base.h | 123 +++ src/ui/tools/eraser-tool.cpp | 1019 ++++++++++++++++++ src/ui/tools/eraser-tool.h | 76 ++ src/ui/tools/flood-tool.cpp | 1263 +++++++++++++++++++++++ src/ui/tools/flood-tool.h | 74 ++ src/ui/tools/freehand-base.cpp | 785 ++++++++++++++ src/ui/tools/freehand-base.h | 144 +++ src/ui/tools/gradient-tool.cpp | 977 ++++++++++++++++++ src/ui/tools/gradient-tool.h | 76 ++ src/ui/tools/lpe-tool.cpp | 503 +++++++++ src/ui/tools/lpe-tool.h | 99 ++ src/ui/tools/measure-tool.cpp | 777 ++++++++++++++ src/ui/tools/measure-tool.h | 50 + src/ui/tools/mesh-tool.cpp | 1017 ++++++++++++++++++ src/ui/tools/mesh-tool.h | 77 ++ src/ui/tools/node-tool.cpp | 705 +++++++++++++ src/ui/tools/node-tool.h | 105 ++ src/ui/tools/pen-tool.cpp | 1404 +++++++++++++++++++++++++ src/ui/tools/pen-tool.h | 105 ++ src/ui/tools/pencil-tool.cpp | 916 +++++++++++++++++ src/ui/tools/pencil-tool.h | 68 ++ src/ui/tools/rect-tool.cpp | 527 ++++++++++ src/ui/tools/rect-tool.h | 65 ++ src/ui/tools/select-tool.cpp | 1252 ++++++++++++++++++++++ src/ui/tools/select-tool.h | 73 ++ src/ui/tools/spiral-tool.cpp | 467 +++++++++ src/ui/tools/spiral-tool.h | 66 ++ src/ui/tools/spray-tool.cpp | 890 ++++++++++++++++ src/ui/tools/spray-tool.h | 121 +++ src/ui/tools/star-tool.cpp | 494 +++++++++ src/ui/tools/star-tool.h | 74 ++ src/ui/tools/text-tool.cpp | 1765 ++++++++++++++++++++++++++++++++ src/ui/tools/text-tool.h | 101 ++ src/ui/tools/tool-base.cpp | 1541 ++++++++++++++++++++++++++++ src/ui/tools/tool-base.h | 244 +++++ src/ui/tools/tweak-tool.cpp | 1524 +++++++++++++++++++++++++++ src/ui/tools/tweak-tool.h | 106 ++ src/ui/tools/zoom-tool.cpp | 250 +++++ src/ui/tools/zoom-tool.h | 47 + src/ui/widget/rotateable.cpp | 2 +- src/ui/widget/selected-style.cpp | 2 +- src/ui/widget/spinbutton.cpp | 2 +- src/vanishing-point.cpp | 2 +- src/verbs.cpp | 8 +- src/widgets/arc-toolbar.cpp | 2 +- src/widgets/box3d-toolbar.cpp | 4 +- src/widgets/calligraphy-toolbar.cpp | 2 +- src/widgets/connector-toolbar.cpp | 4 +- src/widgets/desktop-widget.cpp | 2 +- src/widgets/dropper-toolbar.cpp | 2 +- src/widgets/eraser-toolbar.cpp | 2 +- src/widgets/gradient-toolbar.cpp | 4 +- src/widgets/lpe-toolbar.cpp | 4 +- src/widgets/measure-toolbar.cpp | 2 +- src/widgets/mesh-toolbar.cpp | 4 +- src/widgets/node-toolbar.cpp | 4 +- src/widgets/paintbucket-toolbar.cpp | 4 +- src/widgets/pencil-toolbar.cpp | 4 +- src/widgets/rect-toolbar.cpp | 2 +- src/widgets/sp-color-notebook.cpp | 2 +- src/widgets/spinbutton-events.cpp | 2 +- src/widgets/spiral-toolbar.cpp | 2 +- src/widgets/spray-toolbar.cpp | 4 +- src/widgets/star-toolbar.cpp | 2 +- src/widgets/text-toolbar.cpp | 4 +- src/widgets/toolbox.cpp | 2 +- src/widgets/tweak-toolbar.cpp | 4 +- src/widgets/zoom-toolbar.cpp | 4 +- src/zoom-context.cpp | 250 ----- src/zoom-context.h | 47 - 173 files changed, 25071 insertions(+), 25067 deletions(-) delete mode 100644 src/arc-context.cpp delete mode 100644 src/arc-context.h delete mode 100644 src/box3d-context.cpp delete mode 100644 src/box3d-context.h delete mode 100644 src/common-context.cpp delete mode 100644 src/common-context.h delete mode 100644 src/connector-context.cpp delete mode 100644 src/connector-context.h delete mode 100644 src/draw-context.cpp delete mode 100644 src/draw-context.h delete mode 100644 src/dropper-context.cpp delete mode 100644 src/dropper-context.h delete mode 100644 src/dyna-draw-context.cpp delete mode 100644 src/dyna-draw-context.h delete mode 100644 src/eraser-context.cpp delete mode 100644 src/eraser-context.h delete mode 100644 src/event-context.cpp delete mode 100644 src/event-context.h delete mode 100644 src/flood-context.cpp delete mode 100644 src/flood-context.h delete mode 100644 src/gradient-context.cpp delete mode 100644 src/gradient-context.h delete mode 100644 src/lpe-tool-context.cpp delete mode 100644 src/lpe-tool-context.h delete mode 100644 src/measure-context.cpp delete mode 100644 src/measure-context.h delete mode 100644 src/mesh-context.cpp delete mode 100644 src/mesh-context.h delete mode 100644 src/pen-context.cpp delete mode 100644 src/pen-context.h delete mode 100644 src/pencil-context.cpp delete mode 100644 src/pencil-context.h delete mode 100644 src/rect-context.cpp delete mode 100644 src/rect-context.h delete mode 100644 src/select-context.cpp delete mode 100644 src/select-context.h delete mode 100644 src/spiral-context.cpp delete mode 100644 src/spiral-context.h delete mode 100644 src/spray-context.cpp delete mode 100644 src/spray-context.h delete mode 100644 src/star-context.cpp delete mode 100644 src/star-context.h delete mode 100644 src/text-context.cpp delete mode 100644 src/text-context.h delete mode 100644 src/tweak-context.cpp delete mode 100644 src/tweak-context.h delete mode 100644 src/ui/tool/node-tool.cpp delete mode 100644 src/ui/tool/node-tool.h create mode 100644 src/ui/tools/Makefile_insert create mode 100644 src/ui/tools/arc-tool.cpp create mode 100644 src/ui/tools/arc-tool.h create mode 100644 src/ui/tools/box3d-tool.cpp create mode 100644 src/ui/tools/box3d-tool.h create mode 100644 src/ui/tools/calligraphic-tool.cpp create mode 100644 src/ui/tools/calligraphic-tool.h create mode 100644 src/ui/tools/connector-tool.cpp create mode 100644 src/ui/tools/connector-tool.h create mode 100644 src/ui/tools/dropper-tool.cpp create mode 100644 src/ui/tools/dropper-tool.h create mode 100644 src/ui/tools/dynamic-base.cpp create mode 100644 src/ui/tools/dynamic-base.h create mode 100644 src/ui/tools/eraser-tool.cpp create mode 100644 src/ui/tools/eraser-tool.h create mode 100644 src/ui/tools/flood-tool.cpp create mode 100644 src/ui/tools/flood-tool.h create mode 100644 src/ui/tools/freehand-base.cpp create mode 100644 src/ui/tools/freehand-base.h create mode 100644 src/ui/tools/gradient-tool.cpp create mode 100644 src/ui/tools/gradient-tool.h create mode 100644 src/ui/tools/lpe-tool.cpp create mode 100644 src/ui/tools/lpe-tool.h create mode 100644 src/ui/tools/measure-tool.cpp create mode 100644 src/ui/tools/measure-tool.h create mode 100644 src/ui/tools/mesh-tool.cpp create mode 100644 src/ui/tools/mesh-tool.h create mode 100644 src/ui/tools/node-tool.cpp create mode 100644 src/ui/tools/node-tool.h create mode 100644 src/ui/tools/pen-tool.cpp create mode 100644 src/ui/tools/pen-tool.h create mode 100644 src/ui/tools/pencil-tool.cpp create mode 100644 src/ui/tools/pencil-tool.h create mode 100644 src/ui/tools/rect-tool.cpp create mode 100644 src/ui/tools/rect-tool.h create mode 100644 src/ui/tools/select-tool.cpp create mode 100644 src/ui/tools/select-tool.h create mode 100644 src/ui/tools/spiral-tool.cpp create mode 100644 src/ui/tools/spiral-tool.h create mode 100644 src/ui/tools/spray-tool.cpp create mode 100644 src/ui/tools/spray-tool.h create mode 100644 src/ui/tools/star-tool.cpp create mode 100644 src/ui/tools/star-tool.h create mode 100644 src/ui/tools/text-tool.cpp create mode 100644 src/ui/tools/text-tool.h create mode 100644 src/ui/tools/tool-base.cpp create mode 100644 src/ui/tools/tool-base.h create mode 100644 src/ui/tools/tweak-tool.cpp create mode 100644 src/ui/tools/tweak-tool.h create mode 100644 src/ui/tools/zoom-tool.cpp create mode 100644 src/ui/tools/zoom-tool.h delete mode 100644 src/zoom-context.cpp delete mode 100644 src/zoom-context.h (limited to 'src') diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 32bcf19a7..54b15d342 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -77,7 +77,6 @@ set(sp_SRC sp-tspan.cpp sp-use-reference.cpp sp-use.cpp - spiral-context.cpp splivarot.cpp @@ -167,19 +166,15 @@ set(sp_SRC ) set(inkscape_SRC - arc-context.cpp attributes.cpp axis-manip.cpp - box3d-context.cpp box3d-side.cpp box3d.cpp color-profile.cpp color.cpp - common-context.cpp composite-undo-stack-observer.cpp conditions.cpp conn-avoid-ref.cpp - connector-context.cpp console-output-undo-observer.cpp context-fns.cpp desktop-events.cpp @@ -193,26 +188,19 @@ set(inkscape_SRC document.cpp doxygen-main.cpp draw-anchor.cpp - draw-context.cpp - dropper-context.cpp - dyna-draw-context.cpp ege-adjustment-action.cpp ege-color-prof-tracker.cpp ege-output-action.cpp ege-select-one-action.cpp - eraser-context.cpp - event-context.cpp event-log.cpp extract-uri.cpp file.cpp filter-chemistry.cpp filter-enums.cpp - flood-context.cpp gc-anchored.cpp gc-finalized.cpp gc.cpp gradient-chemistry.cpp - gradient-context.cpp gradient-drag.cpp graphlayout.cpp guide-snapper.cpp @@ -232,12 +220,9 @@ set(inkscape_SRC layer-model.cpp line-geometry.cpp line-snapper.cpp - lpe-tool-context.cpp main-cmdlineact.cpp marker.cpp - measure-context.cpp media.cpp - mesh-context.cpp message-context.cpp message-stack.cpp mod360.cpp @@ -245,8 +230,6 @@ set(inkscape_SRC object-hierarchy.cpp object-snapper.cpp path-chemistry.cpp - pen-context.cpp - pencil-context.cpp persp3d-reference.cpp persp3d.cpp perspective-line.cpp @@ -256,13 +239,11 @@ set(inkscape_SRC profile-manager.cpp proj_pt.cpp rdf.cpp - rect-context.cpp removeoverlap.cpp resource-manager.cpp rubberband.cpp satisfied-guide-cns.cpp selcue.cpp - select-context.cpp selection-chemistry.cpp selection-describer.cpp selection.cpp @@ -276,17 +257,13 @@ set(inkscape_SRC snapped-line.cpp snapped-point.cpp snapper.cpp - spray-context.cpp - star-context.cpp style.cpp svg-view-widget.cpp svg-view.cpp text-chemistry.cpp - text-context.cpp text-editing.cpp tools-switch.cpp transf_mat_3x4.cpp - tweak-context.cpp unclump.cpp unicoderange.cpp uri-references.cpp @@ -294,20 +271,16 @@ set(inkscape_SRC vanishing-point.cpp verbs.cpp version.cpp - zoom-context.cpp - # ------- # Headers MultiPrinter.h PylogFormatter.h TRPIFormatter.h - arc-context.h attributes-test.h attributes.h axis-manip.h bad-uri-exception.h - box3d-context.h box3d-side.h box3d.h cms-color-types.h @@ -318,12 +291,10 @@ set(inkscape_SRC color-rgba.h color.h colorspace.h - common-context.h composite-undo-stack-observer.h conditions.h conn-avoid-ref.h connection-pool.h - connector-context.h console-output-undo-observer.h context-fns.h decimal-round.h @@ -339,16 +310,11 @@ set(inkscape_SRC document-undo.h document.h draw-anchor.h - draw-context.h - dropper-context.h - dyna-draw-context.h ege-adjustment-action.h ege-color-prof-tracker.h ege-output-action.h ege-select-one-action.h enums.h - eraser-context.h - event-context.h event-log.h event.h extract-uri-test.h @@ -358,7 +324,6 @@ set(inkscape_SRC fill-or-stroke.h filter-chemistry.h filter-enums.h - flood-context.h gc-alloc.h gc-allocator.h gc-anchored.h @@ -367,7 +332,6 @@ set(inkscape_SRC gc-managed.h gc-soft-ptr.h gradient-chemistry.h - gradient-context.h gradient-drag.h graphlayout.h guide-snapper.h @@ -393,15 +357,12 @@ set(inkscape_SRC layer-model.h line-geometry.h line-snapper.h - lpe-tool-context.h macros.h main-cmdlineact.h marker-test.h marker.h - measure-context.h media.h menus-skeleton.h - mesh-context.h message-context.h message-stack.h message.h @@ -414,8 +375,6 @@ set(inkscape_SRC object-snapper.h path-chemistry.h path-prefix.h - pen-context.h - pencil-context.h persp3d-reference.h persp3d.h perspective-line.h @@ -427,7 +386,6 @@ set(inkscape_SRC profile-manager.h proj_pt.h rdf.h - rect-context.h registrytool.h remove-last.h removeoverlap.h @@ -438,7 +396,6 @@ set(inkscape_SRC rubberband.h satisfied-guide-cns.h selcue.h - select-context.h selection-chemistry.h selection-describer.h selection.h @@ -454,10 +411,7 @@ set(inkscape_SRC snapped-line.h snapped-point.h snapper.h - spiral-context.h splivarot.h - spray-context.h - star-context.h streq.h strneq.h style-test.h @@ -468,13 +422,11 @@ set(inkscape_SRC syseq.h test-helpers.h text-chemistry.h - text-context.h text-editing.h text-tag-attributes.h tool-factory.h tools-switch.h transf_mat_3x4.h - tweak-context.h unclump.h undo-stack-observer.h unicoderange.h @@ -484,7 +436,6 @@ set(inkscape_SRC verbs-test.h verbs.h version.h - zoom-context.h ) if(WIN32) diff --git a/src/Makefile.am b/src/Makefile.am index 411e3cf86..2f91c3c24 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -134,6 +134,7 @@ include ui/Makefile_insert include ui/cache/Makefile_insert include ui/dialog/Makefile_insert include ui/tool/Makefile_insert +include ui/tools/Makefile_insert include ui/view/Makefile_insert include ui/widget/Makefile_insert include util/Makefile_insert diff --git a/src/Makefile_insert b/src/Makefile_insert index e719f8894..5e441560a 100644 --- a/src/Makefile_insert +++ b/src/Makefile_insert @@ -4,14 +4,12 @@ ink_common_sources += \ util/find-last-if.h \ util/longest-common-suffix.h \ remove-last.h \ - arc-context.cpp arc-context.h \ attributes.cpp attributes.h \ attribute-rel-svg.cpp attribute-rel-svg.h \ attribute-rel-css.cpp attribute-rel-css.h \ attribute-rel-util.cpp attribute-rel-util.h \ axis-manip.cpp axis-manip.h \ bad-uri-exception.h \ - box3d-context.cpp box3d-context.h \ box3d.cpp box3d.h \ box3d-side.cpp box3d-side.h \ brokenimage.xpm \ @@ -22,13 +20,11 @@ ink_common_sources += \ color-profile-cms-fns.h \ color-rgba.h \ colorspace.h \ - common-context.cpp common-context.h \ composite-undo-stack-observer.cpp \ composite-undo-stack-observer.h \ conditions.cpp conditions.h \ conn-avoid-ref.cpp conn-avoid-ref.h \ connection-pool.h \ - connector-context.cpp connector-context.h \ console-output-undo-observer.h console-output-undo-observer.cpp \ context-fns.cpp context-fns.h \ decimal-round.h \ @@ -43,16 +39,11 @@ ink_common_sources += \ document-undo.cpp document-undo.h \ doxygen-main.cpp \ draw-anchor.cpp draw-anchor.h \ - draw-context.cpp draw-context.h \ - dropper-context.cpp dropper-context.h \ - dyna-draw-context.cpp dyna-draw-context.h \ ege-adjustment-action.cpp ege-adjustment-action.h \ ege-color-prof-tracker.cpp ege-color-prof-tracker.h \ ege-output-action.cpp ege-output-action.h \ ege-select-one-action.cpp ege-select-one-action.h \ enums.h \ - eraser-context.cpp eraser-context.h \ - event-context.cpp event-context.h \ event-log.cpp event-log.h event.h \ extract-uri.cpp extract-uri.h \ factory.h \ @@ -60,7 +51,6 @@ ink_common_sources += \ fill-or-stroke.h \ filter-chemistry.cpp filter-chemistry.h \ filter-enums.cpp filter-enums.h \ - flood-context.cpp flood-context.h \ gc-alloc.h \ gc-anchored.h gc-anchored.cpp \ gc-core.h \ @@ -69,7 +59,6 @@ ink_common_sources += \ gc-managed.h \ gc-soft-ptr.h \ gradient-chemistry.cpp gradient-chemistry.h \ - gradient-context.cpp gradient-context.h \ gradient-drag.cpp gradient-drag.h \ graphlayout.cpp graphlayout.h \ guide-snapper.cpp guide-snapper.h \ @@ -96,13 +85,11 @@ ink_common_sources += \ layer-model.cpp layer-model.h \ line-geometry.cpp line-geometry.h \ line-snapper.cpp line-snapper.h \ - lpe-tool-context.cpp lpe-tool-context.h \ macros.h \ main-cmdlineact.cpp main-cmdlineact.h \ marker.cpp marker.h \ media.cpp media.h \ menus-skeleton.h \ - mesh-context.cpp mesh-context.h \ message-context.cpp message-context.h \ message.h \ message-stack.cpp message-stack.h \ @@ -113,8 +100,6 @@ ink_common_sources += \ object-snapper.cpp object-snapper.h \ path-chemistry.cpp path-chemistry.h \ path-prefix.h \ - pencil-context.cpp pencil-context.h \ - pen-context.cpp pen-context.h \ persp3d.cpp persp3d.h \ persp3d-reference.cpp persp3d-reference.h \ perspective-line.cpp perspective-line.h \ @@ -126,14 +111,12 @@ ink_common_sources += \ proj_pt.cpp proj_pt.h \ removeoverlap.cpp removeoverlap.h \ rdf.cpp rdf.h \ - rect-context.cpp rect-context.h \ resource-manager.cpp resource-manager.h \ require-config.h \ round.h \ rubberband.cpp rubberband.h \ satisfied-guide-cns.cpp satisfied-guide-cns.h \ selcue.cpp selcue.h \ - select-context.cpp select-context.h \ selection-chemistry.cpp selection-chemistry.h \ selection.cpp selection.h \ selection-describer.cpp selection-describer.h \ @@ -177,7 +160,6 @@ ink_common_sources += \ sp-guide-constraint.h \ sp-guide.cpp sp-guide.h \ sp-image.cpp sp-image.h \ - spiral-context.cpp spiral-context.h \ sp-item.cpp sp-item.h \ sp-item-group.cpp sp-item-group.h \ sp-item-notify-moveto.cpp sp-item-notify-moveto.h \ @@ -226,8 +208,6 @@ ink_common_sources += \ sp-tspan.cpp sp-tspan.h \ sp-use.cpp sp-use.h \ sp-use-reference.cpp sp-use-reference.h \ - spray-context.cpp spray-context.h \ - star-context.cpp star-context.h \ streq.h \ strneq.h \ style.cpp style.h \ @@ -236,13 +216,11 @@ ink_common_sources += \ svg-view-widget.cpp svg-view-widget.h \ syseq.h \ text-chemistry.cpp text-chemistry.h \ - text-context.cpp text-context.h \ text-editing.cpp text-editing.h \ text-tag-attributes.h \ tool-factory.h \ tools-switch.cpp tools-switch.h \ transf_mat_3x4.cpp transf_mat_3x4.h \ - tweak-context.h tweak-context.cpp \ unclump.cpp unclump.h \ undo-stack-observer.h \ unicoderange.cpp unicoderange.h \ @@ -250,9 +228,7 @@ ink_common_sources += \ uri-references.cpp uri-references.h \ vanishing-point.cpp vanishing-point.h \ verbs.cpp verbs.h \ - version.cpp version.h \ - zoom-context.cpp zoom-context.h \ - measure-context.cpp measure-context.h + version.cpp version.h # Additional dependencies diff --git a/src/arc-context.cpp b/src/arc-context.cpp deleted file mode 100644 index 4e99953f6..000000000 --- a/src/arc-context.cpp +++ /dev/null @@ -1,503 +0,0 @@ -/** - * @file - * Ellipse drawing context. - */ -/* Authors: - * Mitsuru Oka - * Lauris Kaplinski - * bulia byak - * Johan Engelen - * Abhishek Sharma - * Jon A. Cruz - * - * Copyright (C) 2000-2006 Authors - * Copyright (C) 2000-2001 Ximian, Inc. - * - * Released under GNU GPL, read the file 'COPYING' for more information - */ - -#ifdef HAVE_CONFIG_H -# include -#endif -#include - -#include "macros.h" -#include -#include "display/sp-canvas.h" -#include "sp-ellipse.h" -#include "document.h" -#include "document-undo.h" -#include "sp-namedview.h" -#include "selection.h" -#include "desktop-handles.h" -#include "snap.h" -#include "pixmaps/cursor-ellipse.xpm" -#include "xml/repr.h" -#include "xml/node-event-vector.h" -#include "preferences.h" -#include "message-context.h" -#include "desktop.h" -#include "desktop-style.h" -#include "context-fns.h" -#include "verbs.h" -#include "shape-editor.h" -#include "event-context.h" - -#include "arc-context.h" -#include "display/sp-canvas-item.h" - -using Inkscape::DocumentUndo; - -#include "tool-factory.h" - -namespace Inkscape { -namespace UI { -namespace Tools { - -namespace { - ToolBase* createArcContext() { - return new ArcTool(); - } - - bool arcContextRegistered = ToolFactory::instance().registerObject("/tools/shapes/arc", createArcContext); -} - -const std::string& ArcTool::getPrefsPath() { - return ArcTool::prefsPath; -} - -const std::string ArcTool::prefsPath = "/tools/shapes/arc"; - - -ArcTool::ArcTool() : ToolBase() { - this->cursor_shape = cursor_ellipse_xpm; - this->hot_x = 4; - this->hot_y = 4; - this->xp = 0; - this->yp = 0; - this->tolerance = 0; - this->within_tolerance = false; - this->item_to_select = NULL; - //this->tool_url = "/tools/shapes/arc"; - - this->arc = NULL; -} - -void ArcTool::finish() { - sp_canvas_item_ungrab(SP_CANVAS_ITEM(desktop->acetate), GDK_CURRENT_TIME); - this->finishItem(); - this->sel_changed_connection.disconnect(); - - ToolBase::finish(); -} - -ArcTool::~ArcTool() { - this->enableGrDrag(false); - - this->sel_changed_connection.disconnect(); - - delete this->shape_editor; - this->shape_editor = NULL; - - /* fixme: This is necessary because we do not grab */ - if (this->arc) { - this->finishItem(); - } -} - -/** - * Callback that processes the "changed" signal on the selection; - * destroys old and creates new knotholder. - */ -void ArcTool::selection_changed(Inkscape::Selection* selection) { - this->shape_editor->unset_item(SH_KNOTHOLDER); - this->shape_editor->set_item(selection->singleItem(), SH_KNOTHOLDER); -} - -void ArcTool::setup() { - ToolBase::setup(); - - Inkscape::Selection *selection = sp_desktop_selection(this->desktop); - - this->shape_editor = new ShapeEditor(this->desktop); - - SPItem *item = sp_desktop_selection(this->desktop)->singleItem(); - if (item) { - this->shape_editor->set_item(item, SH_KNOTHOLDER); - } - - this->sel_changed_connection.disconnect(); - this->sel_changed_connection = selection->connectChanged( - sigc::mem_fun(this, &ArcTool::selection_changed) - ); - - Inkscape::Preferences *prefs = Inkscape::Preferences::get(); - if (prefs->getBool("/tools/shapes/selcue")) { - this->enableSelectionCue(); - } - - if (prefs->getBool("/tools/shapes/gradientdrag")) { - this->enableGrDrag(); - } -} - -bool ArcTool::item_handler(SPItem* item, GdkEvent* event) { - gint ret = FALSE; - - switch (event->type) { - case GDK_BUTTON_PRESS: - if (event->button.button == 1 && !this->space_panning) { - Inkscape::setup_for_drag_start(desktop, this, event); - ret = TRUE; - } - break; - // motion and release are always on root (why?) - default: - break; - } - -// if ((SP_EVENT_CONTEXT_CLASS(sp_arc_context_parent_class))->item_handler) { -// ret = (SP_EVENT_CONTEXT_CLASS(sp_arc_context_parent_class))->item_handler(event_context, item, event); -// } - // CPPIFY: ret is overwritten... - ret = ToolBase::item_handler(item, event); - - return ret; -} - -bool ArcTool::root_handler(GdkEvent* event) { - static bool dragging; - - Inkscape::Selection *selection = sp_desktop_selection(desktop); - Inkscape::Preferences *prefs = Inkscape::Preferences::get(); - - this->tolerance = prefs->getIntLimited("/options/dragtolerance/value", 0, 0, 100); - - gint ret = FALSE; - - switch (event->type) { - case GDK_BUTTON_PRESS: - if (event->button.button == 1 && !this->space_panning) { - dragging = true; - - this->center = Inkscape::setup_for_drag_start(desktop, this, event); - - /* Snap center */ - SnapManager &m = desktop->namedview->snap_manager; - m.setup(desktop); - m.freeSnapReturnByRef(this->center, Inkscape::SNAPSOURCE_NODE_HANDLE); - - sp_canvas_item_grab(SP_CANVAS_ITEM(desktop->acetate), - GDK_KEY_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK, - NULL, event->button.time); - ret = TRUE; - m.unSetup(); - } - break; - case GDK_MOTION_NOTIFY: - if (dragging && (event->motion.state & GDK_BUTTON1_MASK) && !this->space_panning) { - if ( this->within_tolerance - && ( abs( (gint) event->motion.x - this->xp ) < this->tolerance ) - && ( abs( (gint) event->motion.y - this->yp ) < this->tolerance ) ) { - break; // do not drag if we're within tolerance from origin - } - // Once the user has moved farther than tolerance from the original location - // (indicating they intend to draw, not click), then always process the - // motion notify coordinates as given (no snapping back to origin) - this->within_tolerance = false; - - Geom::Point const motion_w(event->motion.x, event->motion.y); - Geom::Point motion_dt(desktop->w2d(motion_w)); - - this->drag(motion_dt, event->motion.state); - - gobble_motion_events(GDK_BUTTON1_MASK); - - ret = TRUE; - } else if (!sp_event_context_knot_mouseover(this)){ - SnapManager &m = desktop->namedview->snap_manager; - m.setup(desktop); - - Geom::Point const motion_w(event->motion.x, event->motion.y); - Geom::Point motion_dt(desktop->w2d(motion_w)); - m.preSnap(Inkscape::SnapCandidatePoint(motion_dt, Inkscape::SNAPSOURCE_NODE_HANDLE)); - m.unSetup(); - } - break; - case GDK_BUTTON_RELEASE: - this->xp = this->yp = 0; - if (event->button.button == 1 && !this->space_panning) { - dragging = false; - sp_event_context_discard_delayed_snap_event(this); - - if (!this->within_tolerance) { - // we've been dragging, finish the arc - this->finishItem(); - } else if (this->item_to_select) { - // no dragging, select clicked item if any - if (event->button.state & GDK_SHIFT_MASK) { - selection->toggle(this->item_to_select); - } else { - selection->set(this->item_to_select); - } - } else { - // click in an empty space - selection->clear(); - } - - this->xp = 0; - this->yp = 0; - this->item_to_select = NULL; - ret = TRUE; - } - sp_canvas_item_ungrab(SP_CANVAS_ITEM(desktop->acetate), event->button.time); - break; - - case GDK_KEY_PRESS: - switch (get_group0_keyval (&event->key)) { - case GDK_KEY_Alt_L: - case GDK_KEY_Alt_R: - case GDK_KEY_Control_L: - case GDK_KEY_Control_R: - case GDK_KEY_Shift_L: - case GDK_KEY_Shift_R: - case GDK_KEY_Meta_L: // Meta is when you press Shift+Alt (at least on my machine) - case GDK_KEY_Meta_R: - if (!dragging) { - sp_event_show_modifier_tip(this->defaultMessageContext(), event, - _("Ctrl: make circle or integer-ratio ellipse, snap arc/segment angle"), - _("Shift: draw around the starting point"), - NULL); - } - break; - - case GDK_KEY_Up: - case GDK_KEY_Down: - case GDK_KEY_KP_Up: - case GDK_KEY_KP_Down: - // prevent the zoom field from activation - if (!MOD__CTRL_ONLY(event)) - ret = TRUE; - break; - - case GDK_KEY_x: - case GDK_KEY_X: - if (MOD__ALT_ONLY(event)) { - desktop->setToolboxFocusTo ("altx-arc"); - ret = TRUE; - } - break; - - case GDK_KEY_Escape: - if (dragging) { - dragging = false; - sp_event_context_discard_delayed_snap_event(this); - // if drawing, cancel, otherwise pass it up for deselecting - this->cancel(); - ret = TRUE; - } - break; - - case GDK_KEY_space: - if (dragging) { - sp_canvas_item_ungrab(SP_CANVAS_ITEM(desktop->acetate), event->button.time); - dragging = false; - sp_event_context_discard_delayed_snap_event(this); - - if (!this->within_tolerance) { - // we've been dragging, finish the arc - this->finishItem(); - } - // do not return true, so that space would work switching to selector - } - break; - - case GDK_KEY_Delete: - case GDK_KEY_KP_Delete: - case GDK_KEY_BackSpace: - ret = this->deleteSelectedDrag(MOD__CTRL_ONLY(event)); - break; - - default: - break; - } - break; - - case GDK_KEY_RELEASE: - switch (event->key.keyval) { - case GDK_KEY_Alt_L: - case GDK_KEY_Alt_R: - case GDK_KEY_Control_L: - case GDK_KEY_Control_R: - case GDK_KEY_Shift_L: - case GDK_KEY_Shift_R: - case GDK_KEY_Meta_L: // Meta is when you press Shift+Alt - case GDK_KEY_Meta_R: - this->defaultMessageContext()->clear(); - break; - - default: - break; - } - break; - - default: - break; - } - - if (!ret) { - ret = ToolBase::root_handler(event); - } - - return ret; -} - -void ArcTool::drag(Geom::Point pt, guint state) { - if (!this->arc) { - if (Inkscape::have_viable_layer(desktop, this->message_context) == false) { - return; - } - - // Create object - Inkscape::XML::Document *xml_doc = desktop->doc()->getReprDoc(); - Inkscape::XML::Node *repr = xml_doc->createElement("svg:path"); - repr->setAttribute("sodipodi:type", "arc"); - - // Set style - sp_desktop_apply_style_tool(desktop, repr, "/tools/shapes/arc", false); - - this->arc = SP_GENERICELLIPSE(desktop->currentLayer()->appendChildRepr(repr)); - Inkscape::GC::release(repr); - this->arc->transform = SP_ITEM(desktop->currentLayer())->i2doc_affine().inverse(); - this->arc->updateRepr(); - - desktop->canvas->forceFullRedrawAfterInterruptions(5); - } - - bool ctrl_save = false; - - if ((state & GDK_MOD1_MASK) && (state & GDK_CONTROL_MASK) && !(state & GDK_SHIFT_MASK)) { - // if Alt is pressed without Shift in addition to Control, temporarily drop the CONTROL mask - // so that the ellipse is not constrained to integer ratios - ctrl_save = true; - state = state ^ GDK_CONTROL_MASK; - } - - Geom::Rect r = Inkscape::snap_rectangular_box(desktop, this->arc, pt, this->center, state); - - if (ctrl_save) { - state = state ^ GDK_CONTROL_MASK; - } - - Geom::Point dir = r.dimensions() / 2; - - if (state & GDK_MOD1_MASK) { - /* With Alt let the ellipse pass through the mouse pointer */ - Geom::Point c = r.midpoint(); - - if (!ctrl_save) { - if (fabs(dir[Geom::X]) > 1E-6 && fabs(dir[Geom::Y]) > 1E-6) { - Geom::Affine const i2d ( (this->arc)->i2dt_affine() ); - Geom::Point new_dir = pt * i2d - c; - new_dir[Geom::X] *= dir[Geom::Y] / dir[Geom::X]; - double lambda = new_dir.length() / dir[Geom::Y]; - r = Geom::Rect (c - lambda*dir, c + lambda*dir); - } - } else { - /* with Alt+Ctrl (without Shift) we generate a perfect circle - with diameter click point <--> mouse pointer */ - double l = dir.length(); - Geom::Point d (l, l); - r = Geom::Rect (c - d, c + d); - } - } - - this->arc->position_set( - r.midpoint()[Geom::X], r.midpoint()[Geom::Y], - r.dimensions()[Geom::X] / 2, r.dimensions()[Geom::Y] / 2); - - double rdimx = r.dimensions()[Geom::X]; - double rdimy = r.dimensions()[Geom::Y]; - - Inkscape::Util::Quantity rdimx_q = Inkscape::Util::Quantity(rdimx, "px"); - Inkscape::Util::Quantity rdimy_q = Inkscape::Util::Quantity(rdimy, "px"); - GString *xs = g_string_new(rdimx_q.string(desktop->namedview->doc_units).c_str()); - GString *ys = g_string_new(rdimy_q.string(desktop->namedview->doc_units).c_str()); - - if (state & GDK_CONTROL_MASK) { - int ratio_x, ratio_y; - - if (fabs (rdimx) > fabs (rdimy)) { - ratio_x = (int) rint (rdimx / rdimy); - ratio_y = 1; - } else { - ratio_x = 1; - ratio_y = (int) rint (rdimy / rdimx); - } - - this->message_context->setF(Inkscape::IMMEDIATE_MESSAGE, _("Ellipse: %s × %s (constrained to ratio %d:%d); with Shift to draw around the starting point"), xs->str, ys->str, ratio_x, ratio_y); - } else { - this->message_context->setF(Inkscape::IMMEDIATE_MESSAGE, _("Ellipse: %s × %s; with Ctrl to make square or integer-ratio ellipse; with Shift to draw around the starting point"), xs->str, ys->str); - } - - g_string_free(xs, FALSE); - g_string_free(ys, FALSE); -} - -void ArcTool::finishItem() { - this->message_context->clear(); - - if (this->arc != NULL) { - if (this->arc->rx.computed == 0 || this->arc->ry.computed == 0) { - this->cancel(); // Don't allow the creating of zero sized arc, for example when the start and and point snap to the snap grid point - return; - } - - this->arc->updateRepr(); - this->arc->doWriteTransform(this->arc->getRepr(), this->arc->transform, NULL, true); - - desktop->canvas->endForcedFullRedraws(); - - sp_desktop_selection(desktop)->set(this->arc); - - DocumentUndo::done(sp_desktop_document(desktop), SP_VERB_CONTEXT_ARC, _("Create ellipse")); - - this->arc = NULL; - } -} - -void ArcTool::cancel() { - sp_desktop_selection(desktop)->clear(); - sp_canvas_item_ungrab(SP_CANVAS_ITEM(desktop->acetate), 0); - - if (this->arc != NULL) { - this->arc->deleteObject(); - this->arc = NULL; - } - - this->within_tolerance = false; - this->xp = 0; - this->yp = 0; - this->item_to_select = NULL; - - desktop->canvas->endForcedFullRedraws(); - - DocumentUndo::cancel(sp_desktop_document(desktop)); -} - -} -} -} - - -/* - Local Variables: - mode:c++ - c-file-style:"stroustrup" - c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) - indent-tabs-mode:nil - fill-column:99 - End: -*/ -// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/arc-context.h b/src/arc-context.h deleted file mode 100644 index 7d99011d0..000000000 --- a/src/arc-context.h +++ /dev/null @@ -1,76 +0,0 @@ -#ifndef SEEN_ARC_CONTEXT_H -#define SEEN_ARC_CONTEXT_H - -/* - * Ellipse drawing context - * - * Authors: - * Mitsuru Oka - * Lauris Kaplinski - * - * Copyright (C) 2000-2002 Lauris Kaplinski - * Copyright (C) 2000-2001 Ximian, Inc. - * Copyright (C) 2002 Mitsuru Oka - * - * Released under GNU GPL, read the file 'COPYING' for more information - */ - -#include -#include - -#include <2geom/point.h> -#include "event-context.h" - -#include "sp-ellipse.h" - -#define SP_ARC_CONTEXT(obj) (dynamic_cast((Inkscape::UI::Tools::ToolBase*)obj)) -#define SP_IS_ARC_CONTEXT(obj) (dynamic_cast(const Inkscape::UI::Tools::ToolBase*(obj)) != NULL) - -namespace Inkscape { -namespace UI { -namespace Tools { - -class ArcTool : public ToolBase { -public: - ArcTool(); - virtual ~ArcTool(); - - static const std::string prefsPath; - - virtual void setup(); - virtual void finish(); - virtual bool root_handler(GdkEvent* event); - virtual bool item_handler(SPItem* item, GdkEvent* event); - - virtual const std::string& getPrefsPath(); - -private: - SPGenericEllipse *arc; - - Geom::Point center; - - sigc::connection sel_changed_connection; - - void selection_changed(Inkscape::Selection* selection); - - void drag(Geom::Point pt, guint state); - void finishItem(); - void cancel(); -}; - -} -} -} - -#endif /* !SEEN_ARC_CONTEXT_H */ - -/* - Local Variables: - mode:c++ - c-file-style:"stroustrup" - c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) - indent-tabs-mode:nil - fill-column:99 - End: -*/ -// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 : diff --git a/src/box3d-context.cpp b/src/box3d-context.cpp deleted file mode 100644 index a441d45eb..000000000 --- a/src/box3d-context.cpp +++ /dev/null @@ -1,637 +0,0 @@ -/* - * 3D box drawing context - * - * Author: - * Lauris Kaplinski - * bulia byak - * Jon A. Cruz - * Abhishek Sharma - * - * Copyright (C) 2007 Maximilian Albert - * Copyright (C) 2006 Johan Engelen - * Copyright (C) 2000-2005 authors - * Copyright (C) 2000-2001 Ximian, Inc. - * - * Released under GNU GPL, read the file 'COPYING' for more information - */ - -#include "config.h" - -#include - -#include "macros.h" -#include "display/sp-canvas.h" -#include "document.h" -#include "document-undo.h" -#include "sp-namedview.h" -#include "selection.h" -#include "selection-chemistry.h" -#include "desktop-handles.h" -#include "snap.h" -#include "display/curve.h" -#include "display/sp-canvas-item.h" -#include "desktop.h" -#include "message-context.h" -#include "pixmaps/cursor-3dbox.xpm" -#include "box3d.h" -#include "box3d-context.h" -#include -#include "xml/repr.h" -#include "xml/node-event-vector.h" -#include "preferences.h" -#include "context-fns.h" -#include "desktop-style.h" -#include "transf_mat_3x4.h" -#include "perspective-line.h" -#include "persp3d.h" -#include "box3d-side.h" -#include "document-private.h" -#include "line-geometry.h" -#include "shape-editor.h" -#include "verbs.h" - -using Inkscape::DocumentUndo; - -#include "tool-factory.h" - -namespace Inkscape { -namespace UI { -namespace Tools { - -namespace { - ToolBase* createBox3dTool() { - return new Box3dTool(); - } - - bool Box3dToolRegistered = ToolFactory::instance().registerObject("/tools/shapes/3dbox", createBox3dTool); -} - -const std::string& Box3dTool::getPrefsPath() { - return Box3dTool::prefsPath; -} - -const std::string Box3dTool::prefsPath = "/tools/shapes/3dbox"; - -Box3dTool::Box3dTool() : ToolBase() { - this->cursor_shape = cursor_3dbox_xpm; - this->hot_x = 4; - this->hot_y = 4; - this->xp = 0; - this->yp = 0; - this->tolerance = 0; - this->within_tolerance = false; - this->item_to_select = NULL; - - this->box3d = NULL; - - this->ctrl_dragged = false; - this->extruded = false; - - this->_vpdrag = NULL; -} - -void Box3dTool::finish() { - sp_canvas_item_ungrab(SP_CANVAS_ITEM(desktop->acetate), GDK_CURRENT_TIME); - this->finishItem(); - this->sel_changed_connection.disconnect(); - - ToolBase::finish(); -} - - -Box3dTool::~Box3dTool() { - this->enableGrDrag(false); - - delete (this->_vpdrag); - this->_vpdrag = NULL; - - this->sel_changed_connection.disconnect(); - - delete this->shape_editor; - this->shape_editor = NULL; - - /* fixme: This is necessary because we do not grab */ - if (this->box3d) { - this->finishItem(); - } -} - -/** - * Callback that processes the "changed" signal on the selection; - * destroys old and creates new knotholder. - */ -void Box3dTool::selection_changed(Inkscape::Selection* selection) { - this->shape_editor->unset_item(SH_KNOTHOLDER); - this->shape_editor->set_item(selection->singleItem(), SH_KNOTHOLDER); - - if (selection->perspList().size() == 1) { - // selecting a single box changes the current perspective - this->desktop->doc()->setCurrentPersp3D(selection->perspList().front()); - } -} - -/* Create a default perspective in document defs if none is present (which can happen, among other - * circumstances, after 'vacuum defs' or when a pre-0.46 file is opened). - */ -static void sp_box3d_context_ensure_persp_in_defs(SPDocument *document) { - SPDefs *defs = document->getDefs(); - - bool has_persp = false; - for ( SPObject *child = defs->firstChild(); child; child = child->getNext() ) { - if (SP_IS_PERSP3D(child)) { - has_persp = true; - break; - } - } - - if (!has_persp) { - document->setCurrentPersp3D(persp3d_create_xml_element (document)); - } -} - -void Box3dTool::setup() { - ToolBase::setup(); - - this->shape_editor = new ShapeEditor(this->desktop); - - SPItem *item = sp_desktop_selection(this->desktop)->singleItem(); - if (item) { - this->shape_editor->set_item(item, SH_KNOTHOLDER); - } - - this->sel_changed_connection.disconnect(); - this->sel_changed_connection = sp_desktop_selection(this->desktop)->connectChanged( - sigc::mem_fun(this, &Box3dTool::selection_changed) - ); - - this->_vpdrag = new Box3D::VPDrag(sp_desktop_document(this->desktop)); - - Inkscape::Preferences *prefs = Inkscape::Preferences::get(); - - if (prefs->getBool("/tools/shapes/selcue")) { - this->enableSelectionCue(); - } - - if (prefs->getBool("/tools/shapes/gradientdrag")) { - this->enableGrDrag(); - } -} - -bool Box3dTool::item_handler(SPItem* item, GdkEvent* event) { - gint ret = FALSE; - - switch (event->type) { - case GDK_BUTTON_PRESS: - if ( event->button.button == 1 && !this->space_panning) { - Inkscape::setup_for_drag_start(desktop, this, event); - ret = TRUE; - } - break; - // motion and release are always on root (why?) - default: - break; - } - -// if (((ToolBaseClass *) sp_box3d_context_parent_class)->item_handler) { -// ret = ((ToolBaseClass *) sp_box3d_context_parent_class)->item_handler(event_context, item, event); -// } - // CPPIFY: ret is always overwritten... - ret = ToolBase::item_handler(item, event); - - return ret; -} - -bool Box3dTool::root_handler(GdkEvent* event) { - static bool dragging; - - SPDocument *document = sp_desktop_document (desktop); - Inkscape::Selection *selection = sp_desktop_selection (desktop); - Inkscape::Preferences *prefs = Inkscape::Preferences::get(); - int const snaps = prefs->getInt("/options/rotationsnapsperpi/value", 12); - - Persp3D *cur_persp = document->getCurrentPersp3D(); - - this->tolerance = prefs->getIntLimited("/options/dragtolerance/value", 0, 0, 100); - - gint ret = FALSE; - switch (event->type) { - case GDK_BUTTON_PRESS: - if ( event->button.button == 1 && !this->space_panning) { - Geom::Point const button_w(event->button.x, event->button.y); - Geom::Point button_dt(desktop->w2d(button_w)); - - // save drag origin - this->xp = (gint) button_w[Geom::X]; - this->yp = (gint) button_w[Geom::Y]; - this->within_tolerance = true; - - // remember clicked box3d, *not* disregarding groups (since a 3D box is a group), honoring Alt - this->item_to_select = sp_event_context_find_item (desktop, button_w, event->button.state & GDK_MOD1_MASK, event->button.state & GDK_CONTROL_MASK); - - dragging = true; - - SnapManager &m = desktop->namedview->snap_manager; - m.setup(desktop, true, this->box3d); - m.freeSnapReturnByRef(button_dt, Inkscape::SNAPSOURCE_NODE_HANDLE); - m.unSetup(); - this->center = button_dt; - - this->drag_origin = button_dt; - this->drag_ptB = button_dt; - this->drag_ptC = button_dt; - - // This can happen after saving when the last remaining perspective was purged and must be recreated. - if (!cur_persp) { - sp_box3d_context_ensure_persp_in_defs(document); - cur_persp = document->getCurrentPersp3D(); - } - - /* Projective preimages of clicked point under current perspective */ - this->drag_origin_proj = cur_persp->perspective_impl->tmat.preimage (button_dt, 0, Proj::Z); - this->drag_ptB_proj = this->drag_origin_proj; - this->drag_ptC_proj = this->drag_origin_proj; - this->drag_ptC_proj.normalize(); - this->drag_ptC_proj[Proj::Z] = 0.25; - - sp_canvas_item_grab(SP_CANVAS_ITEM(desktop->acetate), - ( GDK_KEY_PRESS_MASK | - GDK_BUTTON_RELEASE_MASK | - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | - GDK_BUTTON_PRESS_MASK ), - NULL, event->button.time); - ret = TRUE; - } - break; - - case GDK_MOTION_NOTIFY: - if (dragging && ( event->motion.state & GDK_BUTTON1_MASK ) && !this->space_panning) { - if ( this->within_tolerance - && ( abs( (gint) event->motion.x - this->xp ) < this->tolerance ) - && ( abs( (gint) event->motion.y - this->yp ) < this->tolerance ) ) { - break; // do not drag if we're within tolerance from origin - } - // Once the user has moved farther than tolerance from the original location - // (indicating they intend to draw, not click), then always process the - // motion notify coordinates as given (no snapping back to origin) - this->within_tolerance = false; - - Geom::Point const motion_w(event->motion.x, - event->motion.y); - Geom::Point motion_dt(desktop->w2d(motion_w)); - - SnapManager &m = desktop->namedview->snap_manager; - m.setup(desktop, true, this->box3d); - m.freeSnapReturnByRef(motion_dt, Inkscape::SNAPSOURCE_NODE_HANDLE); - this->ctrl_dragged = event->motion.state & GDK_CONTROL_MASK; - - if ((event->motion.state & GDK_SHIFT_MASK) && !this->extruded && this->box3d) { - // once shift is pressed, set this->extruded - this->extruded = true; - } - - if (!this->extruded) { - this->drag_ptB = motion_dt; - this->drag_ptC = motion_dt; - - this->drag_ptB_proj = cur_persp->perspective_impl->tmat.preimage (motion_dt, 0, Proj::Z); - this->drag_ptC_proj = this->drag_ptB_proj; - this->drag_ptC_proj.normalize(); - this->drag_ptC_proj[Proj::Z] = 0.25; - } else { - // Without Ctrl, motion of the extruded corner is constrained to the - // perspective line from drag_ptB to vanishing point Y. - if (!this->ctrl_dragged) { - /* snapping */ - Box3D::PerspectiveLine pline (this->drag_ptB, Proj::Z, document->getCurrentPersp3D()); - this->drag_ptC = pline.closest_to (motion_dt); - - this->drag_ptB_proj.normalize(); - this->drag_ptC_proj = cur_persp->perspective_impl->tmat.preimage (this->drag_ptC, this->drag_ptB_proj[Proj::X], Proj::X); - } else { - this->drag_ptC = motion_dt; - - this->drag_ptB_proj.normalize(); - this->drag_ptC_proj = cur_persp->perspective_impl->tmat.preimage (motion_dt, this->drag_ptB_proj[Proj::X], Proj::X); - } - - m.freeSnapReturnByRef(this->drag_ptC, Inkscape::SNAPSOURCE_NODE_HANDLE); - } - - m.unSetup(); - - this->drag(event->motion.state); - - ret = TRUE; - } else if (!sp_event_context_knot_mouseover(this)) { - SnapManager &m = desktop->namedview->snap_manager; - m.setup(desktop); - - Geom::Point const motion_w(event->motion.x, event->motion.y); - Geom::Point motion_dt(desktop->w2d(motion_w)); - m.preSnap(Inkscape::SnapCandidatePoint(motion_dt, Inkscape::SNAPSOURCE_NODE_HANDLE)); - m.unSetup(); - } - break; - - case GDK_BUTTON_RELEASE: - this->xp = this->yp = 0; - - if (event->button.button == 1 && !this->space_panning) { - dragging = false; - sp_event_context_discard_delayed_snap_event(this); - - if (!this->within_tolerance) { - // we've been dragging, finish the box - this->finishItem(); - } else if (this->item_to_select) { - // no dragging, select clicked box3d if any - if (event->button.state & GDK_SHIFT_MASK) { - selection->toggle(this->item_to_select); - } else { - selection->set(this->item_to_select); - } - } else { - // click in an empty space - selection->clear(); - } - - this->item_to_select = NULL; - ret = TRUE; - sp_canvas_item_ungrab(SP_CANVAS_ITEM(desktop->acetate), - event->button.time); - } - break; - - case GDK_KEY_PRESS: - switch (get_group0_keyval (&event->key)) { - case GDK_KEY_Up: - case GDK_KEY_Down: - case GDK_KEY_KP_Up: - case GDK_KEY_KP_Down: - // prevent the zoom field from activation - if (!MOD__CTRL_ONLY(event)) - ret = TRUE; - break; - - case GDK_KEY_bracketright: - persp3d_rotate_VP (document->getCurrentPersp3D(), Proj::X, -180/snaps, MOD__ALT(event)); - DocumentUndo::done(document, SP_VERB_CONTEXT_3DBOX, - _("Change perspective (angle of PLs)")); - ret = true; - break; - - case GDK_KEY_bracketleft: - persp3d_rotate_VP (document->getCurrentPersp3D(), Proj::X, 180/snaps, MOD__ALT(event)); - DocumentUndo::done(document, SP_VERB_CONTEXT_3DBOX, - _("Change perspective (angle of PLs)")); - ret = true; - break; - - case GDK_KEY_parenright: - persp3d_rotate_VP (document->getCurrentPersp3D(), Proj::Y, -180/snaps, MOD__ALT(event)); - DocumentUndo::done(document, SP_VERB_CONTEXT_3DBOX, - _("Change perspective (angle of PLs)")); - ret = true; - break; - - case GDK_KEY_parenleft: - persp3d_rotate_VP (document->getCurrentPersp3D(), Proj::Y, 180/snaps, MOD__ALT(event)); - DocumentUndo::done(document, SP_VERB_CONTEXT_3DBOX, - _("Change perspective (angle of PLs)")); - ret = true; - break; - - case GDK_KEY_braceright: - persp3d_rotate_VP (document->getCurrentPersp3D(), Proj::Z, -180/snaps, MOD__ALT(event)); - DocumentUndo::done(document, SP_VERB_CONTEXT_3DBOX, - _("Change perspective (angle of PLs)")); - ret = true; - break; - - case GDK_KEY_braceleft: - persp3d_rotate_VP (document->getCurrentPersp3D(), Proj::Z, 180/snaps, MOD__ALT(event)); - DocumentUndo::done(document, SP_VERB_CONTEXT_3DBOX, - _("Change perspective (angle of PLs)")); - ret = true; - break; - - /* TODO: what is this??? - case GDK_O: - if (MOD__CTRL(event) && MOD__SHIFT(event)) { - Box3D::create_canvas_point(persp3d_get_VP(document()->getCurrentPersp3D(), Proj::W).affine(), - 6, 0xff00ff00); - } - ret = true; - break; - */ - - case GDK_KEY_g: - case GDK_KEY_G: - if (MOD__SHIFT_ONLY(event)) { - sp_selection_to_guides(desktop); - ret = true; - } - break; - - case GDK_KEY_p: - case GDK_KEY_P: - if (MOD__SHIFT_ONLY(event)) { - if (document->getCurrentPersp3D()) { - persp3d_print_debugging_info (document->getCurrentPersp3D()); - } - ret = true; - } - break; - - case GDK_KEY_x: - case GDK_KEY_X: - if (MOD__ALT_ONLY(event)) { - desktop->setToolboxFocusTo ("altx-box3d"); - ret = TRUE; - } - if (MOD__SHIFT_ONLY(event)) { - persp3d_toggle_VPs(selection->perspList(), Proj::X); - this->_vpdrag->updateLines(); // FIXME: Shouldn't this be done automatically? - ret = true; - } - break; - - case GDK_KEY_y: - case GDK_KEY_Y: - if (MOD__SHIFT_ONLY(event)) { - persp3d_toggle_VPs(selection->perspList(), Proj::Y); - this->_vpdrag->updateLines(); // FIXME: Shouldn't this be done automatically? - ret = true; - } - break; - - case GDK_KEY_z: - case GDK_KEY_Z: - if (MOD__SHIFT_ONLY(event)) { - persp3d_toggle_VPs(selection->perspList(), Proj::Z); - this->_vpdrag->updateLines(); // FIXME: Shouldn't this be done automatically? - ret = true; - } - break; - - case GDK_KEY_Escape: - sp_desktop_selection(desktop)->clear(); - //TODO: make dragging escapable by Esc - break; - - case GDK_KEY_space: - if (dragging) { - sp_canvas_item_ungrab(SP_CANVAS_ITEM(desktop->acetate), - event->button.time); - dragging = false; - sp_event_context_discard_delayed_snap_event(this); - if (!this->within_tolerance) { - // we've been dragging, finish the box - this->finishItem(); - } - // do not return true, so that space would work switching to selector - } - break; - - case GDK_KEY_Delete: - case GDK_KEY_KP_Delete: - case GDK_KEY_BackSpace: - ret = this->deleteSelectedDrag(MOD__CTRL_ONLY(event)); - break; - - default: - break; - } - break; - - default: - break; - } - - if (!ret) { - ret = ToolBase::root_handler(event); - } - - return ret; -} - -void Box3dTool::drag(guint state) { - if (!this->box3d) { - if (Inkscape::have_viable_layer(desktop, this->message_context) == false) { - return; - } - - // Create object - SPBox3D *box3d = SPBox3D::createBox3D((SPItem*)desktop->currentLayer()); - - // Set style - desktop->applyCurrentOrToolStyle(box3d, "/tools/shapes/3dbox", false); - - this->box3d = box3d; - - // TODO: Incorporate this in box3d-side.cpp! - for (int i = 0; i < 6; ++i) { - Box3DSide *side = Box3DSide::createBox3DSide(box3d); - - guint desc = Box3D::int_to_face(i); - - Box3D::Axis plane = (Box3D::Axis) (desc & 0x7); - plane = (Box3D::is_plane(plane) ? plane : Box3D::orth_plane_or_axis(plane)); - side->dir1 = Box3D::extract_first_axis_direction(plane); - side->dir2 = Box3D::extract_second_axis_direction(plane); - side->front_or_rear = (Box3D::FrontOrRear) (desc & 0x8); - - // Set style - Inkscape::Preferences *prefs = Inkscape::Preferences::get(); - - Glib::ustring descr = "/desktop/"; - descr += box3d_side_axes_string(side); - descr += "/style"; - - Glib::ustring cur_style = prefs->getString(descr); - - bool use_current = prefs->getBool("/tools/shapes/3dbox/usecurrent", false); - - if (use_current && !cur_style.empty()) { - // use last used style - side->setAttribute("style", cur_style.data()); - } else { - // use default style - GString *pstring = g_string_new(""); - g_string_printf (pstring, "/tools/shapes/3dbox/%s", box3d_side_axes_string(side)); - desktop->applyCurrentOrToolStyle (side, pstring->str, false); - } - - side->updateRepr(); // calls box3d_side_write() and updates, e.g., the axes string description - } - - box3d_set_z_orders(this->box3d); - this->box3d->updateRepr(); - - // TODO: It would be nice to show the VPs during dragging, but since there is no selection - // at this point (only after finishing the box), we must do this "manually" - /* this._vpdrag->updateDraggers(); */ - - desktop->canvas->forceFullRedrawAfterInterruptions(5); - } - - g_assert(this->box3d); - - this->box3d->orig_corner0 = this->drag_origin_proj; - this->box3d->orig_corner7 = this->drag_ptC_proj; - - box3d_check_for_swapped_coords(this->box3d); - - /* we need to call this from here (instead of from box3d_position_set(), for example) - because z-order setting must not interfere with display updates during undo/redo */ - box3d_set_z_orders (this->box3d); - - box3d_position_set(this->box3d); - - // status text - this->message_context->setF(Inkscape::NORMAL_MESSAGE, "%s", _("3D Box; with Shift to extrude along the Z axis")); -} - -void Box3dTool::finishItem() { - this->message_context->clear(); - this->ctrl_dragged = false; - this->extruded = false; - - if (this->box3d != NULL) { - SPDocument *doc = sp_desktop_document(this->desktop); - - if (!doc || !doc->getCurrentPersp3D()) { - return; - } - - this->box3d->orig_corner0 = this->drag_origin_proj; - this->box3d->orig_corner7 = this->drag_ptC_proj; - - this->box3d->updateRepr(); - - box3d_relabel_corners(this->box3d); - - desktop->canvas->endForcedFullRedraws(); - - sp_desktop_selection(desktop)->set(this->box3d); - DocumentUndo::done(sp_desktop_document(desktop), SP_VERB_CONTEXT_3DBOX, - _("Create 3D box")); - - this->box3d = NULL; - } -} - -} -} -} - -/* - Local Variables: - mode:c++ - c-file-style:"stroustrup" - c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) - indent-tabs-mode:nil - fill-column:99 - End: -*/ -// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/box3d-context.h b/src/box3d-context.h deleted file mode 100644 index 4f8032eeb..000000000 --- a/src/box3d-context.h +++ /dev/null @@ -1,95 +0,0 @@ -#ifndef __SP_BOX3D_CONTEXT_H__ -#define __SP_BOX3D_CONTEXT_H__ - -/* - * 3D box drawing context - * - * Author: - * Lauris Kaplinski - * - * Copyright (C) 2000 Lauris Kaplinski - * Copyright (C) 2000-2001 Ximian, Inc. - * Copyright (C) 2002 Lauris Kaplinski - * Copyright (C) 2007 Maximilian Albert - * - * Released under GNU GPL - */ - -#include -#include -#include "event-context.h" -#include "proj_pt.h" -#include "vanishing-point.h" - -#include "box3d.h" - -#define SP_BOX3D_CONTEXT(obj) (dynamic_cast((Inkscape::UI::Tools::ToolBase*)obj)) -#define SP_IS_BOX3D_CONTEXT(obj) (dynamic_cast((const Inkscape::UI::Tools::ToolBase*)obj) != NULL) - -namespace Inkscape { -namespace UI { -namespace Tools { - -class Box3dTool : public ToolBase { -public: - Box3dTool(); - virtual ~Box3dTool(); - - Box3D::VPDrag * _vpdrag; - - static const std::string prefsPath; - - virtual void setup(); - virtual void finish(); - virtual bool root_handler(GdkEvent* event); - virtual bool item_handler(SPItem* item, GdkEvent* event); - - virtual const std::string& getPrefsPath(); - -private: - SPBox3D* box3d; - Geom::Point center; - - /** - * save three corners while dragging: - * 1) the starting point (already done by the event_context) - * 2) drag_ptB --> the opposite corner of the front face (before pressing shift) - * 3) drag_ptC --> the "extruded corner" (which coincides with the mouse pointer location - * if we are ctrl-dragging but is constrained to the perspective line from drag_ptC - * to the vanishing point Y otherwise) - */ - Geom::Point drag_origin; - Geom::Point drag_ptB; - Geom::Point drag_ptC; - - Proj::Pt3 drag_origin_proj; - Proj::Pt3 drag_ptB_proj; - Proj::Pt3 drag_ptC_proj; - - bool ctrl_dragged; /* whether we are ctrl-dragging */ - bool extruded; /* whether shift-dragging already occured (i.e. the box is already extruded) */ - - sigc::connection sel_changed_connection; - - void selection_changed(Inkscape::Selection* selection); - - void drag(guint state); - void finishItem(); -}; - -} -} -} - -#endif /* __SP_BOX3D_CONTEXT_H__ */ - -/* - Local Variables: - mode:c++ - c-file-style:"stroustrup" - c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) - indent-tabs-mode:nil - fill-column:99 - End: -*/ -// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/box3d-side.cpp b/src/box3d-side.cpp index 040b031c6..7b5ddc00e 100644 --- a/src/box3d-side.cpp +++ b/src/box3d-side.cpp @@ -19,7 +19,7 @@ #include "attributes.h" #include "inkscape.h" #include "persp3d.h" -#include "box3d-context.h" +#include "ui/tools/box3d-tool.h" #include "preferences.h" #include "desktop-style.h" #include "box3d.h" diff --git a/src/box3d.cpp b/src/box3d.cpp index 32442746d..13a8d0e3e 100644 --- a/src/box3d.cpp +++ b/src/box3d.cpp @@ -21,7 +21,7 @@ #include "box3d.h" #include "box3d-side.h" -#include "box3d-context.h" +#include "ui/tools/box3d-tool.h" #include "proj_pt.h" #include "transf_mat_3x4.h" #include "perspective-line.h" diff --git a/src/common-context.cpp b/src/common-context.cpp deleted file mode 100644 index e896d0829..000000000 --- a/src/common-context.cpp +++ /dev/null @@ -1,162 +0,0 @@ - -#include "common-context.h" - -#include - -#include "config.h" - -#include "message-context.h" -#include "streq.h" -#include "preferences.h" -#include "display/sp-canvas-item.h" -#include "desktop.h" - -#define MIN_PRESSURE 0.0 -#define MAX_PRESSURE 1.0 -#define DEFAULT_PRESSURE 1.0 - -#define DRAG_MIN 0.0 -#define DRAG_DEFAULT 1.0 -#define DRAG_MAX 1.0 - -namespace Inkscape { -namespace UI { -namespace Tools { - -SPCommonContext::SPCommonContext() : - ToolBase(), - accumulated(NULL), - segments(NULL), - currentshape(NULL), - currentcurve(NULL), - cal1(NULL), - cal2(NULL), - point1(), - point2(), - repr(NULL), - cur(0,0), - vel(0,0), - vel_max(0), - acc(0,0), - ang(0,0), - last(0,0), - del(0,0), - pressure(DEFAULT_PRESSURE), - xtilt(0), - ytilt(0), - dragging(FALSE), - usepressure(FALSE), - usetilt(FALSE), - mass(0.3), - drag(DRAG_DEFAULT), - angle(30.0), - width(0.2), - vel_thin(0.1), - flatness(0.9), - tremor(0), - cap_rounding(0), - is_drawing(false), - abs_width(false) -{ -} - -SPCommonContext::~SPCommonContext() { - if (this->accumulated) { - this->accumulated = this->accumulated->unref(); - this->accumulated = 0; - } - - while (this->segments) { - sp_canvas_item_destroy(SP_CANVAS_ITEM(this->segments->data)); - this->segments = g_slist_remove(this->segments, this->segments->data); - } - - if (this->currentcurve) { - this->currentcurve = this->currentcurve->unref(); - this->currentcurve = 0; - } - - if (this->cal1) { - this->cal1 = this->cal1->unref(); - this->cal1 = 0; - } - - if (this->cal2) { - this->cal2 = this->cal2->unref(); - this->cal2 = 0; - } - - if (this->currentshape) { - sp_canvas_item_destroy(this->currentshape); - this->currentshape = 0; - } -} - -void SPCommonContext::set(const Inkscape::Preferences::Entry& value) { - Glib::ustring path = value.getEntryName(); - - // ignore preset modifications - static Glib::ustring const presets_path = this->pref_observer->observed_path + "/preset"; - Glib::ustring const &full_path = value.getPath(); - - if (full_path.compare(0, presets_path.size(), presets_path) == 0) { - return; - } - - if (path == "mass") { - this->mass = 0.01 * CLAMP(value.getInt(10), 0, 100); - } else if (path == "wiggle") { - this->drag = CLAMP((1 - 0.01 * value.getInt()), DRAG_MIN, DRAG_MAX); // drag is inverse to wiggle - } else if (path == "angle") { - this->angle = CLAMP(value.getDouble(), -90, 90); - } else if (path == "width") { - this->width = 0.01 * CLAMP(value.getInt(10), 1, 100); - } else if (path == "thinning") { - this->vel_thin = 0.01 * CLAMP(value.getInt(10), -100, 100); - } else if (path == "tremor") { - this->tremor = 0.01 * CLAMP(value.getInt(), 0, 100); - } else if (path == "flatness") { - this->flatness = 0.01 * CLAMP(value.getInt(), 0, 100); - } else if (path == "usepressure") { - this->usepressure = value.getBool(); - } else if (path == "usetilt") { - this->usetilt = value.getBool(); - } else if (path == "abs_width") { - this->abs_width = value.getBool(); - } else if (path == "cap_rounding") { - this->cap_rounding = value.getDouble(); - } -} - -/* Get normalized point */ -Geom::Point SPCommonContext::getNormalizedPoint(Geom::Point v) const { - Geom::Rect drect = this->desktop->get_display_area(); - - double const max = MAX ( drect.dimensions()[Geom::X], drect.dimensions()[Geom::Y] ); - - return Geom::Point(( v[Geom::X] - drect.min()[Geom::X] ) / max, ( v[Geom::Y] - drect.min()[Geom::Y] ) / max); -} - -/* Get view point */ -Geom::Point SPCommonContext::getViewPoint(Geom::Point n) const { - Geom::Rect drect = this->desktop->get_display_area(); - - double const max = MAX ( drect.dimensions()[Geom::X], drect.dimensions()[Geom::Y] ); - - return Geom::Point(n[Geom::X] * max + drect.min()[Geom::X], n[Geom::Y] * max + drect.min()[Geom::Y]); -} - -} -} -} - -/* - Local Variables: - mode:c++ - c-file-style:"stroustrup" - c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) - indent-tabs-mode:nil - fill-column:99 - End: -*/ -// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/common-context.h b/src/common-context.h deleted file mode 100644 index 658d2ef17..000000000 --- a/src/common-context.h +++ /dev/null @@ -1,123 +0,0 @@ -#ifndef COMMON_CONTEXT_H_SEEN -#define COMMON_CONTEXT_H_SEEN - -/* - * Common drawing mode - * - * Authors: - * Mitsuru Oka - * Lauris Kaplinski - * - * The original dynadraw code: - * Paul Haeberli - * - * Copyright (C) 1998 The Free Software Foundation - * Copyright (C) 1999-2002 authors - * Copyright (C) 2001-2002 Ximian, Inc. - * Copyright (C) 2008 Jon A. Cruz - * - * Released under GNU GPL, read the file 'COPYING' for more information - */ - -#include "event-context.h" -#include "display/curve.h" -#include <2geom/point.h> - -#define SAMPLING_SIZE 8 /* fixme: ?? */ - -namespace Inkscape { -namespace UI { -namespace Tools { - -class SPCommonContext : public ToolBase { -public: - SPCommonContext(); - virtual ~SPCommonContext(); - - virtual void set(const Inkscape::Preferences::Entry& val); - -protected: - /** accumulated shape which ultimately goes in svg:path */ - SPCurve *accumulated; - - /** canvas items for "comitted" segments */ - GSList *segments; - - /** canvas item for red "leading" segment */ - SPCanvasItem *currentshape; - /** shape of red "leading" segment */ - SPCurve *currentcurve; - - /** left edge of the stroke; combined to get accumulated */ - SPCurve *cal1; - - /** right edge of the stroke; combined to get accumulated */ - SPCurve *cal2; - - /** left edge points for this segment */ - Geom::Point point1[SAMPLING_SIZE]; - - /** right edge points for this segment */ - Geom::Point point2[SAMPLING_SIZE]; - - /** number of edge points for this segment */ - gint npoints; - - /* repr */ - Inkscape::XML::Node *repr; - - /* common */ - Geom::Point cur; - Geom::Point vel; - double vel_max; - Geom::Point acc; - Geom::Point ang; - Geom::Point last; - Geom::Point del; - - /* extended input data */ - gdouble pressure; - gdouble xtilt; - gdouble ytilt; - - /* attributes */ - guint dragging : 1; /* mouse state: mouse is dragging */ - guint usepressure : 1; - guint usetilt : 1; - double mass, drag; - double angle; - double width; - - double vel_thin; - double flatness; - double tremor; - double cap_rounding; - - //Inkscape::MessageContext *_message_context; - - bool is_drawing; - - /** uses absolute width independent of zoom */ - bool abs_width; - - Geom::Point getViewPoint(Geom::Point n) const; - Geom::Point getNormalizedPoint(Geom::Point v) const; -}; - -} -} -} - -#endif // COMMON_CONTEXT_H_SEEN - -/* - Local Variables: - mode:c++ - c-file-style:"stroustrup" - c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) - indent-tabs-mode:nil - fill-column:99 - End: -*/ -// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : - diff --git a/src/connector-context.cpp b/src/connector-context.cpp deleted file mode 100644 index 6af223b54..000000000 --- a/src/connector-context.cpp +++ /dev/null @@ -1,1496 +0,0 @@ -/* - * Connector creation tool - * - * Authors: - * Michael Wybrow - * Abhishek Sharma - * Jon A. Cruz - * Martin Owens - * - * Copyright (C) 2005-2008 Michael Wybrow - * Copyright (C) 2009 Monash University - * Copyright (C) 2012 Authors - * - * Released under GNU GPL, read the file 'COPYING' for more information - * - * TODO: - * o Show a visual indicator for objects with the 'avoid' property set. - * o Allow user to change a object between a path and connector through - * the interface. - * o Create an interface for setting markers (arrow heads). - * o Better distinguish between paths and connectors to prevent problems - * in the node tool and paths accidentally being turned into connectors - * in the connector tool. Perhaps have a way to convert between. - * o Only call libavoid's updateEndPoint as required. Currently we do it - * for both endpoints, even if only one is moving. - * o Deal sanely with connectors with both endpoints attached to the - * same connection point, and drawing of connectors attaching - * overlapping shapes (currently tries to adjust connector to be - * outside both bounding boxes). - * o Fix many special cases related to connectors updating, - * e.g., copying a couple of shapes and a connector that are - * attached to each other. - * e.g., detach connector when it is moved or transformed in - * one of the other contexts. - * o Cope with shapes whose ids change when they have attached - * connectors. - * o During dragging motion, gobble up to and use the final motion event. - * Gobbling away all duplicates after the current can occasionally result - * in the path lagging behind the mouse cursor if it is no longer being - * dragged. - * o Fix up libavoid's representation after undo actions. It doesn't see - * any transform signals and hence doesn't know shapes have moved back to - * there earlier positions. - * - * ---------------------------------------------------------------------------- - * - * Notes: - * - * Much of the way connectors work for user-defined points has been - * changed so that it no longer defines special attributes to record - * the points. Instead it uses single node paths to define points - * who are then seperate objects that can be fixed on the canvas, - * grouped into objects and take full advantage of all transform, snap - * and align functionality of all other objects. - * - * I think that the style change between polyline and orthogonal - * would be much clearer with two buttons (radio behaviour -- just - * one is true). - * - * The other tools show a label change from "New:" to "Change:" - * depending on whether an object is selected. We could consider - * this but there may not be space. - * - * Likewise for the avoid/ignore shapes buttons. These should be - * inactive when a shape is not selected in the connector context. - * - */ - - - -#include -#include -#include - -#include "connector-context.h" -#include "pixmaps/cursor-connector.xpm" -#include "xml/node-event-vector.h" -#include "xml/repr.h" -#include "svg/svg.h" -#include "desktop.h" -#include "desktop-style.h" -#include "desktop-handles.h" -#include "document.h" -#include "document-undo.h" -#include "message-context.h" -#include "message-stack.h" -#include "selection.h" -#include "inkscape.h" -#include "preferences.h" -#include "sp-path.h" -#include "display/sp-canvas.h" -#include "display/canvas-bpath.h" -#include "display/sodipodi-ctrl.h" -#include -#include -#include "snap.h" -#include "knot.h" -#include "sp-conn-end.h" -#include "sp-conn-end-pair.h" -#include "conn-avoid-ref.h" -#include "libavoid/vertices.h" -#include "libavoid/router.h" -#include "context-fns.h" -#include "sp-namedview.h" -#include "sp-text.h" -#include "sp-flowtext.h" -#include "display/curve.h" -#include "verbs.h" - -using Inkscape::DocumentUndo; - -#include "tool-factory.h" - -namespace Inkscape { -namespace UI { -namespace Tools { - -// Stuff borrowed from DrawContext -static void spcc_connector_set_initial_point(ConnectorTool *cc, Geom::Point const p); -static void spcc_connector_set_subsequent_point(ConnectorTool *cc, Geom::Point const p); -static void spcc_connector_finish_segment(ConnectorTool *cc, Geom::Point p); -static void spcc_reset_colors(ConnectorTool *cc); -static void spcc_connector_finish(ConnectorTool *cc); -static void spcc_concat_colors_and_flush(ConnectorTool *cc); -static void spcc_flush_white(ConnectorTool *cc, SPCurve *gc); - -// Context event handlers -static gint connector_handle_button_press(ConnectorTool *const cc, GdkEventButton const &bevent); -static gint connector_handle_motion_notify(ConnectorTool *const cc, GdkEventMotion const &mevent); -static gint connector_handle_button_release(ConnectorTool *const cc, GdkEventButton const &revent); -static gint connector_handle_key_press(ConnectorTool *const cc, guint const keyval); - -static void cc_active_shape_add_knot(ConnectorTool *cc, SPItem* item); -static void cc_set_active_shape(ConnectorTool *cc, SPItem *item); -static void cc_clear_active_knots(SPKnotList k); -static void cc_clear_active_shape(ConnectorTool *cc); -static void cc_set_active_conn(ConnectorTool *cc, SPItem *item); -static void cc_clear_active_conn(ConnectorTool *cc); -static bool conn_pt_handle_test(ConnectorTool *cc, Geom::Point& p, gchar **href); -static void cc_select_handle(SPKnot* knot); -static void cc_deselect_handle(SPKnot* knot); -static bool cc_item_is_shape(SPItem *item); -static void cc_connector_rerouting_finish(ConnectorTool *const cc, - Geom::Point *const p); - -static void shape_event_attr_deleted(Inkscape::XML::Node *repr, - Inkscape::XML::Node *child, Inkscape::XML::Node *ref, gpointer data); -static void shape_event_attr_changed(Inkscape::XML::Node *repr, gchar const *name, - gchar const *old_value, gchar const *new_value, bool is_interactive, - gpointer data); - -/*static Geom::Point connector_drag_origin_w(0, 0); -static bool connector_within_tolerance = false;*/ - -static Inkscape::XML::NodeEventVector shape_repr_events = { - NULL, /* child_added */ - NULL, /* child_added */ - shape_event_attr_changed, - NULL, /* content_changed */ - NULL /* order_changed */ -}; - -static Inkscape::XML::NodeEventVector layer_repr_events = { - NULL, /* child_added */ - shape_event_attr_deleted, - NULL, /* child_added */ - NULL, /* content_changed */ - NULL /* order_changed */ -}; - -namespace { - ToolBase* createConnectorContext() { - return new ConnectorTool(); - } - - bool connectorContextRegistered = ToolFactory::instance().registerObject("/tools/connector", createConnectorContext); -} - -const std::string& ConnectorTool::getPrefsPath() { - return ConnectorTool::prefsPath; -} - -const std::string ConnectorTool::prefsPath = "/tools/connector"; - -ConnectorTool::ConnectorTool() : ToolBase() { - this->red_curve = 0; - this->isOrthogonal = false; - this->c1 = 0; - this->red_bpath = 0; - this->green_curve = 0; - this->selection = 0; - this->cl0 = 0; - this->cl1 = 0; - this->c0 = 0; - - this->cursor_shape = cursor_connector_xpm; - this->hot_x = 1; - this->hot_y = 1; - this->xp = 0; - this->yp = 0; - - this->red_color = 0xff00007f; - - this->newconn = NULL; - this->newConnRef = NULL; - this->curvature = 0.0; - - this->sel_changed_connection = sigc::connection(); - - this->active_shape = NULL; - this->active_shape_repr = NULL; - this->active_shape_layer_repr = NULL; - - this->active_conn = NULL; - this->active_conn_repr = NULL; - - this->active_handle = NULL; - - this->selected_handle = NULL; - - this->clickeditem = NULL; - this->clickedhandle = NULL; - - for (int i = 0; i < 2; ++i) { - this->endpt_handle[i] = NULL; - this->endpt_handler_id[i] = 0; - } - - this->shref = NULL; - this->ehref = NULL; - this->npoints = 0; - this->state = SP_CONNECTOR_CONTEXT_IDLE; -} - -ConnectorTool::~ConnectorTool() { - this->sel_changed_connection.disconnect(); - - for (int i = 0; i < 2; ++i) { - if (this->endpt_handle[1]) { - g_object_unref(this->endpt_handle[i]); - this->endpt_handle[i] = NULL; - } - } - - if (this->shref) { - g_free(this->shref); - this->shref = NULL; - } - - if (this->ehref) { - g_free(this->shref); - this->shref = NULL; - } - - g_assert( this->newConnRef == NULL ); -} - -void ConnectorTool::setup() { - ToolBase::setup(); - - this->selection = sp_desktop_selection(this->desktop); - - this->sel_changed_connection.disconnect(); - this->sel_changed_connection = this->selection->connectChanged( - sigc::mem_fun(this, &ConnectorTool::selection_changed) - ); - - /* Create red bpath */ - this->red_bpath = sp_canvas_bpath_new(sp_desktop_sketch(this->desktop), NULL); - sp_canvas_bpath_set_stroke(SP_CANVAS_BPATH(this->red_bpath), this->red_color, - 1.0, SP_STROKE_LINEJOIN_MITER, SP_STROKE_LINECAP_BUTT); - sp_canvas_bpath_set_fill(SP_CANVAS_BPATH(this->red_bpath), 0x00000000, - SP_WIND_RULE_NONZERO); - /* Create red curve */ - this->red_curve = new SPCurve(); - - /* Create green curve */ - this->green_curve = new SPCurve(); - - // Notice the initial selection. - //cc_selection_changed(this->selection, (gpointer) this); - this->selection_changed(this->selection); - - this->within_tolerance = false; - - sp_event_context_read(this, "curvature"); - sp_event_context_read(this, "orthogonal"); - Inkscape::Preferences *prefs = Inkscape::Preferences::get(); - if (prefs->getBool("/tools/connector/selcue", 0)) { - this->enableSelectionCue(); - } - - // Make sure we see all enter events for canvas items, - // even if a mouse button is depressed. - this->desktop->canvas->gen_all_enter_events = true; -} - -void ConnectorTool::set(const Inkscape::Preferences::Entry& val) { - /* fixme: Proper error handling for non-numeric data. Use a locale-independent function like - * g_ascii_strtod (or a thin wrapper that does the right thing for invalid values inf/nan). */ - Glib::ustring name = val.getEntryName(); - - if (name == "curvature") { - this->curvature = val.getDoubleLimited(); // prevents NaN and +/-Inf from messing up - } else if (name == "orthogonal") { - this->isOrthogonal = val.getBool(); - } -} - -void ConnectorTool::finish() { - spcc_connector_finish(this); - this->state = SP_CONNECTOR_CONTEXT_IDLE; - - ToolBase::finish(); - - if (this->selection) { - this->selection = NULL; - } - - cc_clear_active_shape(this); - cc_clear_active_conn(this); - - // Restore the default event generating behaviour. - this->desktop->canvas->gen_all_enter_events = false; -} - -//----------------------------------------------------------------------------- - - -static void -cc_clear_active_shape(ConnectorTool *cc) -{ - if (cc->active_shape == NULL) { - return; - } - g_assert( cc->active_shape_repr ); - g_assert( cc->active_shape_layer_repr ); - - cc->active_shape = NULL; - - if (cc->active_shape_repr) { - sp_repr_remove_listener_by_data(cc->active_shape_repr, cc); - Inkscape::GC::release(cc->active_shape_repr); - cc->active_shape_repr = NULL; - - sp_repr_remove_listener_by_data(cc->active_shape_layer_repr, cc); - Inkscape::GC::release(cc->active_shape_layer_repr); - cc->active_shape_layer_repr = NULL; - } - - cc_clear_active_knots(cc->knots); -} - -static void -cc_clear_active_knots(SPKnotList k) -{ - // Hide the connection points if they exist. - if (k.size()) { - for (SPKnotList::iterator it = k.begin(); it != k.end(); ++it) { - sp_knot_hide(it->first); - } - } -} - -static void -cc_clear_active_conn(ConnectorTool *cc) -{ - if (cc->active_conn == NULL) { - return; - } - g_assert( cc->active_conn_repr ); - - cc->active_conn = NULL; - - if (cc->active_conn_repr) { - sp_repr_remove_listener_by_data(cc->active_conn_repr, cc); - Inkscape::GC::release(cc->active_conn_repr); - cc->active_conn_repr = NULL; - } - - // Hide the endpoint handles. - for (int i = 0; i < 2; ++i) { - if (cc->endpt_handle[i]) { - sp_knot_hide(cc->endpt_handle[i]); - } - } -} - - -static bool -conn_pt_handle_test(ConnectorTool *cc, Geom::Point& p, gchar **href) -{ - if (cc->active_handle && (cc->knots.find(cc->active_handle) != cc->knots.end())) - { - p = cc->active_handle->pos; - *href = g_strdup_printf("#%s", cc->active_handle->owner->getId()); - return true; - } - *href = NULL; - return false; -} - -static void -cc_select_handle(SPKnot* knot) -{ - knot->setShape(SP_KNOT_SHAPE_SQUARE); - knot->setSize(10); - knot->setAnchor(SP_ANCHOR_CENTER); - knot->setFill(0x0000ffff, 0x0000ffff, 0x0000ffff); - sp_knot_update_ctrl(knot); -} - -static void -cc_deselect_handle(SPKnot* knot) -{ - knot->setShape(SP_KNOT_SHAPE_SQUARE); - knot->setSize(8); - knot->setAnchor(SP_ANCHOR_CENTER); - knot->setFill(0xffffff00, 0xff0000ff, 0xff0000ff); - sp_knot_update_ctrl(knot); -} - -bool ConnectorTool::item_handler(SPItem* item, GdkEvent* event) { - gint ret = FALSE; - - Geom::Point p(event->button.x, event->button.y); - - switch (event->type) { - case GDK_BUTTON_RELEASE: - if (event->button.button == 1 && !this->space_panning) { - if ((this->state == SP_CONNECTOR_CONTEXT_DRAGGING) && this->within_tolerance) { - spcc_reset_colors(this); - this->state = SP_CONNECTOR_CONTEXT_IDLE; - } - - if (this->state != SP_CONNECTOR_CONTEXT_IDLE) { - // Doing something else like rerouting. - break; - } - - // find out clicked item, honoring Alt - SPItem *item = sp_event_context_find_item(desktop, p, event->button.state & GDK_MOD1_MASK, FALSE); - - if (event->button.state & GDK_SHIFT_MASK) { - this->selection->toggle(item); - } else { - this->selection->set(item); - /* When selecting a new item, do not allow showing - connection points on connectors. (yet?) - */ - - if (item != this->active_shape && !cc_item_is_connector(item)) { - cc_set_active_shape(this, item); - } - } - - ret = TRUE; - } - break; - - case GDK_ENTER_NOTIFY: - if (!this->selected_handle) { - if (cc_item_is_shape(item)) { - cc_set_active_shape(this, item); - } - - ret = TRUE; - } - break; - - default: - break; - } - - return ret; -} - -bool ConnectorTool::root_handler(GdkEvent* event) { - gint ret = FALSE; - - switch (event->type) { - case GDK_BUTTON_PRESS: - ret = connector_handle_button_press(this, event->button); - break; - - case GDK_MOTION_NOTIFY: - ret = connector_handle_motion_notify(this, event->motion); - break; - - case GDK_BUTTON_RELEASE: - ret = connector_handle_button_release(this, event->button); - break; - - case GDK_KEY_PRESS: - ret = connector_handle_key_press(this, get_group0_keyval (&event->key)); - break; - - default: - break; - } - - if (!ret) { - ret = ToolBase::root_handler(event); - } - - return ret; -} - - -static gint -connector_handle_button_press(ConnectorTool *const cc, GdkEventButton const &bevent) -{ - Geom::Point const event_w(bevent.x, bevent.y); - /* Find desktop coordinates */ - Geom::Point p = cc->desktop->w2d(event_w); - ToolBase *event_context = SP_EVENT_CONTEXT(cc); - - gint ret = FALSE; - - if ( bevent.button == 1 && !event_context->space_panning ) { - - SPDesktop *desktop = SP_EVENT_CONTEXT_DESKTOP(cc); - - if (Inkscape::have_viable_layer(desktop, cc->message_context) == false) { - return TRUE; - } - - Geom::Point const event_w(bevent.x, - bevent.y); - - cc->xp = bevent.x; - cc->yp = bevent.y; - cc->within_tolerance = true; - - Geom::Point const event_dt = cc->desktop->w2d(event_w); - - SnapManager &m = cc->desktop->namedview->snap_manager; - - switch (cc->state) { - case SP_CONNECTOR_CONTEXT_STOP: - /* This is allowed, if we just canceled curve */ - case SP_CONNECTOR_CONTEXT_IDLE: - { - if ( cc->npoints == 0 ) { - cc_clear_active_conn(cc); - - SP_EVENT_CONTEXT_DESKTOP(cc)->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("Creating new connector")); - - /* Set start anchor */ - /* Create green anchor */ - Geom::Point p = event_dt; - - // Test whether we clicked on a connection point - bool found = conn_pt_handle_test(cc, p, &cc->shref); - - if (!found) { - // This is the first point, so just snap it to the grid - // as there's no other points to go off. - m.setup(cc->desktop); - m.freeSnapReturnByRef(p, Inkscape::SNAPSOURCE_OTHER_HANDLE); - m.unSetup(); - } - spcc_connector_set_initial_point(cc, p); - - } - cc->state = SP_CONNECTOR_CONTEXT_DRAGGING; - ret = TRUE; - break; - } - case SP_CONNECTOR_CONTEXT_DRAGGING: - { - // This is the second click of a connector creation. - m.setup(cc->desktop); - m.freeSnapReturnByRef(p, Inkscape::SNAPSOURCE_OTHER_HANDLE); - m.unSetup(); - - spcc_connector_set_subsequent_point(cc, p); - spcc_connector_finish_segment(cc, p); - - conn_pt_handle_test(cc, p, &cc->ehref); - if (cc->npoints != 0) { - spcc_connector_finish(cc); - } - cc_set_active_conn(cc, cc->newconn); - cc->state = SP_CONNECTOR_CONTEXT_IDLE; - ret = TRUE; - break; - } - case SP_CONNECTOR_CONTEXT_CLOSE: - { - g_warning("Button down in CLOSE state"); - break; - } - default: - break; - } - } else if (bevent.button == 3) { - if (cc->state == SP_CONNECTOR_CONTEXT_REROUTING) { - // A context menu is going to be triggered here, - // so end the rerouting operation. - cc_connector_rerouting_finish(cc, &p); - - cc->state = SP_CONNECTOR_CONTEXT_IDLE; - - // Don't set ret to TRUE, so we drop through to the - // parent handler which will open the context menu. - } - else if (cc->npoints != 0) { - spcc_connector_finish(cc); - cc->state = SP_CONNECTOR_CONTEXT_IDLE; - ret = TRUE; - } - } - return ret; -} - -static gint -connector_handle_motion_notify(ConnectorTool *const cc, GdkEventMotion const &mevent) -{ - gint ret = FALSE; - ToolBase *event_context = SP_EVENT_CONTEXT(cc); - Inkscape::Preferences *prefs = Inkscape::Preferences::get(); - - if (event_context->space_panning || mevent.state & GDK_BUTTON2_MASK || mevent.state & GDK_BUTTON3_MASK) { - // allow middle-button scrolling - return FALSE; - } - - Geom::Point const event_w(mevent.x, mevent.y); - - if (cc->within_tolerance) { - cc->tolerance = prefs->getIntLimited("/options/dragtolerance/value", 0, 0, 100); - if ( ( abs( (gint) mevent.x - cc->xp ) < cc->tolerance ) && - ( abs( (gint) mevent.y - cc->yp ) < cc->tolerance ) ) { - return FALSE; // Do not drag if we're within tolerance from origin. - } - } - // Once the user has moved farther than tolerance from the original location - // (indicating they intend to move the object, not click), then always process - // the motion notify coordinates as given (no snapping back to origin) - cc->within_tolerance = false; - - SPDesktop *const dt = cc->desktop; - - /* Find desktop coordinates */ - Geom::Point p = dt->w2d(event_w); - - SnapManager &m = dt->namedview->snap_manager; - - switch (cc->state) { - case SP_CONNECTOR_CONTEXT_DRAGGING: - { - gobble_motion_events(mevent.state); - // This is movement during a connector creation. - if ( cc->npoints > 0 ) { - m.setup(dt); - m.freeSnapReturnByRef(p, Inkscape::SNAPSOURCE_OTHER_HANDLE); - m.unSetup(); - cc->selection->clear(); - spcc_connector_set_subsequent_point(cc, p); - ret = TRUE; - } - break; - } - case SP_CONNECTOR_CONTEXT_REROUTING: - { - gobble_motion_events(GDK_BUTTON1_MASK); - g_assert( SP_IS_PATH(cc->clickeditem)); - - m.setup(dt); - m.freeSnapReturnByRef(p, Inkscape::SNAPSOURCE_OTHER_HANDLE); - m.unSetup(); - - // Update the hidden path - Geom::Affine i2d ( (cc->clickeditem)->i2dt_affine() ); - Geom::Affine d2i = i2d.inverse(); - SPPath *path = SP_PATH(cc->clickeditem); - SPCurve *curve = path->get_curve(); - if (cc->clickedhandle == cc->endpt_handle[0]) { - Geom::Point o = cc->endpt_handle[1]->pos; - curve->stretch_endpoints(p * d2i, o * d2i); - } - else { - Geom::Point o = cc->endpt_handle[0]->pos; - curve->stretch_endpoints(o * d2i, p * d2i); - } - sp_conn_reroute_path_immediate(path); - - // Copy this to the temporary visible path - cc->red_curve = path->get_curve_for_edit(); - cc->red_curve->transform(i2d); - - sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(cc->red_bpath), cc->red_curve); - ret = TRUE; - break; - } - case SP_CONNECTOR_CONTEXT_STOP: - /* This is perfectly valid */ - break; - default: - if (!sp_event_context_knot_mouseover(cc)) { - m.setup(dt); - m.preSnap(Inkscape::SnapCandidatePoint(p, Inkscape::SNAPSOURCE_OTHER_HANDLE)); - m.unSetup(); - } - break; - } - return ret; -} - -static gint -connector_handle_button_release(ConnectorTool *const cc, GdkEventButton const &revent) -{ - gint ret = FALSE; - ToolBase *event_context = SP_EVENT_CONTEXT(cc); - if ( revent.button == 1 && !event_context->space_panning ) { - - SPDesktop *desktop = SP_EVENT_CONTEXT_DESKTOP(cc); - SPDocument *doc = sp_desktop_document(desktop); - - SnapManager &m = desktop->namedview->snap_manager; - - Geom::Point const event_w(revent.x, revent.y); - - /* Find desktop coordinates */ - Geom::Point p = cc->desktop->w2d(event_w); - - switch (cc->state) { - //case SP_CONNECTOR_CONTEXT_POINT: - case SP_CONNECTOR_CONTEXT_DRAGGING: - { - m.setup(desktop); - m.freeSnapReturnByRef(p, Inkscape::SNAPSOURCE_OTHER_HANDLE); - m.unSetup(); - - if (cc->within_tolerance) - { - spcc_connector_finish_segment(cc, p); - return TRUE; - } - // Connector has been created via a drag, end it now. - spcc_connector_set_subsequent_point(cc, p); - spcc_connector_finish_segment(cc, p); - // Test whether we clicked on a connection point - conn_pt_handle_test(cc, p, &cc->ehref); - if (cc->npoints != 0) { - spcc_connector_finish(cc); - } - cc_set_active_conn(cc, cc->newconn); - cc->state = SP_CONNECTOR_CONTEXT_IDLE; - break; - } - case SP_CONNECTOR_CONTEXT_REROUTING: - { - m.setup(desktop); - m.freeSnapReturnByRef(p, Inkscape::SNAPSOURCE_OTHER_HANDLE); - m.unSetup(); - cc_connector_rerouting_finish(cc, &p); - - doc->ensureUpToDate(); - cc->state = SP_CONNECTOR_CONTEXT_IDLE; - return TRUE; - break; - } - case SP_CONNECTOR_CONTEXT_STOP: - /* This is allowed, if we just cancelled curve */ - break; - default: - break; - } - ret = TRUE; - } - return ret; -} - -static gint -connector_handle_key_press(ConnectorTool *const cc, guint const keyval) -{ - gint ret = FALSE; - - switch (keyval) { - case GDK_KEY_Return: - case GDK_KEY_KP_Enter: - if (cc->npoints != 0) { - spcc_connector_finish(cc); - cc->state = SP_CONNECTOR_CONTEXT_IDLE; - ret = TRUE; - } - break; - case GDK_KEY_Escape: - if (cc->state == SP_CONNECTOR_CONTEXT_REROUTING) { - - SPDesktop *desktop = SP_EVENT_CONTEXT_DESKTOP(cc); - SPDocument *doc = sp_desktop_document(desktop); - - cc_connector_rerouting_finish(cc, NULL); - - DocumentUndo::undo(doc); - - cc->state = SP_CONNECTOR_CONTEXT_IDLE; - desktop->messageStack()->flash( Inkscape::NORMAL_MESSAGE, - _("Connector endpoint drag cancelled.")); - ret = TRUE; - } - else if (cc->npoints != 0) { - // if drawing, cancel, otherwise pass it up for deselecting - cc->state = SP_CONNECTOR_CONTEXT_STOP; - spcc_reset_colors(cc); - ret = TRUE; - } - break; - default: - break; - } - return ret; -} - - -static void -cc_connector_rerouting_finish(ConnectorTool *const cc, Geom::Point *const p) -{ - SPDesktop *desktop = SP_EVENT_CONTEXT_DESKTOP(cc); - SPDocument *doc = sp_desktop_document(desktop); - - // Clear the temporary path: - cc->red_curve->reset(); - sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(cc->red_bpath), NULL); - - if (p != NULL) - { - // Test whether we clicked on a connection point - gchar *shape_label; - bool found = conn_pt_handle_test(cc, *p, &shape_label); - - if (found) { - if (cc->clickedhandle == cc->endpt_handle[0]) { - cc->clickeditem->setAttribute("inkscape:connection-start", shape_label, NULL); - } - else { - cc->clickeditem->setAttribute("inkscape:connection-end", shape_label, NULL); - } - g_free(shape_label); - } - } - cc->clickeditem->setHidden(false); - sp_conn_reroute_path_immediate(SP_PATH(cc->clickeditem)); - cc->clickeditem->updateRepr(); - DocumentUndo::done(doc, SP_VERB_CONTEXT_CONNECTOR, - _("Reroute connector")); - cc_set_active_conn(cc, cc->clickeditem); -} - - -static void -spcc_reset_colors(ConnectorTool *cc) -{ - /* Red */ - cc->red_curve->reset(); - sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(cc->red_bpath), NULL); - - cc->green_curve->reset(); - cc->npoints = 0; -} - - -static void -spcc_connector_set_initial_point(ConnectorTool *const cc, Geom::Point const p) -{ - g_assert( cc->npoints == 0 ); - - cc->p[0] = p; - cc->p[1] = p; - cc->npoints = 2; - sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(cc->red_bpath), NULL); -} - - -static void -spcc_connector_set_subsequent_point(ConnectorTool *const cc, Geom::Point const p) -{ - g_assert( cc->npoints != 0 ); - - SPDesktop *dt = cc->desktop; - Geom::Point o = dt->dt2doc(cc->p[0]); - Geom::Point d = dt->dt2doc(p); - Avoid::Point src(o[Geom::X], o[Geom::Y]); - Avoid::Point dst(d[Geom::X], d[Geom::Y]); - - if (!cc->newConnRef) { - Avoid::Router *router = sp_desktop_document(dt)->router; - cc->newConnRef = new Avoid::ConnRef(router); - cc->newConnRef->setEndpoint(Avoid::VertID::src, src); - if (cc->isOrthogonal) - cc->newConnRef->setRoutingType(Avoid::ConnType_Orthogonal); - else - cc->newConnRef->setRoutingType(Avoid::ConnType_PolyLine); - } - // Set new endpoint. - cc->newConnRef->setEndpoint(Avoid::VertID::tar, dst); - // Immediately generate new routes for connector. - cc->newConnRef->makePathInvalid(); - cc->newConnRef->router()->processTransaction(); - // Recreate curve from libavoid route. - recreateCurve( cc->red_curve, cc->newConnRef, cc->curvature ); - cc->red_curve->transform(dt->doc2dt()); - sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(cc->red_bpath), cc->red_curve); -} - - -/** - * Concats red, blue and green. - * If any anchors are defined, process these, optionally removing curves from white list - * Invoke _flush_white to write result back to object. - */ -static void -spcc_concat_colors_and_flush(ConnectorTool *cc) -{ - SPCurve *c = cc->green_curve; - cc->green_curve = new SPCurve(); - - cc->red_curve->reset(); - sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(cc->red_bpath), NULL); - - if (c->is_empty()) { - c->unref(); - return; - } - - spcc_flush_white(cc, c); - - c->unref(); -} - - -/* - * Flushes white curve(s) and additional curve into object - * - * No cleaning of colored curves - this has to be done by caller - * No rereading of white data, so if you cannot rely on ::modified, do it in caller - * - */ - -static void -spcc_flush_white(ConnectorTool *cc, SPCurve *gc) -{ - SPCurve *c; - - if (gc) { - c = gc; - c->ref(); - } else { - return; - } - - /* Now we have to go back to item coordinates at last */ - c->transform(SP_EVENT_CONTEXT_DESKTOP(cc)->dt2doc()); - - SPDesktop *desktop = SP_EVENT_CONTEXT_DESKTOP(cc); - SPDocument *doc = sp_desktop_document(desktop); - Inkscape::XML::Document *xml_doc = doc->getReprDoc(); - - if ( c && !c->is_empty() ) { - /* We actually have something to write */ - - Inkscape::XML::Node *repr = xml_doc->createElement("svg:path"); - /* Set style */ - sp_desktop_apply_style_tool(desktop, repr, "/tools/connector", false); - - gchar *str = sp_svg_write_path( c->get_pathvector() ); - g_assert( str != NULL ); - repr->setAttribute("d", str); - g_free(str); - - /* Attach repr */ - cc->newconn = SP_ITEM(desktop->currentLayer()->appendChildRepr(repr)); - cc->newconn->transform = SP_ITEM(desktop->currentLayer())->i2doc_affine().inverse(); - - bool connection = false; - cc->newconn->setAttribute( "inkscape:connector-type", - cc->isOrthogonal ? "orthogonal" : "polyline", NULL ); - cc->newconn->setAttribute( "inkscape:connector-curvature", - Glib::Ascii::dtostr(cc->curvature).c_str(), NULL ); - if (cc->shref) - { - cc->newconn->setAttribute( "inkscape:connection-start", cc->shref, NULL); - connection = true; - } - - if (cc->ehref) - { - cc->newconn->setAttribute( "inkscape:connection-end", cc->ehref, NULL); - connection = true; - } - // Process pending updates. - cc->newconn->updateRepr(); - doc->ensureUpToDate(); - - if (connection) { - // Adjust endpoints to shape edge. - sp_conn_reroute_path_immediate(SP_PATH(cc->newconn)); - cc->newconn->updateRepr(); - } - - cc->newconn->doWriteTransform(cc->newconn->getRepr(), cc->newconn->transform, NULL, true); - - // Only set the selection after we are finished with creating the attributes of - // the connector. Otherwise, the selection change may alter the defaults for - // values like curvature in the connector context, preventing subsequent lookup - // of their original values. - cc->selection->set(repr); - Inkscape::GC::release(repr); - } - - c->unref(); - - DocumentUndo::done(doc, SP_VERB_CONTEXT_CONNECTOR, _("Create connector")); -} - - -static void -spcc_connector_finish_segment(ConnectorTool *const cc, Geom::Point const /*p*/) -{ - if (!cc->red_curve->is_empty()) { - cc->green_curve->append_continuous(cc->red_curve, 0.0625); - - cc->p[0] = cc->p[3]; - cc->p[1] = cc->p[4]; - cc->npoints = 2; - - cc->red_curve->reset(); - } -} - - -static void -spcc_connector_finish(ConnectorTool *const cc) -{ - SPDesktop *const desktop = cc->desktop; - desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("Finishing connector")); - - cc->red_curve->reset(); - spcc_concat_colors_and_flush(cc); - - cc->npoints = 0; - - if (cc->newConnRef) { - cc->newConnRef->removeFromGraph(); - delete cc->newConnRef; - cc->newConnRef = NULL; - } -} - - -static gboolean -cc_generic_knot_handler(SPCanvasItem *, GdkEvent *event, SPKnot *knot) -{ - g_assert (knot != NULL); - - g_object_ref(knot); - - ConnectorTool *cc = SP_CONNECTOR_CONTEXT( - knot->desktop->event_context); - - gboolean consumed = FALSE; - - gchar const *knot_tip = "Click to join at this point"; - switch (event->type) { - case GDK_ENTER_NOTIFY: - sp_knot_set_flag(knot, SP_KNOT_MOUSEOVER, TRUE); - - cc->active_handle = knot; - if (knot_tip) - { - knot->desktop->event_context->defaultMessageContext()->set( - Inkscape::NORMAL_MESSAGE, knot_tip); - } - - consumed = TRUE; - break; - case GDK_LEAVE_NOTIFY: - sp_knot_set_flag(knot, SP_KNOT_MOUSEOVER, FALSE); - - cc->active_handle = NULL; - - if (knot_tip) { - knot->desktop->event_context->defaultMessageContext()->clear(); - } - - consumed = TRUE; - break; - default: - break; - } - - g_object_unref(knot); - - return consumed; -} - - -static gboolean -endpt_handler(SPKnot */*knot*/, GdkEvent *event, ConnectorTool *cc) -{ - //g_assert( SP_IS_CONNECTOR_CONTEXT(cc) ); - - gboolean consumed = FALSE; - - switch (event->type) { - case GDK_BUTTON_PRESS: - g_assert( (cc->active_handle == cc->endpt_handle[0]) || - (cc->active_handle == cc->endpt_handle[1]) ); - if (cc->state == SP_CONNECTOR_CONTEXT_IDLE) { - cc->clickeditem = cc->active_conn; - cc->clickedhandle = cc->active_handle; - cc_clear_active_conn(cc); - cc->state = SP_CONNECTOR_CONTEXT_REROUTING; - - // Disconnect from attached shape - unsigned ind = (cc->active_handle == cc->endpt_handle[0]) ? 0 : 1; - sp_conn_end_detach(cc->clickeditem, ind); - - Geom::Point origin; - if (cc->clickedhandle == cc->endpt_handle[0]) { - origin = cc->endpt_handle[1]->pos; - } - else { - origin = cc->endpt_handle[0]->pos; - } - - // Show the red path for dragging. - cc->red_curve = SP_PATH(cc->clickeditem)->get_curve_for_edit(); - Geom::Affine i2d = (cc->clickeditem)->i2dt_affine(); - cc->red_curve->transform(i2d); - sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(cc->red_bpath), cc->red_curve); - - cc->clickeditem->setHidden(true); - - // The rest of the interaction rerouting the connector is - // handled by the context root handler. - consumed = TRUE; - } - break; - default: - break; - } - - return consumed; -} - -static void cc_active_shape_add_knot(ConnectorTool *cc, SPItem* item) -{ - SPDesktop *desktop = cc->desktop; - SPKnot *knot = sp_knot_new(desktop, 0); - - knot->owner = item; - knot->setShape(SP_KNOT_SHAPE_SQUARE); - knot->setSize(8); - knot->setAnchor(SP_ANCHOR_CENTER); - knot->setFill(0xffffff00, 0xff0000ff, 0xff0000ff); - sp_knot_update_ctrl(knot); - - // We don't want to use the standard knot handler. - g_signal_handler_disconnect(G_OBJECT(knot->item), - knot->_event_handler_id); - knot->_event_handler_id = 0; - - g_signal_connect(G_OBJECT(knot->item), "event", - G_CALLBACK(cc_generic_knot_handler), knot); - sp_knot_set_position(knot, item->avoidRef->getConnectionPointPos() * desktop->doc2dt(), 0); - sp_knot_show(knot); - cc->knots[knot] = 1; -} - -static void cc_set_active_shape(ConnectorTool *cc, SPItem *item) -{ - g_assert(item != NULL ); - - if (cc->active_shape != item) - { - // The active shape has changed - // Rebuild everything - cc->active_shape = item; - // Remove existing active shape listeners - if (cc->active_shape_repr) { - sp_repr_remove_listener_by_data(cc->active_shape_repr, cc); - Inkscape::GC::release(cc->active_shape_repr); - - sp_repr_remove_listener_by_data(cc->active_shape_layer_repr, cc); - Inkscape::GC::release(cc->active_shape_layer_repr); - } - - // Listen in case the active shape changes - cc->active_shape_repr = item->getRepr(); - if (cc->active_shape_repr) { - Inkscape::GC::anchor(cc->active_shape_repr); - sp_repr_add_listener(cc->active_shape_repr, &shape_repr_events, cc); - - cc->active_shape_layer_repr = cc->active_shape_repr->parent(); - Inkscape::GC::anchor(cc->active_shape_layer_repr); - sp_repr_add_listener(cc->active_shape_layer_repr, &layer_repr_events, cc); - } - - cc_clear_active_knots(cc->knots); - - // The idea here is to try and add a group's children to solidify - // connection handling. We react to path objects with only one node. - for (SPObject *child = item->firstChild() ; child ; child = child->getNext() ) { - if (SP_IS_PATH(child) && SP_PATH(child)->nodesInPath() == 1) { - cc_active_shape_add_knot(cc, (SPItem *) child); - } - } - cc_active_shape_add_knot(cc, item); - - } - else - { - // Ensure the item's connection_points map - // has been updated - item->document->ensureUpToDate(); - } -} - - -static void -cc_set_active_conn(ConnectorTool *cc, SPItem *item) -{ - g_assert( SP_IS_PATH(item) ); - - const SPCurve *curve = SP_PATH(item)->get_curve_reference(); - Geom::Affine i2dt = item->i2dt_affine(); - - if (cc->active_conn == item) - { - if (curve->is_empty()) - { - // Connector is invisible because it is clipped to the boundary of - // two overlpapping shapes. - sp_knot_hide(cc->endpt_handle[0]); - sp_knot_hide(cc->endpt_handle[1]); - } - else - { - // Just adjust handle positions. - Geom::Point startpt = *(curve->first_point()) * i2dt; - sp_knot_set_position(cc->endpt_handle[0], startpt, 0); - - Geom::Point endpt = *(curve->last_point()) * i2dt; - sp_knot_set_position(cc->endpt_handle[1], endpt, 0); - } - - return; - } - - cc->active_conn = item; - - // Remove existing active conn listeners - if (cc->active_conn_repr) { - sp_repr_remove_listener_by_data(cc->active_conn_repr, cc); - Inkscape::GC::release(cc->active_conn_repr); - cc->active_conn_repr = NULL; - } - - // Listen in case the active conn changes - cc->active_conn_repr = item->getRepr(); - if (cc->active_conn_repr) { - Inkscape::GC::anchor(cc->active_conn_repr); - sp_repr_add_listener(cc->active_conn_repr, &shape_repr_events, cc); - } - - for (int i = 0; i < 2; ++i) { - - // Create the handle if it doesn't exist - if ( cc->endpt_handle[i] == NULL ) { - SPKnot *knot = sp_knot_new(cc->desktop, - _("Connector endpoint: drag to reroute or connect to new shapes")); - - knot->setShape(SP_KNOT_SHAPE_SQUARE); - knot->setSize(7); - knot->setAnchor(SP_ANCHOR_CENTER); - knot->setFill(0xffffff00, 0xff0000ff, 0xff0000ff); - knot->setStroke(0x000000ff, 0x000000ff, 0x000000ff); - sp_knot_update_ctrl(knot); - - // We don't want to use the standard knot handler, - // since we don't want this knot to be draggable. - g_signal_handler_disconnect(G_OBJECT(knot->item), - knot->_event_handler_id); - knot->_event_handler_id = 0; - - g_signal_connect(G_OBJECT(knot->item), "event", - G_CALLBACK(cc_generic_knot_handler), knot); - - cc->endpt_handle[i] = knot; - } - - // Remove any existing handlers - if (cc->endpt_handler_id[i]) { - g_signal_handlers_disconnect_by_func( - G_OBJECT(cc->endpt_handle[i]->item), - (void*)G_CALLBACK(endpt_handler), (gpointer) cc ); - cc->endpt_handler_id[i] = 0; - } - - // Setup handlers for connector endpoints, this is - // is as 'after' so that cc_generic_knot_handler is - // triggered first for any endpoint. - cc->endpt_handler_id[i] = g_signal_connect_after( - G_OBJECT(cc->endpt_handle[i]->item), "event", - G_CALLBACK(endpt_handler), cc); - } - - if (curve->is_empty()) - { - // Connector is invisible because it is clipped to the boundary - // of two overlpapping shapes. So, it doesn't need endpoints. - return; - } - - Geom::Point startpt = *(curve->first_point()) * i2dt; - sp_knot_set_position(cc->endpt_handle[0], startpt, 0); - - Geom::Point endpt = *(curve->last_point()) * i2dt; - sp_knot_set_position(cc->endpt_handle[1], endpt, 0); - - sp_knot_show(cc->endpt_handle[0]); - sp_knot_show(cc->endpt_handle[1]); -} - -void cc_create_connection_point(ConnectorTool* cc) -{ - if (cc->active_shape && cc->state == SP_CONNECTOR_CONTEXT_IDLE) - { - if (cc->selected_handle) - { - cc_deselect_handle( cc->selected_handle ); - } - SPKnot *knot = sp_knot_new(cc->desktop, 0); - // We do not process events on this knot. - g_signal_handler_disconnect(G_OBJECT(knot->item), - knot->_event_handler_id); - knot->_event_handler_id = 0; - - cc_select_handle( knot ); - cc->selected_handle = knot; - sp_knot_show(cc->selected_handle); - cc->state = SP_CONNECTOR_CONTEXT_NEWCONNPOINT; - } -} - -static bool cc_item_is_shape(SPItem *item) -{ - if (SP_IS_PATH(item)) { - const SPCurve * curve = (SP_SHAPE(item))->_curve; - if ( curve && !(curve->is_closed()) ) { - // Open paths are connectors. - return false; - } - } - else if (SP_IS_TEXT(item) || SP_IS_FLOWTEXT(item)) { - Inkscape::Preferences *prefs = Inkscape::Preferences::get(); - if (prefs->getBool("/tools/connector/ignoretext", true)) { - // Don't count text as a shape we can connect connector to. - return false; - } - } - return true; -} - - -bool cc_item_is_connector(SPItem *item) -{ - if (SP_IS_PATH(item)) { - bool closed = SP_PATH(item)->get_curve_reference()->is_closed(); - if (SP_PATH(item)->connEndPair.isAutoRoutingConn() && !closed) { - // To be considered a connector, an object must be a non-closed - // path that is marked with a "inkscape:connector-type" attribute. - return true; - } - } - return false; -} - - -void cc_selection_set_avoid(bool const set_avoid) -{ - SPDesktop *desktop = inkscape_active_desktop(); - if (desktop == NULL) { - return; - } - - SPDocument *document = sp_desktop_document(desktop); - - Inkscape::Selection *selection = sp_desktop_selection(desktop); - - GSList *l = const_cast(selection->itemList()); - - int changes = 0; - - while (l) { - SPItem *item = SP_ITEM(l->data); - - char const *value = (set_avoid) ? "true" : NULL; - - if (cc_item_is_shape(item)) { - item->setAttribute("inkscape:connector-avoid", value, NULL); - item->avoidRef->handleSettingChange(); - changes++; - } - - l = l->next; - } - - if (changes == 0) { - desktop->messageStack()->flash(Inkscape::WARNING_MESSAGE, - _("Select at least one non-connector object.")); - return; - } - - char *event_desc = (set_avoid) ? - _("Make connectors avoid selected objects") : - _("Make connectors ignore selected objects"); - DocumentUndo::done(document, SP_VERB_CONTEXT_CONNECTOR, event_desc); -} - -void ConnectorTool::selection_changed(Inkscape::Selection *selection) { - SPItem *item = selection->singleItem(); - - if (this->active_conn == item) { - // Nothing to change. - return; - } - - if (item == NULL) { - cc_clear_active_conn(this); - return; - } - - if (cc_item_is_connector(item)) { - cc_set_active_conn(this, item); - } -} - -static void -shape_event_attr_deleted(Inkscape::XML::Node */*repr*/, Inkscape::XML::Node *child, - Inkscape::XML::Node */*ref*/, gpointer data) -{ - g_assert(data); - ConnectorTool *cc = SP_CONNECTOR_CONTEXT(data); - - if (child == cc->active_shape_repr) { - // The active shape has been deleted. Clear active shape. - cc_clear_active_shape(cc); - } -} - - -static void -shape_event_attr_changed(Inkscape::XML::Node *repr, gchar const *name, - gchar const */*old_value*/, gchar const */*new_value*/, - bool /*is_interactive*/, gpointer data) -{ - g_assert(data); - ConnectorTool *cc = SP_CONNECTOR_CONTEXT(data); - - // Look for changes that result in onscreen movement. - if (!strcmp(name, "d") || !strcmp(name, "x") || !strcmp(name, "y") || - !strcmp(name, "width") || !strcmp(name, "height") || - !strcmp(name, "transform")) - { - if (repr == cc->active_shape_repr) { - // Active shape has moved. Clear active shape. - cc_clear_active_shape(cc); - } - else if (repr == cc->active_conn_repr) { - // The active conn has been moved. - // Set it again, which just sets new handle positions. - cc_set_active_conn(cc, cc->active_conn); - } - } -} - -} -} -} - - -/* - Local Variables: - mode:c++ - c-file-style:"stroustrup" - c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) - indent-tabs-mode:nil - fill-column:99 - End: -*/ -// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/connector-context.h b/src/connector-context.h deleted file mode 100644 index b568b44fc..000000000 --- a/src/connector-context.h +++ /dev/null @@ -1,135 +0,0 @@ -#ifndef SEEN_CONNECTOR_CONTEXT_H -#define SEEN_CONNECTOR_CONTEXT_H - -/* - * Connector creation tool - * - * Authors: - * Michael Wybrow - * - * Copyright (C) 2005 Michael Wybrow - * - * Released under GNU GPL, read the file 'COPYING' for more information - */ - -#include -#include -#include -#include "event-context.h" -#include <2geom/point.h> -#include "libavoid/connector.h" -#include - -#define SP_CONNECTOR_CONTEXT(obj) (dynamic_cast((Inkscape::UI::Tools::ToolBase*)obj)) -//#define SP_IS_CONNECTOR_CONTEXT(obj) (dynamic_cast((const ToolBase*)obj) != NULL) - -struct SPKnot; -class SPCurve; - -namespace Inkscape -{ - class Selection; -} - -enum { - SP_CONNECTOR_CONTEXT_IDLE, - SP_CONNECTOR_CONTEXT_DRAGGING, - SP_CONNECTOR_CONTEXT_CLOSE, - SP_CONNECTOR_CONTEXT_STOP, - SP_CONNECTOR_CONTEXT_REROUTING, - SP_CONNECTOR_CONTEXT_NEWCONNPOINT -}; - -typedef std::map SPKnotList; - -namespace Inkscape { -namespace UI { -namespace Tools { - -class ConnectorTool : public ToolBase { -public: - ConnectorTool(); - virtual ~ConnectorTool(); - - Inkscape::Selection *selection; - Geom::Point p[5]; - - /** \invar npoints in {0, 2}. */ - gint npoints; - unsigned int state : 4; - - // Red curve - SPCanvasItem *red_bpath; - SPCurve *red_curve; - guint32 red_color; - - // Green curve - SPCurve *green_curve; - - // The new connector - SPItem *newconn; - Avoid::ConnRef *newConnRef; - gdouble curvature; - bool isOrthogonal; - - // The active shape - SPItem *active_shape; - Inkscape::XML::Node *active_shape_repr; - Inkscape::XML::Node *active_shape_layer_repr; - - // Same as above, but for the active connector - SPItem *active_conn; - Inkscape::XML::Node *active_conn_repr; - sigc::connection sel_changed_connection; - - // The activehandle - SPKnot *active_handle; - - // The selected handle, used in editing mode - SPKnot *selected_handle; - - SPItem *clickeditem; - SPKnot *clickedhandle; - - SPKnotList knots; - SPKnot *endpt_handle[2]; - guint endpt_handler_id[2]; - gchar *shref; - gchar *ehref; - SPCanvasItem *c0, *c1, *cl0, *cl1; - - static const std::string prefsPath; - - virtual void setup(); - virtual void finish(); - virtual void set(const Inkscape::Preferences::Entry& val); - virtual bool root_handler(GdkEvent* event); - virtual bool item_handler(SPItem* item, GdkEvent* event); - - virtual const std::string& getPrefsPath(); - -private: - void selection_changed(Inkscape::Selection *selection); -}; - -void cc_selection_set_avoid(bool const set_ignore); -void cc_create_connection_point(ConnectorTool* cc); -void cc_remove_connection_point(ConnectorTool* cc); -bool cc_item_is_connector(SPItem *item); - -} -} -} - -#endif /* !SEEN_CONNECTOR_CONTEXT_H */ - -/* - Local Variables: - mode:c++ - c-file-style:"stroustrup" - c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) - indent-tabs-mode:nil - fill-column:99 - End: -*/ -// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/context-fns.cpp b/src/context-fns.cpp index 8c18c0764..1e30e51de 100644 --- a/src/context-fns.cpp +++ b/src/context-fns.cpp @@ -9,7 +9,7 @@ #include "message-stack.h" #include "context-fns.h" #include "snap.h" -#include "event-context.h" +#include "ui/tools/tool-base.h" #include "sp-namedview.h" #include "display/snap-indicator.h" diff --git a/src/desktop-events.cpp b/src/desktop-events.cpp index 67c500b05..4f8f7889f 100644 --- a/src/desktop-events.cpp +++ b/src/desktop-events.cpp @@ -32,7 +32,7 @@ #include "display/snap-indicator.h" #include "document.h" #include "document-undo.h" -#include "event-context.h" +#include "ui/tools/tool-base.h" #include "helper/action.h" #include "message-context.h" #include "preferences.h" diff --git a/src/desktop-style.cpp b/src/desktop-style.cpp index f8fad9711..00b9db7d0 100644 --- a/src/desktop-style.cpp +++ b/src/desktop-style.cpp @@ -42,7 +42,7 @@ #include "xml/sp-css-attr.h" #include "libnrtype/font-style-to-pos.h" #include "sp-path.h" -#include "event-context.h" +#include "ui/tools/tool-base.h" #include "desktop-style.h" #include "svg/svg-icc-color.h" diff --git a/src/desktop.cpp b/src/desktop.cpp index 52aed1cce..cf8c60fec 100644 --- a/src/desktop.cpp +++ b/src/desktop.cpp @@ -33,7 +33,7 @@ #include <2geom/transforms.h> #include <2geom/rect.h> -#include "box3d-context.h" +#include "ui/tools/box3d-tool.h" #include "color.h" #include "desktop-events.h" #include "desktop.h" @@ -64,7 +64,7 @@ #include "message-stack.h" #include "preferences.h" #include "resource-manager.h" -#include "select-context.h" +#include "ui/tools/select-tool.h" #include "selection.h" #include "sp-item-group.h" #include "sp-item-group.h" @@ -76,7 +76,7 @@ #include "helper/action.h" //sp_action_perform // TODO those includes are only for node tool quick zoom. Remove them after fixing it. -#include "ui/tool/node-tool.h" +#include "ui/tools/node-tool.h" #include "ui/tool/control-point-selection.h" namespace Inkscape { namespace XML { class Node; }} diff --git a/src/dialogs/dialog-events.cpp b/src/dialogs/dialog-events.cpp index ac4ed58f9..a12e3eafc 100644 --- a/src/dialogs/dialog-events.cpp +++ b/src/dialogs/dialog-events.cpp @@ -27,7 +27,7 @@ #include "desktop.h" #include "inkscape-private.h" #include "preferences.h" -#include "event-context.h" +#include "ui/tools/tool-base.h" #include "dialog-events.h" diff --git a/src/draw-anchor.cpp b/src/draw-anchor.cpp index 7cd6daeb5..cd57b2461 100644 --- a/src/draw-anchor.cpp +++ b/src/draw-anchor.cpp @@ -16,8 +16,8 @@ #include "draw-anchor.h" #include "desktop.h" #include "desktop-handles.h" -#include "event-context.h" -#include "lpe-tool-context.h" +#include "ui/tools/tool-base.h" +#include "ui/tools/lpe-tool.h" #include "display/sodipodi-ctrl.h" #include "display/curve.h" #include "ui/control-manager.h" @@ -30,7 +30,7 @@ using Inkscape::ControlManager; /** * Creates an anchor object and initializes it. */ -SPDrawAnchor *sp_draw_anchor_new(Inkscape::UI::Tools::SPDrawContext *dc, SPCurve *curve, gboolean start, Geom::Point delta) +SPDrawAnchor *sp_draw_anchor_new(Inkscape::UI::Tools::FreehandBase *dc, SPCurve *curve, gboolean start, Geom::Point delta) { if (SP_IS_LPETOOL_CONTEXT(dc)) { // suppress all kinds of anchors in LPEToolContext diff --git a/src/draw-anchor.h b/src/draw-anchor.h index 2312c6c32..e7b252186 100644 --- a/src/draw-anchor.h +++ b/src/draw-anchor.h @@ -12,7 +12,7 @@ namespace Inkscape { namespace UI { namespace Tools { -class SPDrawContext; +class FreehandBase; } } @@ -24,7 +24,7 @@ struct SPCanvasItem; /// The drawing anchor. /// \todo Make this a regular knot, this will allow to set statusbar tips. struct SPDrawAnchor { - Inkscape::UI::Tools::SPDrawContext *dc; + Inkscape::UI::Tools::FreehandBase *dc; SPCurve *curve; guint start : 1; guint active : 1; @@ -33,7 +33,7 @@ struct SPDrawAnchor { }; -SPDrawAnchor *sp_draw_anchor_new(Inkscape::UI::Tools::SPDrawContext *dc, SPCurve *curve, gboolean start, +SPDrawAnchor *sp_draw_anchor_new(Inkscape::UI::Tools::FreehandBase *dc, SPCurve *curve, gboolean start, Geom::Point delta); SPDrawAnchor *sp_draw_anchor_destroy(SPDrawAnchor *anchor); SPDrawAnchor *sp_draw_anchor_test(SPDrawAnchor *anchor, Geom::Point w, gboolean activate); diff --git a/src/draw-context.cpp b/src/draw-context.cpp deleted file mode 100644 index 8fd52b83e..000000000 --- a/src/draw-context.cpp +++ /dev/null @@ -1,785 +0,0 @@ -/* - * Generic drawing context - * - * Author: - * Lauris Kaplinski - * Abhishek Sharma - * Jon A. Cruz - * - * Copyright (C) 2000 Lauris Kaplinski - * Copyright (C) 2000-2001 Ximian, Inc. - * Copyright (C) 2002 Lauris Kaplinski - * Copyright (C) 2012 Johan Engelen - * - * Released under GNU GPL, read the file 'COPYING' for more information - */ - -#define DRAW_VERBOSE - -#ifdef HAVE_CONFIG_H -# include "config.h" -#endif - -#include "live_effects/lpe-patternalongpath.h" -#include "display/canvas-bpath.h" -#include "xml/repr.h" -#include "svg/svg.h" -#include -#include "display/curve.h" -#include "desktop.h" -#include "desktop-handles.h" -#include "desktop-style.h" -#include "document.h" -#include "draw-anchor.h" -#include "macros.h" -#include "message-stack.h" -#include "pen-context.h" -#include "lpe-tool-context.h" -#include "preferences.h" -#include "selection.h" -#include "selection-chemistry.h" -#include "snap.h" -#include "sp-path.h" -#include "sp-namedview.h" -#include "live_effects/lpe-powerstroke.h" -#include "style.h" -#include "ui/control-manager.h" -#include "draw-context.h" - -#include - -using Inkscape::DocumentUndo; - -namespace Inkscape { -namespace UI { -namespace Tools { - -static void spdc_selection_changed(Inkscape::Selection *sel, SPDrawContext *dc); -static void spdc_selection_modified(Inkscape::Selection *sel, guint flags, SPDrawContext *dc); - -static void spdc_attach_selection(SPDrawContext *dc, Inkscape::Selection *sel); - -/** - * Flushes white curve(s) and additional curve into object. - * - * No cleaning of colored curves - this has to be done by caller - * No rereading of white data, so if you cannot rely on ::modified, do it in caller - */ -static void spdc_flush_white(SPDrawContext *dc, SPCurve *gc); - -static void spdc_reset_white(SPDrawContext *dc); -static void spdc_free_colors(SPDrawContext *dc); - -SPDrawContext::SPDrawContext() : ToolBase() { - this->selection = 0; - this->grab = 0; - this->anchor_statusbar = false; - - this->attach = FALSE; - - this->red_color = 0xff00007f; - this->blue_color = 0x0000ff7f; - this->green_color = 0x00ff007f; - this->red_curve_is_valid = false; - - this->red_bpath = NULL; - this->red_curve = NULL; - - this->blue_bpath = NULL; - this->blue_curve = NULL; - - this->green_bpaths = NULL; - this->green_curve = NULL; - this->green_anchor = NULL; - this->green_closed = false; - - this->white_item = NULL; - this->white_curves = NULL; - this->white_anchors = NULL; - - this->sa = NULL; - this->ea = NULL; - - this->waiting_LPE_type = Inkscape::LivePathEffect::INVALID_LPE; -} - -SPDrawContext::~SPDrawContext() { - if (this->grab) { - sp_canvas_item_ungrab(this->grab, GDK_CURRENT_TIME); - this->grab = NULL; - } - - if (this->selection) { - this->selection = NULL; - } - - spdc_free_colors(this); -} - -void SPDrawContext::setup() { - ToolBase::setup(); - - this->selection = sp_desktop_selection(desktop); - - // Connect signals to track selection changes - this->sel_changed_connection = this->selection->connectChanged( - sigc::bind(sigc::ptr_fun(&spdc_selection_changed), this) - ); - this->sel_modified_connection = this->selection->connectModified( - sigc::bind(sigc::ptr_fun(&spdc_selection_modified), this) - ); - - // Create red bpath - this->red_bpath = sp_canvas_bpath_new(sp_desktop_sketch(this->desktop), NULL); - sp_canvas_bpath_set_stroke(SP_CANVAS_BPATH(this->red_bpath), this->red_color, 1.0, SP_STROKE_LINEJOIN_MITER, SP_STROKE_LINECAP_BUTT); - - // Create red curve - this->red_curve = new SPCurve(); - - // Create blue bpath - this->blue_bpath = sp_canvas_bpath_new(sp_desktop_sketch(this->desktop), NULL); - sp_canvas_bpath_set_stroke(SP_CANVAS_BPATH(this->blue_bpath), this->blue_color, 1.0, SP_STROKE_LINEJOIN_MITER, SP_STROKE_LINECAP_BUTT); - - // Create blue curve - this->blue_curve = new SPCurve(); - - // Create green curve - this->green_curve = new SPCurve(); - - // No green anchor by default - this->green_anchor = NULL; - this->green_closed = FALSE; - - this->attach = TRUE; - spdc_attach_selection(this, this->selection); -} - -void SPDrawContext::finish() { - this->sel_changed_connection.disconnect(); - this->sel_modified_connection.disconnect(); - - if (this->grab) { - sp_canvas_item_ungrab(this->grab, GDK_CURRENT_TIME); - } - - if (this->selection) { - this->selection = NULL; - } - - spdc_free_colors(this); -} - -void SPDrawContext::set(const Inkscape::Preferences::Entry& /*value*/) { -} - -bool SPDrawContext::root_handler(GdkEvent* event) { - gint ret = FALSE; - - switch (event->type) { - case GDK_KEY_PRESS: - switch (get_group0_keyval (&event->key)) { - case GDK_KEY_Up: - case GDK_KEY_Down: - case GDK_KEY_KP_Up: - case GDK_KEY_KP_Down: - // prevent the zoom field from activation - if (!MOD__CTRL_ONLY(event)) { - ret = TRUE; - } - break; - default: - break; - } - break; - default: - break; - } - - if (!ret) { - ret = ToolBase::root_handler(event); - } - - return ret; -} - -static Glib::ustring const tool_name(SPDrawContext *dc) -{ - return ( SP_IS_PEN_CONTEXT(dc) - ? "/tools/freehand/pen" - : "/tools/freehand/pencil" ); -} - -static void spdc_paste_curve_as_freehand_shape(const SPCurve *c, SPDrawContext *dc, SPItem *item) -{ - using namespace Inkscape::LivePathEffect; - - // TODO: Don't paste path if nothing is on the clipboard - - Effect::createAndApply(PATTERN_ALONG_PATH, dc->desktop->doc(), item); - Effect* lpe = SP_LPE_ITEM(item)->getCurrentLPE(); - gchar *svgd = sp_svg_write_path(c->get_pathvector()); - static_cast(lpe)->pattern.paste_param_path(svgd); -} - -static void spdc_apply_powerstroke_shape(const std::vector & points, SPDrawContext *dc, SPItem *item) -{ - using namespace Inkscape::LivePathEffect; - - Effect::createAndApply(POWERSTROKE, dc->desktop->doc(), item); - Effect* lpe = SP_LPE_ITEM(item)->getCurrentLPE(); - static_cast(lpe)->offset_points.param_set_and_write_new_value(points); - - // write powerstroke parameters: - lpe->getRepr()->setAttribute("start_linecap_type", "zerowidth"); - lpe->getRepr()->setAttribute("end_linecap_type", "zerowidth"); - lpe->getRepr()->setAttribute("cusp_linecap_type", "round"); - lpe->getRepr()->setAttribute("sort_points", "true"); - lpe->getRepr()->setAttribute("interpolator_type", "CubicBezierJohan"); - lpe->getRepr()->setAttribute("interpolator_beta", "0.2"); -} - -static void spdc_check_for_and_apply_waiting_LPE(SPDrawContext *dc, SPItem *item, SPCurve *curve) -{ - using namespace Inkscape::LivePathEffect; - Inkscape::Preferences *prefs = Inkscape::Preferences::get(); - - if (item && SP_IS_LPE_ITEM(item)) { - if (prefs->getInt(tool_name(dc) + "/freehand-mode", 0) == 1) { - Effect::createAndApply(SPIRO, dc->desktop->doc(), item); - } - - int shape = prefs->getInt(tool_name(dc) + "/shape", 0); - bool shape_applied = false; - SPCSSAttr *css_item = sp_css_attr_from_object(item, SP_STYLE_FLAG_ALWAYS); - const char *cstroke = sp_repr_css_property(css_item, "stroke", "none"); - -#define SHAPE_LENGTH 10 -#define SHAPE_HEIGHT 10 - - switch (shape) { - case 0: - // don't apply any shape - break; - case 1: - { - // "triangle in" - std::vector points(1); - points[0] = Geom::Point(0., SHAPE_HEIGHT/2); - spdc_apply_powerstroke_shape(points, dc, item); - - shape_applied = true; - break; - } - case 2: - { - // "triangle out" - guint curve_length = curve->get_segment_count(); - std::vector points(1); - points[0] = Geom::Point((double)curve_length, SHAPE_HEIGHT/2); - spdc_apply_powerstroke_shape(points, dc, item); - - shape_applied = true; - break; - } - case 3: - { - // "ellipse" - SPCurve *c = new SPCurve(); - const double C1 = 0.552; - c->moveto(0, SHAPE_HEIGHT/2); - c->curveto(0, (1 - C1) * SHAPE_HEIGHT/2, (1 - C1) * SHAPE_LENGTH/2, 0, SHAPE_LENGTH/2, 0); - c->curveto((1 + C1) * SHAPE_LENGTH/2, 0, SHAPE_LENGTH, (1 - C1) * SHAPE_HEIGHT/2, SHAPE_LENGTH, SHAPE_HEIGHT/2); - c->curveto(SHAPE_LENGTH, (1 + C1) * SHAPE_HEIGHT/2, (1 + C1) * SHAPE_LENGTH/2, SHAPE_HEIGHT, SHAPE_LENGTH/2, SHAPE_HEIGHT); - c->curveto((1 - C1) * SHAPE_LENGTH/2, SHAPE_HEIGHT, 0, (1 + C1) * SHAPE_HEIGHT/2, 0, SHAPE_HEIGHT/2); - c->closepath(); - spdc_paste_curve_as_freehand_shape(c, dc, item); - c->unref(); - shape_applied = true; - break; - } - case 4: - { - // take shape from clipboard; TODO: catch the case where clipboard is empty - Effect::createAndApply(PATTERN_ALONG_PATH, dc->desktop->doc(), item); - Effect* lpe = SP_LPE_ITEM(item)->getCurrentLPE(); - static_cast(lpe)->pattern.on_paste_button_click(); - - shape_applied = true; - break; - } - default: - break; - } - if (shape_applied) { - // apply original stroke color as fill and unset stroke; then return - SPCSSAttr *css = sp_repr_css_attr_new(); - - if (!strcmp(cstroke, "none")){ - sp_repr_css_set_property (css, "fill", "black"); - } else { - sp_repr_css_set_property (css, "fill", cstroke); - } - sp_repr_css_set_property (css, "stroke", "none"); - sp_desktop_apply_css_recursive(item, css, true); - sp_repr_css_attr_unref(css); - return; - } - - if (dc->waiting_LPE_type != INVALID_LPE) { - Effect::createAndApply(dc->waiting_LPE_type, dc->desktop->doc(), item); - dc->waiting_LPE_type = INVALID_LPE; - - if (SP_IS_LPETOOL_CONTEXT(dc)) { - // since a geometric LPE was applied, we switch back to "inactive" mode - lpetool_context_switch_mode(SP_LPETOOL_CONTEXT(dc), INVALID_LPE); - } - } - if (SP_IS_PEN_CONTEXT(dc)) { - sp_pen_context_set_polyline_mode(SP_PEN_CONTEXT(dc)); - } - } -} - -/* - * Selection handlers - */ - -static void spdc_selection_changed(Inkscape::Selection *sel, SPDrawContext *dc) -{ - if (dc->attach) { - spdc_attach_selection(dc, sel); - } -} - -/* fixme: We have to ensure this is not delayed (Lauris) */ - -static void spdc_selection_modified(Inkscape::Selection *sel, guint /*flags*/, SPDrawContext *dc) -{ - if (dc->attach) { - spdc_attach_selection(dc, sel); - } -} - -static void spdc_attach_selection(SPDrawContext *dc, Inkscape::Selection */*sel*/) -{ - // We reset white and forget white/start/end anchors - spdc_reset_white(dc); - dc->sa = NULL; - dc->ea = NULL; - - SPItem *item = dc->selection ? dc->selection->singleItem() : NULL; - - if ( item && SP_IS_PATH(item) ) { - // Create new white data - // Item - dc->white_item = item; - - // Curve list - // We keep it in desktop coordinates to eliminate calculation errors - SPCurve *norm = SP_PATH(item)->get_curve_for_edit(); - norm->transform((dc->white_item)->i2dt_affine()); - g_return_if_fail( norm != NULL ); - dc->white_curves = g_slist_reverse(norm->split()); - norm->unref(); - - // Anchor list - for (GSList *l = dc->white_curves; l != NULL; l = l->next) { - SPCurve *c; - c = static_cast(l->data); - g_return_if_fail( c->get_segment_count() > 0 ); - if ( !c->is_closed() ) { - SPDrawAnchor *a; - a = sp_draw_anchor_new(dc, c, TRUE, *(c->first_point())); - if (a) - dc->white_anchors = g_slist_prepend(dc->white_anchors, a); - a = sp_draw_anchor_new(dc, c, FALSE, *(c->last_point())); - if (a) - dc->white_anchors = g_slist_prepend(dc->white_anchors, a); - } - } - // fixme: recalculate active anchor? - } -} - - -void spdc_endpoint_snap_rotation(ToolBase const *const ec, Geom::Point &p, Geom::Point const &o, - guint state) -{ - Inkscape::Preferences *prefs = Inkscape::Preferences::get(); - unsigned const snaps = abs(prefs->getInt("/options/rotationsnapsperpi/value", 12)); - - SnapManager &m = SP_EVENT_CONTEXT_DESKTOP(ec)->namedview->snap_manager; - m.setup(SP_EVENT_CONTEXT_DESKTOP(ec)); - - bool snap_enabled = m.snapprefs.getSnapEnabledGlobally(); - if (state & GDK_SHIFT_MASK) { - // SHIFT disables all snapping, except the angular snapping. After all, the user explicitly asked for angular - // snapping by pressing CTRL, otherwise we wouldn't have arrived here. But although we temporarily disable - // the snapping here, we must still call for a constrained snap in order to apply the constraints (i.e. round - // to the nearest angle increment) - m.snapprefs.setSnapEnabledGlobally(false); - } - - Inkscape::SnappedPoint dummy = m.constrainedAngularSnap(Inkscape::SnapCandidatePoint(p, Inkscape::SNAPSOURCE_NODE_HANDLE), boost::optional(), o, snaps); - p = dummy.getPoint(); - - if (state & GDK_SHIFT_MASK) { - m.snapprefs.setSnapEnabledGlobally(snap_enabled); // restore the original setting - } - - m.unSetup(); -} - - -void spdc_endpoint_snap_free(ToolBase const * const ec, Geom::Point& p, boost::optional &start_of_line, guint const /*state*/) -{ - SPDesktop *dt = SP_EVENT_CONTEXT_DESKTOP(ec); - SnapManager &m = dt->namedview->snap_manager; - Inkscape::Selection *selection = sp_desktop_selection (dt); - - // selection->singleItem() is the item that is currently being drawn. This item will not be snapped to (to avoid self-snapping) - // TODO: Allow snapping to the stationary parts of the item, and only ignore the last segment - - m.setup(dt, true, selection->singleItem()); - Inkscape::SnapCandidatePoint scp(p, Inkscape::SNAPSOURCE_NODE_HANDLE); - if (start_of_line) { - scp.addOrigin(*start_of_line); - } - - Inkscape::SnappedPoint sp = m.freeSnap(scp); - p = sp.getPoint(); - - m.unSetup(); -} - -static SPCurve *reverse_then_unref(SPCurve *orig) -{ - SPCurve *ret = orig->create_reverse(); - orig->unref(); - return ret; -} - -void spdc_concat_colors_and_flush(SPDrawContext *dc, gboolean forceclosed) -{ - // Concat RBG - SPCurve *c = dc->green_curve; - - // Green - dc->green_curve = new SPCurve(); - while (dc->green_bpaths) { - sp_canvas_item_destroy(SP_CANVAS_ITEM(dc->green_bpaths->data)); - dc->green_bpaths = g_slist_remove(dc->green_bpaths, dc->green_bpaths->data); - } - - // Blue - c->append_continuous(dc->blue_curve, 0.0625); - dc->blue_curve->reset(); - sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(dc->blue_bpath), NULL); - - // Red - if (dc->red_curve_is_valid) { - c->append_continuous(dc->red_curve, 0.0625); - } - dc->red_curve->reset(); - sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(dc->red_bpath), NULL); - - if (c->is_empty()) { - c->unref(); - return; - } - - // Step A - test, whether we ended on green anchor - if ( forceclosed || ( dc->green_anchor && dc->green_anchor->active ) ) { - // We hit green anchor, closing Green-Blue-Red - SP_EVENT_CONTEXT_DESKTOP(dc)->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("Path is closed.")); - c->closepath_current(); - // Closed path, just flush - spdc_flush_white(dc, c); - c->unref(); - return; - } - - // Step B - both start and end anchored to same curve - if ( dc->sa && dc->ea - && ( dc->sa->curve == dc->ea->curve ) - && ( ( dc->sa != dc->ea ) - || dc->sa->curve->is_closed() ) ) - { - // We hit bot start and end of single curve, closing paths - SP_EVENT_CONTEXT_DESKTOP(dc)->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("Closing path.")); - if (dc->sa->start && !(dc->sa->curve->is_closed()) ) { - c = reverse_then_unref(c); - } - dc->sa->curve->append_continuous(c, 0.0625); - c->unref(); - dc->sa->curve->closepath_current(); - spdc_flush_white(dc, NULL); - return; - } - - // Step C - test start - if (dc->sa) { - SPCurve *s = dc->sa->curve; - dc->white_curves = g_slist_remove(dc->white_curves, s); - if (dc->sa->start) { - s = reverse_then_unref(s); - } - s->append_continuous(c, 0.0625); - c->unref(); - c = s; - } else /* Step D - test end */ if (dc->ea) { - SPCurve *e = dc->ea->curve; - dc->white_curves = g_slist_remove(dc->white_curves, e); - if (!dc->ea->start) { - e = reverse_then_unref(e); - } - c->append_continuous(e, 0.0625); - e->unref(); - } - - - spdc_flush_white(dc, c); - - c->unref(); -} - -static void spdc_flush_white(SPDrawContext *dc, SPCurve *gc) -{ - SPCurve *c; - - if (dc->white_curves) { - g_assert(dc->white_item); - c = SPCurve::concat(dc->white_curves); - g_slist_free(dc->white_curves); - dc->white_curves = NULL; - if (gc) { - c->append(gc, FALSE); - } - } else if (gc) { - c = gc; - c->ref(); - } else { - return; - } - - // Now we have to go back to item coordinates at last - c->transform( dc->white_item - ? (dc->white_item)->dt2i_affine() - : SP_EVENT_CONTEXT_DESKTOP(dc)->dt2doc() ); - - SPDesktop *desktop = SP_EVENT_CONTEXT_DESKTOP(dc); - SPDocument *doc = sp_desktop_document(desktop); - Inkscape::XML::Document *xml_doc = doc->getReprDoc(); - - if ( c && !c->is_empty() ) { - // We actually have something to write - - bool has_lpe = false; - Inkscape::XML::Node *repr; - if (dc->white_item) { - repr = dc->white_item->getRepr(); - has_lpe = SP_LPE_ITEM(dc->white_item)->hasPathEffectRecursive(); - } else { - repr = xml_doc->createElement("svg:path"); - // Set style - sp_desktop_apply_style_tool(desktop, repr, tool_name(dc).data(), false); - } - - gchar *str = sp_svg_write_path( c->get_pathvector() ); - g_assert( str != NULL ); - if (has_lpe) - repr->setAttribute("inkscape:original-d", str); - else - repr->setAttribute("d", str); - g_free(str); - - if (!dc->white_item) { - // Attach repr - SPItem *item = SP_ITEM(desktop->currentLayer()->appendChildRepr(repr)); - - // we finished the path; now apply any waiting LPEs or freehand shapes - spdc_check_for_and_apply_waiting_LPE(dc, item, c); - - dc->selection->set(repr); - Inkscape::GC::release(repr); - item->transform = SP_ITEM(desktop->currentLayer())->i2doc_affine().inverse(); - item->doWriteTransform(item->getRepr(), item->transform, NULL, true); - item->updateRepr(); - } - - DocumentUndo::done(doc, SP_IS_PEN_CONTEXT(dc)? SP_VERB_CONTEXT_PEN : SP_VERB_CONTEXT_PENCIL, - _("Draw path")); - - // When quickly drawing several subpaths with Shift, the next subpath may be finished and - // flushed before the selection_modified signal is fired by the previous change, which - // results in the tool losing all of the selected path's curve except that last subpath. To - // fix this, we force the selection_modified callback now, to make sure the tool's curve is - // in sync immediately. - spdc_selection_modified(sp_desktop_selection(desktop), 0, dc); - } - - c->unref(); - - // Flush pending updates - doc->ensureUpToDate(); -} - -SPDrawAnchor *spdc_test_inside(SPDrawContext *dc, Geom::Point p) -{ - SPDrawAnchor *active = NULL; - - // Test green anchor - if (dc->green_anchor) { - active = sp_draw_anchor_test(dc->green_anchor, p, TRUE); - } - - for (GSList *l = dc->white_anchors; l != NULL; l = l->next) { - SPDrawAnchor *na = sp_draw_anchor_test(static_cast(l->data), p, !active); - if ( !active && na ) { - active = na; - } - } - - return active; -} - -static void spdc_reset_white(SPDrawContext *dc) -{ - if (dc->white_item) { - // We do not hold refcount - dc->white_item = NULL; - } - while (dc->white_curves) { - reinterpret_cast(dc->white_curves->data)->unref(); - dc->white_curves = g_slist_remove(dc->white_curves, dc->white_curves->data); - } - while (dc->white_anchors) { - sp_draw_anchor_destroy(static_cast(dc->white_anchors->data)); - dc->white_anchors = g_slist_remove(dc->white_anchors, dc->white_anchors->data); - } -} - -static void spdc_free_colors(SPDrawContext *dc) -{ - // Red - if (dc->red_bpath) { - sp_canvas_item_destroy(SP_CANVAS_ITEM(dc->red_bpath)); - dc->red_bpath = NULL; - } - if (dc->red_curve) { - dc->red_curve = dc->red_curve->unref(); - } - - // Blue - if (dc->blue_bpath) { - sp_canvas_item_destroy(SP_CANVAS_ITEM(dc->blue_bpath)); - dc->blue_bpath = NULL; - } - if (dc->blue_curve) { - dc->blue_curve = dc->blue_curve->unref(); - } - - // Green - while (dc->green_bpaths) { - sp_canvas_item_destroy(SP_CANVAS_ITEM(dc->green_bpaths->data)); - dc->green_bpaths = g_slist_remove(dc->green_bpaths, dc->green_bpaths->data); - } - if (dc->green_curve) { - dc->green_curve = dc->green_curve->unref(); - } - if (dc->green_anchor) { - dc->green_anchor = sp_draw_anchor_destroy(dc->green_anchor); - } - - // White - if (dc->white_item) { - // We do not hold refcount - dc->white_item = NULL; - } - while (dc->white_curves) { - reinterpret_cast(dc->white_curves->data)->unref(); - dc->white_curves = g_slist_remove(dc->white_curves, dc->white_curves->data); - } - while (dc->white_anchors) { - sp_draw_anchor_destroy(static_cast(dc->white_anchors->data)); - dc->white_anchors = g_slist_remove(dc->white_anchors, dc->white_anchors->data); - } -} - -void spdc_create_single_dot(ToolBase *ec, Geom::Point const &pt, char const *tool, guint event_state) { - g_return_if_fail(!strcmp(tool, "/tools/freehand/pen") || !strcmp(tool, "/tools/freehand/pencil")); - Glib::ustring tool_path = tool; - - SPDesktop *desktop = SP_EVENT_CONTEXT_DESKTOP(ec); - Inkscape::XML::Document *xml_doc = desktop->doc()->getReprDoc(); - Inkscape::XML::Node *repr = xml_doc->createElement("svg:path"); - repr->setAttribute("sodipodi:type", "arc"); - SPItem *item = SP_ITEM(desktop->currentLayer()->appendChildRepr(repr)); - Inkscape::GC::release(repr); - - // apply the tool's current style - sp_desktop_apply_style_tool(desktop, repr, tool, false); - - // find out stroke width (TODO: is there an easier way??) - double stroke_width = 3.0; - gchar const *style_str = NULL; - style_str = repr->attribute("style"); - if (style_str) { - SPStyle *style = sp_style_new(SP_ACTIVE_DOCUMENT); - sp_style_merge_from_style_string(style, style_str); - stroke_width = style->stroke_width.computed; - style->stroke_width.computed = 0; - sp_style_unref(style); - } - - // unset stroke and set fill color to former stroke color - gchar * str; - str = g_strdup_printf("fill:#%06x;stroke:none;", sp_desktop_get_color_tool(desktop, tool, false) >> 8); - repr->setAttribute("style", str); - g_free(str); - - // put the circle where the mouse click occurred and set the diameter to the - // current stroke width, multiplied by the amount specified in the preferences - Inkscape::Preferences *prefs = Inkscape::Preferences::get(); - - Geom::Affine const i2d (item->i2dt_affine ()); - Geom::Point pp = pt * i2d.inverse(); - double rad = 0.5 * prefs->getDouble(tool_path + "/dot-size", 3.0); - if (event_state & GDK_MOD1_MASK) { - // TODO: We vary the dot size between 0.5*rad and 1.5*rad, where rad is the dot size - // as specified in prefs. Very simple, but it might be sufficient in practice. If not, - // we need to devise something more sophisticated. - double s = g_random_double_range(-0.5, 0.5); - rad *= (1 + s); - } - if (event_state & GDK_SHIFT_MASK) { - // double the point size - rad *= 2; - } - - sp_repr_set_svg_double (repr, "sodipodi:cx", pp[Geom::X]); - sp_repr_set_svg_double (repr, "sodipodi:cy", pp[Geom::Y]); - sp_repr_set_svg_double (repr, "sodipodi:rx", rad * stroke_width); - sp_repr_set_svg_double (repr, "sodipodi:ry", rad * stroke_width); - item->updateRepr(); - - sp_desktop_selection(desktop)->set(item); - - desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("Creating single dot")); - DocumentUndo::done(sp_desktop_document(desktop), SP_VERB_NONE, _("Create single dot")); -} - -} -} -} - -/* - Local Variables: - mode:c++ - c-file-style:"stroustrup" - c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) - indent-tabs-mode:nil - fill-column:99 - End: -*/ -// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/draw-context.h b/src/draw-context.h deleted file mode 100644 index 691080b72..000000000 --- a/src/draw-context.h +++ /dev/null @@ -1,144 +0,0 @@ -#ifndef SEEN_SP_DRAW_CONTEXT_H -#define SEEN_SP_DRAW_CONTEXT_H - -/* - * Generic drawing context - * - * Author: - * Lauris Kaplinski - * - * Copyright (C) 2000 Lauris Kaplinski - * Copyright (C) 2000-2001 Ximian, Inc. - * Copyright (C) 2002 Lauris Kaplinski - * - * Released under GNU GPL - */ - -#include -#include -#include <2geom/point.h> -#include "event-context.h" -#include "live_effects/effect.h" - -/* Freehand context */ - -#define SP_DRAW_CONTEXT(obj) (dynamic_cast((Inkscape::UI::Tools::ToolBase*)obj)) -#define SP_IS_DRAW_CONTEXT(obj) (dynamic_cast((const Inkscape::UI::Tools::ToolBase*)obj) != NULL) - -struct SPDrawAnchor; -namespace Inkscape -{ - class Selection; -} - -namespace Inkscape { -namespace UI { -namespace Tools { - -class SPDrawContext : public ToolBase { -public: - SPDrawContext(); - virtual ~SPDrawContext(); - - Inkscape::Selection *selection; - SPCanvasItem *grab; - - guint attach : 1; - - guint32 red_color; - guint32 blue_color; - guint32 green_color; - - // Red - SPCanvasItem *red_bpath; - SPCurve *red_curve; - - // Blue - SPCanvasItem *blue_bpath; - SPCurve *blue_curve; - - // Green - GSList *green_bpaths; - SPCurve *green_curve; - SPDrawAnchor *green_anchor; - gboolean green_closed; // a flag meaning we hit the green anchor, so close the path on itself - - // White - SPItem *white_item; - GSList *white_curves; - GSList *white_anchors; - - // Start anchor - SPDrawAnchor *sa; - - // End anchor - SPDrawAnchor *ea; - - /* type of the LPE that is to be applied automatically to a finished path (if any) */ - Inkscape::LivePathEffect::EffectType waiting_LPE_type; - - sigc::connection sel_changed_connection; - sigc::connection sel_modified_connection; - - bool red_curve_is_valid; - - bool anchor_statusbar; - -protected: - virtual void setup(); - virtual void finish(); - virtual void set(const Inkscape::Preferences::Entry& val); - virtual bool root_handler(GdkEvent* event); -}; - -/** - * Returns FIRST active anchor (the activated one). - */ -SPDrawAnchor *spdc_test_inside(SPDrawContext *dc, Geom::Point p); - -/** - * Concats red, blue and green. - * If any anchors are defined, process these, optionally removing curves from white list - * Invoke _flush_white to write result back to object. - */ -void spdc_concat_colors_and_flush(SPDrawContext *dc, gboolean forceclosed); - -/** - * Snaps node or handle to PI/rotationsnapsperpi degree increments. - * - * @param dc draw context. - * @param p cursor point (to be changed by snapping). - * @param o origin point. - * @param state keyboard state to check if ctrl or shift was pressed. - */ -void spdc_endpoint_snap_rotation(ToolBase const *const ec, Geom::Point &p, Geom::Point const &o, guint state); - -void spdc_endpoint_snap_free(ToolBase const *ec, Geom::Point &p, boost::optional &start_of_line, guint state); - -/** - * If we have an item and a waiting LPE, apply the effect to the item - * (spiro spline mode is treated separately). - */ -void spdc_check_for_and_apply_waiting_LPE(SPDrawContext *dc, SPItem *item); - -/** - * Create a single dot represented by a circle. - */ -void spdc_create_single_dot(ToolBase *ec, Geom::Point const &pt, char const *tool, guint event_state); - -} -} -} - -#endif // SEEN_SP_DRAW_CONTEXT_H - -/* - Local Variables: - mode:c++ - c-file-style:"stroustrup" - c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) - indent-tabs-mode:nil - fill-column:99 - End: -*/ -// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/dropper-context.cpp b/src/dropper-context.cpp deleted file mode 100644 index cf2f6b6c9..000000000 --- a/src/dropper-context.cpp +++ /dev/null @@ -1,414 +0,0 @@ -/* - * Tool for picking colors from drawing - * - * Authors: - * Lauris Kaplinski - * bulia byak - * Abhishek Sharma - * - * Copyright (C) 1999-2005 Authors - * - * Released under GNU GPL, read the file 'COPYING' for more information - */ - -#ifdef HAVE_CONFIG_H -# include -#endif - -#include -#include -#include -#include <2geom/transforms.h> -#include <2geom/circle.h> - -#include "macros.h" -#include "display/canvas-bpath.h" -#include "display/canvas-arena.h" -#include "display/curve.h" -#include "display/cairo-utils.h" -#include "svg/svg-color.h" -#include "color.h" -#include "color-rgba.h" -#include "desktop-style.h" -#include "preferences.h" -#include "sp-namedview.h" -#include "sp-cursor.h" -#include "desktop.h" -#include "desktop-handles.h" -#include "selection.h" -#include "document.h" -#include "document-undo.h" - -#include "pixmaps/cursor-dropper-f.xpm" -#include "pixmaps/cursor-dropper-s.xpm" - -#include "dropper-context.h" -#include "message-context.h" -#include "verbs.h" -#include "event-context.h" - -using Inkscape::DocumentUndo; - -static GdkCursor *cursor_dropper_fill = NULL; -static GdkCursor *cursor_dropper_stroke = NULL; - -#include "tool-factory.h" - -namespace Inkscape { -namespace UI { -namespace Tools { - -namespace { - ToolBase* createDropperContext() { - return new DropperTool(); - } - - bool dropperContextRegistered = ToolFactory::instance().registerObject("/tools/dropper", createDropperContext); -} - -const std::string& DropperTool::getPrefsPath() { - return DropperTool::prefsPath; -} - -const std::string DropperTool::prefsPath = "/tools/dropper"; - -DropperTool::DropperTool() : ToolBase() { - this->R = 0; - this->G = 0; - this->B = 0; - this->alpha = 0; - this->dragging = false; - - this->grabbed = 0; - this->area = 0; - this->centre = Geom::Point(0, 0); - - this->cursor_shape = cursor_dropper_f_xpm; - this->hot_x = 7; - this->hot_y = 7; - - cursor_dropper_fill = sp_cursor_new_from_xpm(cursor_dropper_f_xpm , 7, 7); - cursor_dropper_stroke = sp_cursor_new_from_xpm(cursor_dropper_s_xpm , 7, 7); -} - -DropperTool::~DropperTool() { -} - -void DropperTool::setup() { - ToolBase::setup(); - - /* TODO: have a look at CalligraphicTool::setup where the same is done.. generalize? */ - Geom::PathVector path; - Geom::Circle(0, 0, 1).getPath(path); - - SPCurve *c = new SPCurve(path); - - this->area = sp_canvas_bpath_new(sp_desktop_controls(this->desktop), c); - - c->unref(); - - sp_canvas_bpath_set_fill(SP_CANVAS_BPATH(this->area), 0x00000000,(SPWindRule)0); - sp_canvas_bpath_set_stroke(SP_CANVAS_BPATH(this->area), 0x0000007f, 1.0, SP_STROKE_LINEJOIN_MITER, SP_STROKE_LINECAP_BUTT); - sp_canvas_item_hide(this->area); - - Inkscape::Preferences *prefs = Inkscape::Preferences::get(); - - if (prefs->getBool("/tools/dropper/selcue")) { - this->enableSelectionCue(); - } - - if (prefs->getBool("/tools/dropper/gradientdrag")) { - this->enableGrDrag(); - } -} - -void DropperTool::finish() { - this->enableGrDrag(false); - - if (this->grabbed) { - sp_canvas_item_ungrab(this->grabbed, GDK_CURRENT_TIME); - this->grabbed = NULL; - } - - if (this->area) { - sp_canvas_item_destroy(this->area); - this->area = NULL; - } - - if (cursor_dropper_fill) { -#if GTK_CHECK_VERSION(3,0,0) - g_object_unref(cursor_dropper_fill); -#else - gdk_cursor_unref (cursor_dropper_fill); -#endif - cursor_dropper_fill = NULL; - } - - if (cursor_dropper_stroke) { -#if GTK_CHECK_VERSION(3,0,0) - g_object_unref(cursor_dropper_stroke); -#else - gdk_cursor_unref (cursor_dropper_stroke); -#endif - cursor_dropper_fill = NULL; - } -} - -/** - * Returns the current dropper context color. - */ -guint32 DropperTool::get_color() { - Inkscape::Preferences *prefs = Inkscape::Preferences::get(); - - int pick = prefs->getInt("/tools/dropper/pick", SP_DROPPER_PICK_VISIBLE); - bool setalpha = prefs->getBool("/tools/dropper/setalpha", true); - - return SP_RGBA32_F_COMPOSE(this->R, - this->G, - this->B, - (pick == SP_DROPPER_PICK_ACTUAL && setalpha) ? this->alpha : 1.0); -} - -bool DropperTool::root_handler(GdkEvent* event) { - Inkscape::Preferences *prefs = Inkscape::Preferences::get(); - - int ret = FALSE; - - int pick = prefs->getInt("/tools/dropper/pick", SP_DROPPER_PICK_VISIBLE); - bool setalpha = prefs->getBool("/tools/dropper/setalpha", true); - - switch (event->type) { - case GDK_BUTTON_PRESS: - if (event->button.button == 1 && !this->space_panning) { - this->centre = Geom::Point(event->button.x, event->button.y); - this->dragging = true; - ret = TRUE; - } - - sp_canvas_item_grab(SP_CANVAS_ITEM(desktop->acetate), - GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK | GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK, - NULL, event->button.time); - this->grabbed = SP_CANVAS_ITEM(desktop->acetate); - - break; - case GDK_MOTION_NOTIFY: - if (event->motion.state & GDK_BUTTON2_MASK || event->motion.state & GDK_BUTTON3_MASK) { - // pass on middle and right drag - ret = FALSE; - break; - } else if (!this->space_panning) { - // otherwise, constantly calculate color no matter is any button pressed or not - - // If one time pick with stroke set the pixmap - if (prefs->getBool("/tools/dropper/onetimepick", false) && prefs->getInt("/dialogs/fillstroke/page", 0) == 1) { - //TODO Only set when not set already - GdkWindow* window = gtk_widget_get_window(GTK_WIDGET(sp_desktop_canvas(desktop))); - gdk_window_set_cursor(window, cursor_dropper_stroke); - } - - double rw = 0.0; - double R(0), G(0), B(0), A(0); - - if (this->dragging) { - // calculate average - - // radius - rw = std::min(Geom::L2(Geom::Point(event->button.x, event->button.y) - this->centre), 400.0); - - if (rw == 0) { // happens sometimes, little idea why... - break; - } - - Geom::Point const cd = desktop->w2d(this->centre); - Geom::Affine const w2dt = desktop->w2d(); - const double scale = rw * w2dt.descrim(); - Geom::Affine const sm( Geom::Scale(scale, scale) * Geom::Translate(cd) ); - sp_canvas_item_affine_absolute(this->area, sm); - sp_canvas_item_show(this->area); - - /* Get buffer */ - Geom::Rect r(this->centre, this->centre); - r.expandBy(rw); - if (!r.hasZeroArea()) { - Geom::IntRect area = r.roundOutwards(); - cairo_surface_t *s = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, area.width(), area.height()); - sp_canvas_arena_render_surface(SP_CANVAS_ARENA(sp_desktop_drawing(desktop)), s, area); - ink_cairo_surface_average_color_premul(s, R, G, B, A); - cairo_surface_destroy(s); - } - } else { - // pick single pixel - Geom::IntRect area = Geom::IntRect::from_xywh(floor(event->button.x), floor(event->button.y), 1, 1); - cairo_surface_t *s = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 1, 1); - sp_canvas_arena_render_surface(SP_CANVAS_ARENA(sp_desktop_drawing(desktop)), s, area); - ink_cairo_surface_average_color_premul(s, R, G, B, A); - cairo_surface_destroy(s); - } - - if (pick == SP_DROPPER_PICK_VISIBLE) { - // compose with page color - guint32 bg = sp_desktop_namedview(desktop)->pagecolor; - R = R + (SP_RGBA32_R_F(bg)) * (1 - A); - G = G + (SP_RGBA32_G_F(bg)) * (1 - A); - B = B + (SP_RGBA32_B_F(bg)) * (1 - A); - A = 1.0; - } else { - // un-premultiply color channels - if (A > 0) { - R /= A; - G /= A; - B /= A; - } - } - - if (fabs(A) < 1e-4) { - A = 0; // suppress exponentials, CSS does not allow that - } - - // remember color - this->R = R; - this->G = G; - this->B = B; - this->alpha = A; - - // status message - double alpha_to_set = setalpha? this->alpha : 1.0; - guint32 c32 = SP_RGBA32_F_COMPOSE(R, G, B, alpha_to_set); - - gchar c[64]; - sp_svg_write_color(c, sizeof(c), c32); - - // alpha of color under cursor, to show in the statusbar - // locale-sensitive printf is OK, since this goes to the UI, not into SVG - gchar *alpha = g_strdup_printf(_(" alpha %.3g"), alpha_to_set); - // where the color is picked, to show in the statusbar - gchar *where = this->dragging ? g_strdup_printf(_(", averaged with radius %d"), (int) rw) : g_strdup_printf("%s", _(" under cursor")); - // message, to show in the statusbar - const gchar *message = this->dragging ? _("Release mouse to set color.") : _("Click to set fill, Shift+click to set stroke; drag to average color in area; with Alt to pick inverse color; Ctrl+C to copy the color under mouse to clipboard"); - - this->defaultMessageContext()->setF( - Inkscape::NORMAL_MESSAGE, - "%s%s%s. %s", c, - (pick == SP_DROPPER_PICK_VISIBLE) ? "" : alpha, where, message); - - g_free(where); - g_free(alpha); - - ret = TRUE; - } - break; - - case GDK_BUTTON_RELEASE: - if (event->button.button == 1 && !this->space_panning) { - sp_canvas_item_hide(this->area); - this->dragging = false; - - if (this->grabbed) { - sp_canvas_item_ungrab(this->grabbed, event->button.time); - this->grabbed = NULL; - } - - double alpha_to_set = setalpha? this->alpha : 1.0; - - bool fill = !(event->button.state & GDK_SHIFT_MASK); // Stroke if Shift key held - - if (prefs->getBool("/tools/dropper/onetimepick", false)) { - // "One time" pick from Fill/Stroke dialog stroke page, always apply fill or stroke (ignore key) - fill = (prefs->getInt("/dialogs/fillstroke/page", 0) == 0) ? true : false; - } - - // do the actual color setting - sp_desktop_set_color(desktop, - (event->button.state & GDK_MOD1_MASK)? - ColorRGBA(1 - this->R, 1 - this->G, 1 - this->B, alpha_to_set) : ColorRGBA(this->R, this->G, this->B, alpha_to_set), - false, fill); - - // REJON: set aux. toolbar input to hex color! - - if (event->button.state & GDK_SHIFT_MASK) { - GdkWindow* window = gtk_widget_get_window(GTK_WIDGET(sp_desktop_canvas(desktop))); - gdk_window_set_cursor(window, cursor_dropper_stroke); - } - - if (!(sp_desktop_selection(desktop)->isEmpty())) { - DocumentUndo::done(sp_desktop_document(desktop), SP_VERB_CONTEXT_DROPPER, - _("Set picked color")); - } - - if (prefs->getBool("/tools/dropper/onetimepick", false)) { - prefs->setBool("/tools/dropper/onetimepick", false); - sp_toggle_dropper(desktop); - } - - ret = TRUE; - } - break; - - case GDK_KEY_PRESS: - switch (get_group0_keyval(&event->key)) { - case GDK_KEY_Up: - case GDK_KEY_Down: - case GDK_KEY_KP_Up: - case GDK_KEY_KP_Down: - // prevent the zoom field from activation - if (!MOD__CTRL_ONLY(event)) { - ret = TRUE; - } - break; - - case GDK_KEY_Escape: - sp_desktop_selection(desktop)->clear(); - case GDK_KEY_Shift_L: - case GDK_KEY_Shift_R: - if (!desktop->isWaitingCursor() && !prefs->getBool("/tools/dropper/onetimepick", false)) { - GdkWindow* window = gtk_widget_get_window(GTK_WIDGET(sp_desktop_canvas(desktop))); - gdk_window_set_cursor(window, cursor_dropper_stroke); - } - - break; - default: - break; - } - break; - - case GDK_KEY_RELEASE: - switch (get_group0_keyval(&event->key)) { - case GDK_KEY_Shift_L: - case GDK_KEY_Shift_R: - if (!desktop->isWaitingCursor() && !prefs->getBool("/tools/dropper/onetimepick", false)) { - GdkWindow* window = gtk_widget_get_window(GTK_WIDGET(sp_desktop_canvas(desktop))); - gdk_window_set_cursor(window, cursor_dropper_fill); - } - break; - - default: - break; - } - break; - - default: - break; - } - - if (!ret) { - ret = ToolBase::root_handler(event); - } - - return ret; -} - -} -} -} - - -/* - Local Variables: - mode:c++ - c-file-style:"stroustrup" - c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) - indent-tabs-mode:nil - fill-column:99 - End: -*/ -// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 : diff --git a/src/dropper-context.h b/src/dropper-context.h deleted file mode 100644 index a56720693..000000000 --- a/src/dropper-context.h +++ /dev/null @@ -1,73 +0,0 @@ -#ifndef __SP_DROPPER_CONTEXT_H__ -#define __SP_DROPPER_CONTEXT_H__ - -/* - * Tool for picking colors from drawing - * - * Authors: - * Lauris Kaplinski - * - * Copyright (C) 1999-2002 Authors - * - * Released under GNU GPL, read the file 'COPYING' for more information - */ - -#include "event-context.h" - -#define SP_DROPPER_CONTEXT(obj) (dynamic_cast((Inkscape::UI::Tools::ToolBase*)obj)) -#define SP_IS_DROPPER_CONTEXT(obj) (dynamic_cast((const Inkscape::UI::Tools::ToolBase*)obj) != NULL) - -enum { - SP_DROPPER_PICK_VISIBLE, - SP_DROPPER_PICK_ACTUAL -}; - -namespace Inkscape { -namespace UI { -namespace Tools { - -class DropperTool : public ToolBase { -public: - DropperTool(); - virtual ~DropperTool(); - - static const std::string prefsPath; - - virtual const std::string& getPrefsPath(); - - guint32 get_color(); - -protected: - virtual void setup(); - virtual void finish(); - virtual bool root_handler(GdkEvent* event); - -private: - double R; - double G; - double B; - double alpha; - - bool dragging; - - SPCanvasItem* grabbed; - SPCanvasItem* area; - Geom::Point centre; -}; - -} -} -} - -#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:fileencoding=utf-8:textwidth=99 : diff --git a/src/dyna-draw-context.cpp b/src/dyna-draw-context.cpp deleted file mode 100644 index 8fc777712..000000000 --- a/src/dyna-draw-context.cpp +++ /dev/null @@ -1,1216 +0,0 @@ -/* - * Handwriting-like drawing mode - * - * Authors: - * Mitsuru Oka - * Lauris Kaplinski - * bulia byak - * MenTaLguY - * Abhishek Sharma - * Jon A. Cruz - * - * The original dynadraw code: - * Paul Haeberli - * - * Copyright (C) 1998 The Free Software Foundation - * Copyright (C) 1999-2005 authors - * Copyright (C) 2001-2002 Ximian, Inc. - * Copyright (C) 2005-2007 bulia byak - * Copyright (C) 2006 MenTaLguY - * - * Released under GNU GPL, read the file 'COPYING' for more information - */ - -#define noDYNA_DRAW_VERBOSE - -#include "config.h" - -#include -#include -#include -#include -#include -#include - -#include "svg/svg.h" -#include "display/canvas-bpath.h" -#include "display/cairo-utils.h" -#include <2geom/math-utils.h> -#include <2geom/pathvector.h> -#include <2geom/bezier-utils.h> -#include <2geom/circle.h> -#include "display/curve.h" -#include -#include "macros.h" -#include "document.h" -#include "document-undo.h" -#include "selection.h" -#include "desktop.h" -#include "desktop-events.h" -#include "desktop-handles.h" -#include "desktop-style.h" -#include "message-context.h" -#include "preferences.h" -#include "pixmaps/cursor-calligraphy.xpm" -#include "xml/repr.h" -#include "context-fns.h" -#include "sp-item.h" -#include "inkscape.h" -#include "color.h" -#include "splivarot.h" -#include "sp-item-group.h" -#include "sp-shape.h" -#include "sp-path.h" -#include "sp-text.h" -#include "display/sp-canvas.h" -#include "display/canvas-bpath.h" -#include "display/canvas-arena.h" -#include "livarot/Shape.h" -#include "verbs.h" - -#include "dyna-draw-context.h" - -using Inkscape::DocumentUndo; - -#define DDC_RED_RGBA 0xff0000ff - -#define TOLERANCE_CALLIGRAPHIC 0.1 - -#define DYNA_EPSILON 0.5e-6 -#define DYNA_EPSILON_START 0.5e-2 -#define DYNA_VEL_START 1e-5 - -#define DYNA_MIN_WIDTH 1.0e-6 - -#include "tool-factory.h" - -namespace Inkscape { -namespace UI { -namespace Tools { - -static void add_cap(SPCurve *curve, Geom::Point const &from, Geom::Point const &to, double rounding); - -namespace { - ToolBase* createCalligraphicContext() { - return new CalligraphicTool(); - } - - bool calligraphicContextRegistered = ToolFactory::instance().registerObject("/tools/calligraphic", createCalligraphicContext); -} - -const std::string& CalligraphicTool::getPrefsPath() { - return CalligraphicTool::prefsPath; -} - - -const std::string CalligraphicTool::prefsPath = "/tools/calligraphic"; - -CalligraphicTool::CalligraphicTool() : SPCommonContext() { - this->cursor_shape = cursor_calligraphy_xpm; - this->hot_x = 4; - this->hot_y = 4; - - this->vel_thin = 0.1; - this->flatness = 0.9; - this->cap_rounding = 0.0; - - this->abs_width = false; - this->keep_selected = true; - - this->hatch_spacing = 0; - this->hatch_spacing_step = 0; - - this->hatch_last_nearest = Geom::Point(0,0); - this->hatch_last_pointer = Geom::Point(0,0); - this->hatch_escaped = false; - this->hatch_area = NULL; - this->hatch_item = NULL; - this->hatch_livarot_path = NULL; - - this->trace_bg = false; - this->just_started_drawing = false; -} - -CalligraphicTool::~CalligraphicTool() { - if (this->hatch_area) { - sp_canvas_item_destroy(this->hatch_area); - this->hatch_area = NULL; - } -} - -void CalligraphicTool::setup() { - SPCommonContext::setup(); - - this->accumulated = new SPCurve(); - this->currentcurve = new SPCurve(); - - this->cal1 = new SPCurve(); - this->cal2 = new SPCurve(); - - this->currentshape = sp_canvas_item_new(sp_desktop_sketch(this->desktop), SP_TYPE_CANVAS_BPATH, NULL); - sp_canvas_bpath_set_fill(SP_CANVAS_BPATH(this->currentshape), DDC_RED_RGBA, SP_WIND_RULE_EVENODD); - sp_canvas_bpath_set_stroke(SP_CANVAS_BPATH(this->currentshape), 0x00000000, 1.0, SP_STROKE_LINEJOIN_MITER, SP_STROKE_LINECAP_BUTT); - - /* fixme: Cannot we cascade it to root more clearly? */ - g_signal_connect(G_OBJECT(this->currentshape), "event", G_CALLBACK(sp_desktop_root_handler), this->desktop); - - { - /* TODO: have a look at DropperTool::setup where the same is done.. generalize? */ - Geom::PathVector path; - Geom::Circle(0, 0, 1).getPath(path); - - SPCurve *c = new SPCurve(path); - - this->hatch_area = sp_canvas_bpath_new(sp_desktop_controls(this->desktop), c); - - c->unref(); - - sp_canvas_bpath_set_fill(SP_CANVAS_BPATH(this->hatch_area), 0x00000000,(SPWindRule)0); - sp_canvas_bpath_set_stroke(SP_CANVAS_BPATH(this->hatch_area), 0x0000007f, 1.0, SP_STROKE_LINEJOIN_MITER, SP_STROKE_LINECAP_BUTT); - sp_canvas_item_hide(this->hatch_area); - } - - sp_event_context_read(this, "mass"); - sp_event_context_read(this, "wiggle"); - sp_event_context_read(this, "angle"); - sp_event_context_read(this, "width"); - sp_event_context_read(this, "thinning"); - sp_event_context_read(this, "tremor"); - sp_event_context_read(this, "flatness"); - sp_event_context_read(this, "tracebackground"); - sp_event_context_read(this, "usepressure"); - sp_event_context_read(this, "usetilt"); - sp_event_context_read(this, "abs_width"); - sp_event_context_read(this, "keep_selected"); - sp_event_context_read(this, "cap_rounding"); - - this->is_drawing = false; - - Inkscape::Preferences *prefs = Inkscape::Preferences::get(); - if (prefs->getBool("/tools/calligraphic/selcue")) { - this->enableSelectionCue(); - } -} - -void CalligraphicTool::set(const Inkscape::Preferences::Entry& val) { - Glib::ustring path = val.getEntryName(); - - if (path == "tracebackground") { - this->trace_bg = val.getBool(); - } else if (path == "keep_selected") { - this->keep_selected = val.getBool(); - } else { - //pass on up to parent class to handle common attributes. - SPCommonContext::set(val); - } - - //g_print("DDC: %g %g %g %g\n", ddc->mass, ddc->drag, ddc->angle, ddc->width); -} - -static double -flerp(double f0, double f1, double p) -{ - return f0 + ( f1 - f0 ) * p; -} - -///* Get normalized point */ -//Geom::Point CalligraphicTool::getNormalizedPoint(Geom::Point v) const { -// Geom::Rect drect = desktop->get_display_area(); -// -// double const max = MAX ( drect.dimensions()[Geom::X], drect.dimensions()[Geom::Y] ); -// -// return Geom::Point(( v[Geom::X] - drect.min()[Geom::X] ) / max, ( v[Geom::Y] - drect.min()[Geom::Y] ) / max); -//} -// -///* Get view point */ -//Geom::Point CalligraphicTool::getViewPoint(Geom::Point n) const { -// Geom::Rect drect = desktop->get_display_area(); -// -// double const max = MAX ( drect.dimensions()[Geom::X], drect.dimensions()[Geom::Y] ); -// -// return Geom::Point(n[Geom::X] * max + drect.min()[Geom::X], n[Geom::Y] * max + drect.min()[Geom::Y]); -//} - -void CalligraphicTool::reset(Geom::Point p) { - this->last = this->cur = this->getNormalizedPoint(p); - - this->vel = Geom::Point(0,0); - this->vel_max = 0; - this->acc = Geom::Point(0,0); - this->ang = Geom::Point(0,0); - this->del = Geom::Point(0,0); -} - -void CalligraphicTool::extinput(GdkEvent *event) { - if (gdk_event_get_axis (event, GDK_AXIS_PRESSURE, &this->pressure)) { - this->pressure = CLAMP (this->pressure, DDC_MIN_PRESSURE, DDC_MAX_PRESSURE); - } else { - this->pressure = DDC_DEFAULT_PRESSURE; - } - - if (gdk_event_get_axis (event, GDK_AXIS_XTILT, &this->xtilt)) { - this->xtilt = CLAMP (this->xtilt, DDC_MIN_TILT, DDC_MAX_TILT); - } else { - this->xtilt = DDC_DEFAULT_TILT; - } - - if (gdk_event_get_axis (event, GDK_AXIS_YTILT, &this->ytilt)) { - this->ytilt = CLAMP (this->ytilt, DDC_MIN_TILT, DDC_MAX_TILT); - } else { - this->ytilt = DDC_DEFAULT_TILT; - } -} - - -bool CalligraphicTool::apply(Geom::Point p) { - Geom::Point n = this->getNormalizedPoint(p); - - /* Calculate mass and drag */ - double const mass = flerp(1.0, 160.0, this->mass); - double const drag = flerp(0.0, 0.5, this->drag * this->drag); - - /* Calculate force and acceleration */ - Geom::Point force = n - this->cur; - - // If force is below the absolute threshold DYNA_EPSILON, - // or we haven't yet reached DYNA_VEL_START (i.e. at the beginning of stroke) - // _and_ the force is below the (higher) DYNA_EPSILON_START threshold, - // discard this move. - // This prevents flips, blobs, and jerks caused by microscopic tremor of the tablet pen, - // especially bothersome at the start of the stroke where we don't yet have the inertia to - // smooth them out. - if ( Geom::L2(force) < DYNA_EPSILON || (this->vel_max < DYNA_VEL_START && Geom::L2(force) < DYNA_EPSILON_START)) { - return FALSE; - } - - this->acc = force / mass; - - /* Calculate new velocity */ - this->vel += this->acc; - - if (Geom::L2(this->vel) > this->vel_max) - this->vel_max = Geom::L2(this->vel); - - /* Calculate angle of drawing tool */ - - double a1; - if (this->usetilt) { - // 1a. calculate nib angle from input device tilt: - gdouble length = std::sqrt(this->xtilt*this->xtilt + this->ytilt*this->ytilt);; - - if (length > 0) { - Geom::Point ang1 = Geom::Point(this->ytilt/length, this->xtilt/length); - a1 = atan2(ang1); - } - else - a1 = 0.0; - } - else { - // 1b. fixed dc->angle (absolutely flat nib): - double const radians = ( (this->angle - 90) / 180.0 ) * M_PI; - Geom::Point ang1 = Geom::Point(-sin(radians), cos(radians)); - a1 = atan2(ang1); - } - - // 2. perpendicular to dc->vel (absolutely non-flat nib): - gdouble const mag_vel = Geom::L2(this->vel); - if ( mag_vel < DYNA_EPSILON ) { - return FALSE; - } - Geom::Point ang2 = Geom::rot90(this->vel) / mag_vel; - - // 3. Average them using flatness parameter: - // calculate angles - double a2 = atan2(ang2); - // flip a2 to force it to be in the same half-circle as a1 - bool flipped = false; - if (fabs (a2-a1) > 0.5*M_PI) { - a2 += M_PI; - flipped = true; - } - // normalize a2 - if (a2 > M_PI) - a2 -= 2*M_PI; - if (a2 < -M_PI) - a2 += 2*M_PI; - // find the flatness-weighted bisector angle, unflip if a2 was flipped - // FIXME: when dc->vel is oscillating around the fixed angle, the new_ang flips back and forth. How to avoid this? - double new_ang = a1 + (1 - this->flatness) * (a2 - a1) - (flipped? M_PI : 0); - - // Try to detect a sudden flip when the new angle differs too much from the previous for the - // current velocity; in that case discard this move - double angle_delta = Geom::L2(Geom::Point (cos (new_ang), sin (new_ang)) - this->ang); - if ( angle_delta / Geom::L2(this->vel) > 4000 ) { - return FALSE; - } - - // convert to point - this->ang = Geom::Point (cos (new_ang), sin (new_ang)); - -// g_print ("force %g acc %g vel_max %g vel %g a1 %g a2 %g new_ang %g\n", Geom::L2(force), Geom::L2(dc->acc), dc->vel_max, Geom::L2(dc->vel), a1, a2, new_ang); - - /* Apply drag */ - this->vel *= 1.0 - drag; - - /* Update position */ - this->last = this->cur; - this->cur += this->vel; - - return TRUE; -} - -void CalligraphicTool::brush() { - g_assert( this->npoints >= 0 && this->npoints < SAMPLING_SIZE ); - - // How much velocity thins strokestyle - double vel_thin = flerp (0, 160, this->vel_thin); - - // Influence of pressure on thickness - double pressure_thick = (this->usepressure ? this->pressure : 1.0); - - // get the real brush point, not the same as pointer (affected by hatch tracking and/or mass - // drag) - Geom::Point brush = this->getViewPoint(this->cur); - Geom::Point brush_w = SP_EVENT_CONTEXT(this)->desktop->d2w(brush); - - double trace_thick = 1; - if (this->trace_bg) { - // pick single pixel - double R, G, B, A; - Geom::IntRect area = Geom::IntRect::from_xywh(brush_w.floor(), Geom::IntPoint(1, 1)); - cairo_surface_t *s = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 1, 1); - sp_canvas_arena_render_surface(SP_CANVAS_ARENA(sp_desktop_drawing(SP_EVENT_CONTEXT(this)->desktop)), s, area); - ink_cairo_surface_average_color_premul(s, R, G, B, A); - cairo_surface_destroy(s); - double max = MAX (MAX (R, G), B); - double min = MIN (MIN (R, G), B); - double L = A * (max + min)/2 + (1 - A); // blend with white bg - trace_thick = 1 - L; - //g_print ("L %g thick %g\n", L, trace_thick); - } - - double width = (pressure_thick * trace_thick - vel_thin * Geom::L2(this->vel)) * this->width; - - double tremble_left = 0, tremble_right = 0; - if (this->tremor > 0) { - // obtain two normally distributed random variables, using polar Box-Muller transform - double x1, x2, w, y1, y2; - do { - x1 = 2.0 * g_random_double_range(0,1) - 1.0; - x2 = 2.0 * g_random_double_range(0,1) - 1.0; - w = x1 * x1 + x2 * x2; - } while ( w >= 1.0 ); - w = sqrt( (-2.0 * log( w ) ) / w ); - y1 = x1 * w; - y2 = x2 * w; - - // deflect both left and right edges randomly and independently, so that: - // (1) dc->tremor=1 corresponds to sigma=1, decreasing dc->tremor narrows the bell curve; - // (2) deflection depends on width, but is upped for small widths for better visual uniformity across widths; - // (3) deflection somewhat depends on speed, to prevent fast strokes looking - // comparatively smooth and slow ones excessively jittery - tremble_left = (y1)*this->tremor * (0.15 + 0.8*width) * (0.35 + 14*Geom::L2(this->vel)); - tremble_right = (y2)*this->tremor * (0.15 + 0.8*width) * (0.35 + 14*Geom::L2(this->vel)); - } - - if ( width < 0.02 * this->width ) { - width = 0.02 * this->width; - } - - double dezoomify_factor = 0.05 * 1000; - if (!this->abs_width) { - dezoomify_factor /= SP_EVENT_CONTEXT(this)->desktop->current_zoom(); - } - - Geom::Point del_left = dezoomify_factor * (width + tremble_left) * this->ang; - Geom::Point del_right = dezoomify_factor * (width + tremble_right) * this->ang; - - this->point1[this->npoints] = brush + del_left; - this->point2[this->npoints] = brush - del_right; - - this->del = 0.5*(del_left + del_right); - - this->npoints++; -} - -static void -sp_ddc_update_toolbox (SPDesktop *desktop, const gchar *id, double value) -{ - desktop->setToolboxAdjustmentValue (id, value); -} - -void CalligraphicTool::cancel() { - this->dragging = false; - this->is_drawing = false; - - sp_canvas_item_ungrab(SP_CANVAS_ITEM(desktop->acetate), 0); - - /* Remove all temporary line segments */ - while (this->segments) { - sp_canvas_item_destroy(SP_CANVAS_ITEM(this->segments->data)); - this->segments = g_slist_remove(this->segments, this->segments->data); - } - - /* reset accumulated curve */ - this->accumulated->reset(); - this->clear_current(); - - if (this->repr) { - this->repr = NULL; - } -} - -bool CalligraphicTool::root_handler(GdkEvent* event) { - gint ret = FALSE; - - switch (event->type) { - case GDK_BUTTON_PRESS: - if (event->button.button == 1 && !this->space_panning) { - if (Inkscape::have_viable_layer(desktop, this->message_context) == false) { - return TRUE; - } - - this->accumulated->reset(); - - if (this->repr) { - this->repr = NULL; - } - - /* initialize first point */ - this->npoints = 0; - - sp_canvas_item_grab(SP_CANVAS_ITEM(desktop->acetate), - ( GDK_KEY_PRESS_MASK | - GDK_BUTTON_RELEASE_MASK | - GDK_POINTER_MOTION_MASK | - GDK_BUTTON_PRESS_MASK ), - NULL, - event->button.time); - - ret = TRUE; - - desktop->canvas->forceFullRedrawAfterInterruptions(3); - this->is_drawing = true; - this->just_started_drawing = true; - } - break; - case GDK_MOTION_NOTIFY: - { - Geom::Point const motion_w(event->motion.x, - event->motion.y); - Geom::Point motion_dt(desktop->w2d(motion_w)); - this->extinput(event); - - this->message_context->clear(); - - // for hatching: - double hatch_dist = 0; - Geom::Point hatch_unit_vector(0,0); - Geom::Point nearest(0,0); - Geom::Point pointer(0,0); - Geom::Affine motion_to_curve(Geom::identity()); - - if (event->motion.state & GDK_CONTROL_MASK) { // hatching - sense the item - - SPItem *selected = sp_desktop_selection(desktop)->singleItem(); - if (selected && (SP_IS_SHAPE(selected) || SP_IS_TEXT(selected))) { - // One item selected, and it's a path; - // let's try to track it as a guide - - if (selected != this->hatch_item) { - this->hatch_item = selected; - if (this->hatch_livarot_path) - delete this->hatch_livarot_path; - this->hatch_livarot_path = Path_for_item (this->hatch_item, true, true); - this->hatch_livarot_path->ConvertWithBackData(0.01); - } - - // calculate pointer point in the guide item's coords - motion_to_curve = selected->dt2i_affine() * selected->i2doc_affine(); - pointer = motion_dt * motion_to_curve; - - // calculate the nearest point on the guide path - boost::optional position = get_nearest_position_on_Path(this->hatch_livarot_path, pointer); - nearest = get_point_on_Path(this->hatch_livarot_path, position->piece, position->t); - - - // distance from pointer to nearest - hatch_dist = Geom::L2(pointer - nearest); - // unit-length vector - hatch_unit_vector = (pointer - nearest)/hatch_dist; - - this->message_context->set(Inkscape::NORMAL_MESSAGE, _("Guide path selected; start drawing along the guide with Ctrl")); - } else { - this->message_context->set(Inkscape::NORMAL_MESSAGE, _("Select a guide path to track with Ctrl")); - } - } - - if ( this->is_drawing && (event->motion.state & GDK_BUTTON1_MASK) && !this->space_panning) { - this->dragging = TRUE; - - if (event->motion.state & GDK_CONTROL_MASK && this->hatch_item) { // hatching - -#define HATCH_VECTOR_ELEMENTS 12 -#define INERTIA_ELEMENTS 24 -#define SPEED_ELEMENTS 12 -#define SPEED_MIN 0.3 -#define SPEED_NORMAL 0.35 -#define INERTIA_FORCE 0.5 - - // speed is the movement of the nearest point along the guide path, divided by - // the movement of the pointer at the same period; it is averaged for the last - // SPEED_ELEMENTS motion events. Normally, as you track the guide path, speed - // is about 1, i.e. the nearest point on the path is moved by about the same - // distance as the pointer. If the speed starts to decrease, we are losing - // contact with the guide; if it drops below SPEED_MIN, we are on our own and - // not attracted to guide anymore. Most often this happens when you have - // tracked to the end of a guide calligraphic stroke and keep moving - // further. We try to handle this situation gracefully: not stick with the - // guide forever but let go of it smoothly and without sharp jerks (non-zero - // mass recommended; with zero mass, jerks are still quite noticeable). - - double speed = 1; - if (Geom::L2(this->hatch_last_nearest) != 0) { - // the distance nearest moved since the last motion event - double nearest_moved = Geom::L2(nearest - this->hatch_last_nearest); - // the distance pointer moved since the last motion event - double pointer_moved = Geom::L2(pointer - this->hatch_last_pointer); - // store them in stacks limited to SPEED_ELEMENTS - this->hatch_nearest_past.push_front(nearest_moved); - if (this->hatch_nearest_past.size() > SPEED_ELEMENTS) - this->hatch_nearest_past.pop_back(); - this->hatch_pointer_past.push_front(pointer_moved); - if (this->hatch_pointer_past.size() > SPEED_ELEMENTS) - this->hatch_pointer_past.pop_back(); - - // If the stacks are full, - if (this->hatch_nearest_past.size() == SPEED_ELEMENTS) { - // calculate the sums of all stored movements - double nearest_sum = std::accumulate (this->hatch_nearest_past.begin(), this->hatch_nearest_past.end(), 0.0); - double pointer_sum = std::accumulate (this->hatch_pointer_past.begin(), this->hatch_pointer_past.end(), 0.0); - // and divide to get the speed - speed = nearest_sum/pointer_sum; - //g_print ("nearest sum %g pointer_sum %g speed %g\n", nearest_sum, pointer_sum, speed); - } - } - - if ( this->hatch_escaped // already escaped, do not reattach - || (speed < SPEED_MIN) // stuck; most likely reached end of traced stroke - || (this->hatch_spacing > 0 && hatch_dist > 50 * this->hatch_spacing) // went too far from the guide - ) { - // We are NOT attracted to the guide! - - //g_print ("\nlast_nearest %g %g nearest %g %g pointer %g %g pos %d %g\n", dc->last_nearest[Geom::X], dc->last_nearest[Geom::Y], nearest[Geom::X], nearest[Geom::Y], pointer[Geom::X], pointer[Geom::Y], position->piece, position->t); - - // Remember hatch_escaped so we don't get - // attracted again until the end of this stroke - this->hatch_escaped = true; - - if (this->inertia_vectors.size() >= INERTIA_ELEMENTS/2) { // move by inertia - Geom::Point moved_past_escape = motion_dt - this->inertia_vectors.front(); - Geom::Point inertia = - this->inertia_vectors.front() - this->inertia_vectors.back(); - - double dot = Geom::dot (moved_past_escape, inertia); - dot /= Geom::L2(moved_past_escape) * Geom::L2(inertia); - - if (dot > 0) { // mouse is still moving in approx the same direction - Geom::Point should_have_moved = - (inertia) * (1/Geom::L2(inertia)) * Geom::L2(moved_past_escape); - motion_dt = this->inertia_vectors.front() + - (INERTIA_FORCE * should_have_moved + (1 - INERTIA_FORCE) * moved_past_escape); - } - } - - } else { - - // Calculate angle cosine of this vector-to-guide and all past vectors - // summed, to detect if we accidentally flipped to the other side of the - // guide - Geom::Point hatch_vector_accumulated = std::accumulate - (this->hatch_vectors.begin(), this->hatch_vectors.end(), Geom::Point(0,0)); - double dot = Geom::dot (pointer - nearest, hatch_vector_accumulated); - dot /= Geom::L2(pointer - nearest) * Geom::L2(hatch_vector_accumulated); - - if (this->hatch_spacing != 0) { // spacing was already set - double target; - if (speed > SPEED_NORMAL) { - // all ok, strictly obey the spacing - target = this->hatch_spacing; - } else { - // looks like we're starting to lose speed, - // so _gradually_ let go attraction to prevent jerks - target = (this->hatch_spacing * speed + hatch_dist * (SPEED_NORMAL - speed))/SPEED_NORMAL; - } - if (!IS_NAN(dot) && dot < -0.5) {// flip - target = -target; - } - - // This is the track pointer that we will use instead of the real one - Geom::Point new_pointer = nearest + target * hatch_unit_vector; - - // some limited feedback: allow persistent pulling to slightly change - // the spacing - this->hatch_spacing += (hatch_dist - this->hatch_spacing)/3500; - - // return it to the desktop coords - motion_dt = new_pointer * motion_to_curve.inverse(); - - if (speed >= SPEED_NORMAL) { - this->inertia_vectors.push_front(motion_dt); - if (this->inertia_vectors.size() > INERTIA_ELEMENTS) - this->inertia_vectors.pop_back(); - } - - } else { - // this is the first motion event, set the dist - this->hatch_spacing = hatch_dist; - } - - // remember last points - this->hatch_last_pointer = pointer; - this->hatch_last_nearest = nearest; - - this->hatch_vectors.push_front(pointer - nearest); - if (this->hatch_vectors.size() > HATCH_VECTOR_ELEMENTS) - this->hatch_vectors.pop_back(); - } - - this->message_context->set(Inkscape::NORMAL_MESSAGE, this->hatch_escaped? _("Tracking: connection to guide path lost!") : _("Tracking a guide path")); - - } else { - this->message_context->set(Inkscape::NORMAL_MESSAGE, _("Drawing a calligraphic stroke")); - } - - if (this->just_started_drawing) { - this->just_started_drawing = false; - this->reset(motion_dt); - } - - if (!this->apply(motion_dt)) { - ret = TRUE; - break; - } - - if ( this->cur != this->last ) { - this->brush(); - g_assert( this->npoints > 0 ); - this->fit_and_split(false); - } - ret = TRUE; - } - - // Draw the hatching circle if necessary - if (event->motion.state & GDK_CONTROL_MASK) { - if (this->hatch_spacing == 0 && hatch_dist != 0) { - // Haven't set spacing yet: gray, center free, update radius live - Geom::Point c = desktop->w2d(motion_w); - Geom::Affine const sm (Geom::Scale(hatch_dist, hatch_dist) * Geom::Translate(c)); - sp_canvas_item_affine_absolute(this->hatch_area, sm); - sp_canvas_bpath_set_stroke(SP_CANVAS_BPATH(this->hatch_area), 0x7f7f7fff, 1.0, SP_STROKE_LINEJOIN_MITER, SP_STROKE_LINECAP_BUTT); - sp_canvas_item_show(this->hatch_area); - } else if (this->dragging && !this->hatch_escaped) { - // Tracking: green, center snapped, fixed radius - Geom::Point c = motion_dt; - Geom::Affine const sm (Geom::Scale(this->hatch_spacing, this->hatch_spacing) * Geom::Translate(c)); - sp_canvas_item_affine_absolute(this->hatch_area, sm); - sp_canvas_bpath_set_stroke(SP_CANVAS_BPATH(this->hatch_area), 0x00FF00ff, 1.0, SP_STROKE_LINEJOIN_MITER, SP_STROKE_LINECAP_BUTT); - sp_canvas_item_show(this->hatch_area); - } else if (this->dragging && this->hatch_escaped) { - // Tracking escaped: red, center free, fixed radius - Geom::Point c = motion_dt; - Geom::Affine const sm (Geom::Scale(this->hatch_spacing, this->hatch_spacing) * Geom::Translate(c)); - - sp_canvas_item_affine_absolute(this->hatch_area, sm); - sp_canvas_bpath_set_stroke(SP_CANVAS_BPATH(this->hatch_area), 0xFF0000ff, 1.0, SP_STROKE_LINEJOIN_MITER, SP_STROKE_LINECAP_BUTT); - sp_canvas_item_show(this->hatch_area); - } else { - // Not drawing but spacing set: gray, center snapped, fixed radius - Geom::Point c = (nearest + this->hatch_spacing * hatch_unit_vector) * motion_to_curve.inverse(); - if (!IS_NAN(c[Geom::X]) && !IS_NAN(c[Geom::Y])) { - Geom::Affine const sm (Geom::Scale(this->hatch_spacing, this->hatch_spacing) * Geom::Translate(c)); - sp_canvas_item_affine_absolute(this->hatch_area, sm); - sp_canvas_bpath_set_stroke(SP_CANVAS_BPATH(this->hatch_area), 0x7f7f7fff, 1.0, SP_STROKE_LINEJOIN_MITER, SP_STROKE_LINECAP_BUTT); - sp_canvas_item_show(this->hatch_area); - } - } - } else { - sp_canvas_item_hide(this->hatch_area); - } - } - break; - - - case GDK_BUTTON_RELEASE: - { - Geom::Point const motion_w(event->button.x, event->button.y); - Geom::Point const motion_dt(desktop->w2d(motion_w)); - - sp_canvas_item_ungrab(SP_CANVAS_ITEM(desktop->acetate), event->button.time); - desktop->canvas->endForcedFullRedraws(); - this->is_drawing = false; - - if (this->dragging && event->button.button == 1 && !this->space_panning) { - this->dragging = FALSE; - - this->apply(motion_dt); - - /* Remove all temporary line segments */ - while (this->segments) { - sp_canvas_item_destroy(SP_CANVAS_ITEM(this->segments->data)); - this->segments = g_slist_remove(this->segments, this->segments->data); - } - - /* Create object */ - this->fit_and_split(true); - if (this->accumulate()) - this->set_to_accumulated(event->button.state & GDK_SHIFT_MASK, event->button.state & GDK_MOD1_MASK); // performs document_done - else - g_warning ("Failed to create path: invalid data in dc->cal1 or dc->cal2"); - - /* reset accumulated curve */ - this->accumulated->reset(); - - this->clear_current(); - if (this->repr) { - this->repr = NULL; - } - - if (!this->hatch_pointer_past.empty()) this->hatch_pointer_past.clear(); - if (!this->hatch_nearest_past.empty()) this->hatch_nearest_past.clear(); - if (!this->inertia_vectors.empty()) this->inertia_vectors.clear(); - if (!this->hatch_vectors.empty()) this->hatch_vectors.clear(); - this->hatch_last_nearest = Geom::Point(0,0); - this->hatch_last_pointer = Geom::Point(0,0); - this->hatch_escaped = false; - this->hatch_item = NULL; - this->hatch_livarot_path = NULL; - this->just_started_drawing = false; - - if (this->hatch_spacing != 0 && !this->keep_selected) { - // we do not select the newly drawn path, so increase spacing by step - if (this->hatch_spacing_step == 0) { - this->hatch_spacing_step = this->hatch_spacing; - } - this->hatch_spacing += this->hatch_spacing_step; - } - - this->message_context->clear(); - ret = TRUE; - } - break; - } - - case GDK_KEY_PRESS: - switch (get_group0_keyval (&event->key)) { - case GDK_KEY_Up: - case GDK_KEY_KP_Up: - if (!MOD__CTRL_ONLY(event)) { - this->angle += 5.0; - if (this->angle > 90.0) - this->angle = 90.0; - sp_ddc_update_toolbox (desktop, "calligraphy-angle", this->angle); - ret = TRUE; - } - break; - case GDK_KEY_Down: - case GDK_KEY_KP_Down: - if (!MOD__CTRL_ONLY(event)) { - this->angle -= 5.0; - if (this->angle < -90.0) - this->angle = -90.0; - sp_ddc_update_toolbox (desktop, "calligraphy-angle", this->angle); - ret = TRUE; - } - break; - case GDK_KEY_Right: - case GDK_KEY_KP_Right: - if (!MOD__CTRL_ONLY(event)) { - this->width += 0.01; - if (this->width > 1.0) - this->width = 1.0; - sp_ddc_update_toolbox (desktop, "altx-calligraphy", this->width * 100); // the same spinbutton is for alt+x - ret = TRUE; - } - break; - case GDK_KEY_Left: - case GDK_KEY_KP_Left: - if (!MOD__CTRL_ONLY(event)) { - this->width -= 0.01; - if (this->width < 0.01) - this->width = 0.01; - sp_ddc_update_toolbox (desktop, "altx-calligraphy", this->width * 100); - ret = TRUE; - } - break; - case GDK_KEY_Home: - case GDK_KEY_KP_Home: - this->width = 0.01; - sp_ddc_update_toolbox (desktop, "altx-calligraphy", this->width * 100); - ret = TRUE; - break; - case GDK_KEY_End: - case GDK_KEY_KP_End: - this->width = 1.0; - sp_ddc_update_toolbox (desktop, "altx-calligraphy", this->width * 100); - ret = TRUE; - break; - case GDK_KEY_x: - case GDK_KEY_X: - if (MOD__ALT_ONLY(event)) { - desktop->setToolboxFocusTo ("altx-calligraphy"); - ret = TRUE; - } - break; - case GDK_KEY_Escape: - if (this->is_drawing) { - // if drawing, cancel, otherwise pass it up for deselecting - this->cancel(); - ret = TRUE; - } - break; - case GDK_KEY_z: - case GDK_KEY_Z: - if (MOD__CTRL_ONLY(event) && this->is_drawing) { - // if drawing, cancel, otherwise pass it up for undo - this->cancel(); - ret = TRUE; - } - break; - default: - break; - } - break; - - case GDK_KEY_RELEASE: - switch (get_group0_keyval(&event->key)) { - case GDK_KEY_Control_L: - case GDK_KEY_Control_R: - this->message_context->clear(); - this->hatch_spacing = 0; - this->hatch_spacing_step = 0; - break; - default: - break; - } - break; - - default: - break; - } - - if (!ret) { -// if ((SP_EVENT_CONTEXT_CLASS(sp_dyna_draw_context_parent_class))->root_handler) { -// ret = (SP_EVENT_CONTEXT_CLASS(sp_dyna_draw_context_parent_class))->root_handler(event_context, event); -// } - ret = SPCommonContext::root_handler(event); - } - - return ret; -} - - -void CalligraphicTool::clear_current() { - /* reset bpath */ - sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(this->currentshape), NULL); - /* reset curve */ - this->currentcurve->reset(); - this->cal1->reset(); - this->cal2->reset(); - /* reset points */ - this->npoints = 0; -} - -void CalligraphicTool::set_to_accumulated(bool unionize, bool subtract) { - if (!this->accumulated->is_empty()) { - if (!this->repr) { - /* Create object */ - Inkscape::XML::Document *xml_doc = desktop->doc()->getReprDoc(); - Inkscape::XML::Node *repr = xml_doc->createElement("svg:path"); - - /* Set style */ - sp_desktop_apply_style_tool (desktop, repr, "/tools/calligraphic", false); - - this->repr = repr; - - SPItem *item=SP_ITEM(desktop->currentLayer()->appendChildRepr(this->repr)); - Inkscape::GC::release(this->repr); - item->transform = SP_ITEM(desktop->currentLayer())->i2doc_affine().inverse(); - item->updateRepr(); - } - - Geom::PathVector pathv = this->accumulated->get_pathvector() * desktop->dt2doc(); - gchar *str = sp_svg_write_path(pathv); - g_assert( str != NULL ); - this->repr->setAttribute("d", str); - g_free(str); - - if (unionize) { - sp_desktop_selection(desktop)->add(this->repr); - sp_selected_path_union_skip_undo(sp_desktop_selection(desktop), desktop); - } else if (subtract) { - sp_desktop_selection(desktop)->add(this->repr); - sp_selected_path_diff_skip_undo(sp_desktop_selection(desktop), desktop); - } else { - if (this->keep_selected) { - sp_desktop_selection(desktop)->set(this->repr); - } - } - - // Now we need to write the transform information. - // First, find out whether our repr is still linked to a valid object. In this case, - // we need to write the transform data only for this element. - // Either there was no boolean op or it failed. - SPItem *result = SP_ITEM(desktop->doc()->getObjectByRepr(this->repr)); - - if (result == NULL) { - // The boolean operation succeeded. - // Now we fetch the single item, that has been set as selected by the boolean op. - // This is its result. - result = desktop->getSelection()->singleItem(); - } - - result->doWriteTransform(result->getRepr(), result->transform, NULL, true); - } else { - if (this->repr) { - sp_repr_unparent(this->repr); - } - - this->repr = NULL; - } - - DocumentUndo::done(sp_desktop_document(desktop), SP_VERB_CONTEXT_CALLIGRAPHIC, - _("Draw calligraphic stroke")); -} - -static void -add_cap(SPCurve *curve, - Geom::Point const &from, - Geom::Point const &to, - double rounding) -{ - if (Geom::L2( to - from ) > DYNA_EPSILON) { - Geom::Point vel = rounding * Geom::rot90( to - from ) / sqrt(2.0); - double mag = Geom::L2(vel); - - Geom::Point v = mag * Geom::rot90( to - from ) / Geom::L2( to - from ); - curve->curveto(from + v, to + v, to); - } -} - -bool CalligraphicTool::accumulate() { - if ( - this->cal1->is_empty() || - this->cal2->is_empty() || - (this->cal1->get_segment_count() <= 0) || - this->cal1->first_path()->closed() - ) { - - this->cal1->reset(); - this->cal2->reset(); - - return false; // failure - } - - SPCurve *rev_cal2 = this->cal2->create_reverse(); - - if ((rev_cal2->get_segment_count() <= 0) || rev_cal2->first_path()->closed()) { - rev_cal2->unref(); - - this->cal1->reset(); - this->cal2->reset(); - - return false; // failure - } - - Geom::Curve const * dc_cal1_firstseg = this->cal1->first_segment(); - Geom::Curve const * rev_cal2_firstseg = rev_cal2->first_segment(); - Geom::Curve const * dc_cal1_lastseg = this->cal1->last_segment(); - Geom::Curve const * rev_cal2_lastseg = rev_cal2->last_segment(); - - this->accumulated->reset(); /* Is this required ?? */ - - this->accumulated->append(this->cal1, false); - - add_cap(this->accumulated, dc_cal1_lastseg->finalPoint(), rev_cal2_firstseg->initialPoint(), this->cap_rounding); - - this->accumulated->append(rev_cal2, true); - - add_cap(this->accumulated, rev_cal2_lastseg->finalPoint(), dc_cal1_firstseg->initialPoint(), this->cap_rounding); - - this->accumulated->closepath(); - - rev_cal2->unref(); - - this->cal1->reset(); - this->cal2->reset(); - - return true; // success -} - -static double square(double const x) -{ - return x * x; -} - -void CalligraphicTool::fit_and_split(bool release) { - double const tolerance_sq = square( desktop->w2d().descrim() * TOLERANCE_CALLIGRAPHIC ); - -#ifdef DYNA_DRAW_VERBOSE - g_print("[F&S:R=%c]", release?'T':'F'); -#endif - - if (!( this->npoints > 0 && this->npoints < SAMPLING_SIZE )) { - return; // just clicked - } - - if ( this->npoints == SAMPLING_SIZE - 1 || release ) { -#define BEZIER_SIZE 4 -#define BEZIER_MAX_BEZIERS 8 -#define BEZIER_MAX_LENGTH ( BEZIER_SIZE * BEZIER_MAX_BEZIERS ) - -#ifdef DYNA_DRAW_VERBOSE - g_print("[F&S:#] dc->npoints:%d, release:%s\n", - this->npoints, release ? "TRUE" : "FALSE"); -#endif - - /* Current calligraphic */ - if ( this->cal1->is_empty() || this->cal2->is_empty() ) { - /* dc->npoints > 0 */ - /* g_print("calligraphics(1|2) reset\n"); */ - this->cal1->reset(); - this->cal2->reset(); - - this->cal1->moveto(this->point1[0]); - this->cal2->moveto(this->point2[0]); - } - - Geom::Point b1[BEZIER_MAX_LENGTH]; - gint const nb1 = Geom::bezier_fit_cubic_r(b1, this->point1, this->npoints, - tolerance_sq, BEZIER_MAX_BEZIERS); - g_assert( nb1 * BEZIER_SIZE <= gint(G_N_ELEMENTS(b1)) ); - - Geom::Point b2[BEZIER_MAX_LENGTH]; - gint const nb2 = Geom::bezier_fit_cubic_r(b2, this->point2, this->npoints, - tolerance_sq, BEZIER_MAX_BEZIERS); - g_assert( nb2 * BEZIER_SIZE <= gint(G_N_ELEMENTS(b2)) ); - - if ( nb1 != -1 && nb2 != -1 ) { - /* Fit and draw and reset state */ -#ifdef DYNA_DRAW_VERBOSE - g_print("nb1:%d nb2:%d\n", nb1, nb2); -#endif - /* CanvasShape */ - if (! release) { - this->currentcurve->reset(); - this->currentcurve->moveto(b1[0]); - for (Geom::Point *bp1 = b1; bp1 < b1 + BEZIER_SIZE * nb1; bp1 += BEZIER_SIZE) { - this->currentcurve->curveto(bp1[1], bp1[2], bp1[3]); - } - this->currentcurve->lineto(b2[BEZIER_SIZE*(nb2-1) + 3]); - for (Geom::Point *bp2 = b2 + BEZIER_SIZE * ( nb2 - 1 ); bp2 >= b2; bp2 -= BEZIER_SIZE) { - this->currentcurve->curveto(bp2[2], bp2[1], bp2[0]); - } - // FIXME: dc->segments is always NULL at this point?? - if (!this->segments) { // first segment - add_cap(this->currentcurve, b2[0], b1[0], this->cap_rounding); - } - this->currentcurve->closepath(); - sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(this->currentshape), this->currentcurve); - } - - /* Current calligraphic */ - for (Geom::Point *bp1 = b1; bp1 < b1 + BEZIER_SIZE * nb1; bp1 += BEZIER_SIZE) { - this->cal1->curveto(bp1[1], bp1[2], bp1[3]); - } - for (Geom::Point *bp2 = b2; bp2 < b2 + BEZIER_SIZE * nb2; bp2 += BEZIER_SIZE) { - this->cal2->curveto(bp2[1], bp2[2], bp2[3]); - } - } else { - /* fixme: ??? */ -#ifdef DYNA_DRAW_VERBOSE - g_print("[fit_and_split] failed to fit-cubic.\n"); -#endif - this->draw_temporary_box(); - - for (gint i = 1; i < this->npoints; i++) { - this->cal1->lineto(this->point1[i]); - } - for (gint i = 1; i < this->npoints; i++) { - this->cal2->lineto(this->point2[i]); - } - } - - /* Fit and draw and copy last point */ -#ifdef DYNA_DRAW_VERBOSE - g_print("[%d]Yup\n", this->npoints); -#endif - if (!release) { - g_assert(!this->currentcurve->is_empty()); - - SPCanvasItem *cbp = sp_canvas_item_new(sp_desktop_sketch(desktop), - SP_TYPE_CANVAS_BPATH, - NULL); - SPCurve *curve = this->currentcurve->copy(); - sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH (cbp), curve); - curve->unref(); - - guint32 fillColor = sp_desktop_get_color_tool (desktop, "/tools/calligraphic", true); - //guint32 strokeColor = sp_desktop_get_color_tool (desktop, "/tools/calligraphic", false); - double opacity = sp_desktop_get_master_opacity_tool (desktop, "/tools/calligraphic"); - double fillOpacity = sp_desktop_get_opacity_tool (desktop, "/tools/calligraphic", true); - //double strokeOpacity = sp_desktop_get_opacity_tool (desktop, "/tools/calligraphic", false); - sp_canvas_bpath_set_fill(SP_CANVAS_BPATH(cbp), ((fillColor & 0xffffff00) | SP_COLOR_F_TO_U(opacity*fillOpacity)), SP_WIND_RULE_EVENODD); - //on second thougtht don't do stroke yet because we don't have stoke-width yet and because stoke appears between segments while drawing - //sp_canvas_bpath_set_stroke(SP_CANVAS_BPATH(cbp), ((strokeColor & 0xffffff00) | SP_COLOR_F_TO_U(opacity*strokeOpacity)), 1.0, SP_STROKE_LINEJOIN_MITER, SP_STROKE_LINECAP_BUTT); - sp_canvas_bpath_set_stroke(SP_CANVAS_BPATH(cbp), 0x00000000, 1.0, SP_STROKE_LINEJOIN_MITER, SP_STROKE_LINECAP_BUTT); - /* fixme: Cannot we cascade it to root more clearly? */ - g_signal_connect(G_OBJECT(cbp), "event", G_CALLBACK(sp_desktop_root_handler), desktop); - - this->segments = g_slist_prepend(this->segments, cbp); - } - - this->point1[0] = this->point1[this->npoints - 1]; - this->point2[0] = this->point2[this->npoints - 1]; - this->npoints = 1; - } else { - this->draw_temporary_box(); - } -} - -void CalligraphicTool::draw_temporary_box() { - this->currentcurve->reset(); - - this->currentcurve->moveto(this->point2[this->npoints-1]); - - for (gint i = this->npoints-2; i >= 0; i--) { - this->currentcurve->lineto(this->point2[i]); - } - - for (gint i = 0; i < this->npoints; i++) { - this->currentcurve->lineto(this->point1[i]); - } - - if (this->npoints >= 2) { - add_cap(this->currentcurve, this->point1[this->npoints-1], this->point2[this->npoints-1], this->cap_rounding); - } - - this->currentcurve->closepath(); - sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(this->currentshape), this->currentcurve); -} - -} -} -} - - -/* - Local Variables: - mode:c++ - c-file-style:"stroustrup" - c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) - indent-tabs-mode:nil - fill-column:99 - End: -*/ -// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/dyna-draw-context.h b/src/dyna-draw-context.h deleted file mode 100644 index 5793b1142..000000000 --- a/src/dyna-draw-context.h +++ /dev/null @@ -1,94 +0,0 @@ -#ifndef SP_DYNA_DRAW_CONTEXT_H_SEEN -#define SP_DYNA_DRAW_CONTEXT_H_SEEN - -/* - * Handwriting-like drawing mode - * - * Authors: - * Mitsuru Oka - * Lauris Kaplinski - * - * The original dynadraw code: - * Paul Haeberli - * - * Copyright (C) 1998 The Free Software Foundation - * Copyright (C) 1999-2002 authors - * Copyright (C) 2001-2002 Ximian, Inc. - * - * Released under GNU GPL, read the file 'COPYING' for more information - */ - -#include "common-context.h" -#include "splivarot.h" - -#define DDC_MIN_PRESSURE 0.0 -#define DDC_MAX_PRESSURE 1.0 -#define DDC_DEFAULT_PRESSURE 1.0 - -#define DDC_MIN_TILT -1.0 -#define DDC_MAX_TILT 1.0 -#define DDC_DEFAULT_TILT 0.0 - -namespace Inkscape { -namespace UI { -namespace Tools { - -class CalligraphicTool : public SPCommonContext { -public: - CalligraphicTool(); - virtual ~CalligraphicTool(); - - static const std::string prefsPath; - - virtual void setup(); - virtual void set(const Inkscape::Preferences::Entry& val); - virtual bool root_handler(GdkEvent* event); - - virtual const std::string& getPrefsPath(); - -private: - /** newly created object remain selected */ - bool keep_selected; - - double hatch_spacing; - double hatch_spacing_step; - SPItem *hatch_item; - Path *hatch_livarot_path; - std::list hatch_nearest_past; - std::list hatch_pointer_past; - std::list inertia_vectors; - Geom::Point hatch_last_nearest, hatch_last_pointer; - std::list hatch_vectors; - bool hatch_escaped; - SPCanvasItem *hatch_area; - bool just_started_drawing; - bool trace_bg; - - void clear_current(); - void set_to_accumulated(bool unionize, bool subtract); - bool accumulate(); - void fit_and_split(bool release); - void draw_temporary_box(); - void cancel(); - void brush(); - bool apply(Geom::Point p); - void extinput(GdkEvent *event); - void reset(Geom::Point p); -}; - -} -} -} - -#endif // SP_DYNA_DRAW_CONTEXT_H_SEEN - -/* - Local Variables: - mode:c++ - c-file-style:"stroustrup" - c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) - indent-tabs-mode:nil - fill-column:99 - End: -*/ -// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/eraser-context.cpp b/src/eraser-context.cpp deleted file mode 100644 index 6445ac3d5..000000000 --- a/src/eraser-context.cpp +++ /dev/null @@ -1,1019 +0,0 @@ -/* - * Eraser drawing mode - * - * Authors: - * Mitsuru Oka - * Lauris Kaplinski - * bulia byak - * MenTaLguY - * Jon A. Cruz - * Abhishek Sharma - * - * The original dynadraw code: - * Paul Haeberli - * - * Copyright (C) 1998 The Free Software Foundation - * Copyright (C) 1999-2005 authors - * Copyright (C) 2001-2002 Ximian, Inc. - * Copyright (C) 2005-2007 bulia byak - * Copyright (C) 2006 MenTaLguY - * Copyright (C) 2008 Jon A. Cruz - * - * Released under GNU GPL, read the file 'COPYING' for more information - */ - -#define noERASER_VERBOSE - -#include "config.h" - -#include -#include -#include -#include -#include -#include - -#include "svg/svg.h" -#include "display/sp-canvas.h" -#include "display/canvas-bpath.h" -#include <2geom/bezier-utils.h> - -#include -#include "macros.h" -#include "document.h" -#include "selection.h" -#include "desktop.h" -#include "desktop-events.h" -#include "desktop-handles.h" -#include "desktop-style.h" -#include "message-context.h" -#include "preferences.h" -#include "pixmaps/cursor-eraser.xpm" -#include "xml/repr.h" -#include "context-fns.h" -#include "sp-item.h" -#include "color.h" -#include "rubberband.h" -#include "splivarot.h" -#include "sp-item-group.h" -#include "sp-shape.h" -#include "sp-path.h" -#include "sp-text.h" -#include "display/canvas-bpath.h" -#include "display/canvas-arena.h" -#include "livarot/Shape.h" -#include "document-undo.h" -#include "verbs.h" -#include <2geom/math-utils.h> -#include <2geom/pathvector.h> - -#include "eraser-context.h" - -using Inkscape::DocumentUndo; - -#define ERC_RED_RGBA 0xff0000ff - -#define TOLERANCE_ERASER 0.1 - -#define ERASER_EPSILON 0.5e-6 -#define ERASER_EPSILON_START 0.5e-2 -#define ERASER_VEL_START 1e-5 - -#define DRAG_MIN 0.0 -#define DRAG_DEFAULT 1.0 -#define DRAG_MAX 1.0 - -#include "tool-factory.h" - -namespace Inkscape { -namespace UI { -namespace Tools { - -namespace { - ToolBase* createEraserContext() { - return new EraserTool(); - } - - bool eraserContextRegistered = ToolFactory::instance().registerObject("/tools/eraser", createEraserContext); -} - -const std::string& EraserTool::getPrefsPath() { - return EraserTool::prefsPath; -} - -const std::string EraserTool::prefsPath = "/tools/eraser"; - -EraserTool::EraserTool() : SPCommonContext() { - this->cursor_shape = cursor_eraser_xpm; - this->hot_x = 4; - this->hot_y = 4; -} - -EraserTool::~EraserTool() { -} - -void EraserTool::setup() { - SPCommonContext::setup(); - - this->accumulated = new SPCurve(); - this->currentcurve = new SPCurve(); - - this->cal1 = new SPCurve(); - this->cal2 = new SPCurve(); - - this->currentshape = sp_canvas_item_new(sp_desktop_sketch(desktop), SP_TYPE_CANVAS_BPATH, NULL); - - sp_canvas_bpath_set_fill(SP_CANVAS_BPATH(this->currentshape), ERC_RED_RGBA, SP_WIND_RULE_EVENODD); - sp_canvas_bpath_set_stroke(SP_CANVAS_BPATH(this->currentshape), 0x00000000, 1.0, SP_STROKE_LINEJOIN_MITER, SP_STROKE_LINECAP_BUTT); - - /* fixme: Cannot we cascade it to root more clearly? */ - g_signal_connect(G_OBJECT(this->currentshape), "event", G_CALLBACK(sp_desktop_root_handler), desktop); - -/* -static ProfileFloatElement f_profile[PROFILE_FLOAT_SIZE] = { - {"mass",0.02, 0.0, 1.0}, - {"wiggle",0.0, 0.0, 1.0}, - {"angle",30.0, -90.0, 90.0}, - {"thinning",0.1, -1.0, 1.0}, - {"tremor",0.0, 0.0, 1.0}, - {"flatness",0.9, 0.0, 1.0}, - {"cap_rounding",0.0, 0.0, 5.0} -}; -*/ - - sp_event_context_read(this, "mass"); - sp_event_context_read(this, "wiggle"); - sp_event_context_read(this, "angle"); - sp_event_context_read(this, "width"); - sp_event_context_read(this, "thinning"); - sp_event_context_read(this, "tremor"); - sp_event_context_read(this, "flatness"); - sp_event_context_read(this, "tracebackground"); - sp_event_context_read(this, "usepressure"); - sp_event_context_read(this, "usetilt"); - sp_event_context_read(this, "abs_width"); - sp_event_context_read(this, "cap_rounding"); - - this->is_drawing = false; - - Inkscape::Preferences *prefs = Inkscape::Preferences::get(); - if (prefs->getBool("/tools/eraser/selcue", 0) != 0) { - this->enableSelectionCue(); - } - - // TODO temp force: - this->enableSelectionCue(); -} - -static double -flerp(double f0, double f1, double p) -{ - return f0 + ( f1 - f0 ) * p; -} - -void EraserTool::reset(Geom::Point p) { - this->last = this->cur = getNormalizedPoint(p); - this->vel = Geom::Point(0,0); - this->vel_max = 0; - this->acc = Geom::Point(0,0); - this->ang = Geom::Point(0,0); - this->del = Geom::Point(0,0); -} - -void EraserTool::extinput(GdkEvent *event) { - if (gdk_event_get_axis (event, GDK_AXIS_PRESSURE, &this->pressure)) - this->pressure = CLAMP (this->pressure, ERC_MIN_PRESSURE, ERC_MAX_PRESSURE); - else - this->pressure = ERC_DEFAULT_PRESSURE; - - if (gdk_event_get_axis (event, GDK_AXIS_XTILT, &this->xtilt)) - this->xtilt = CLAMP (this->xtilt, ERC_MIN_TILT, ERC_MAX_TILT); - else - this->xtilt = ERC_DEFAULT_TILT; - - if (gdk_event_get_axis (event, GDK_AXIS_YTILT, &this->ytilt)) - this->ytilt = CLAMP (this->ytilt, ERC_MIN_TILT, ERC_MAX_TILT); - else - this->ytilt = ERC_DEFAULT_TILT; -} - - -bool EraserTool::apply(Geom::Point p) { - Geom::Point n = getNormalizedPoint(p); - - /* Calculate mass and drag */ - double const mass = flerp(1.0, 160.0, this->mass); - double const drag = flerp(0.0, 0.5, this->drag * this->drag); - - /* Calculate force and acceleration */ - Geom::Point force = n - this->cur; - - // If force is below the absolute threshold ERASER_EPSILON, - // or we haven't yet reached ERASER_VEL_START (i.e. at the beginning of stroke) - // _and_ the force is below the (higher) ERASER_EPSILON_START threshold, - // discard this move. - // This prevents flips, blobs, and jerks caused by microscopic tremor of the tablet pen, - // especially bothersome at the start of the stroke where we don't yet have the inertia to - // smooth them out. - if ( Geom::L2(force) < ERASER_EPSILON || (this->vel_max < ERASER_VEL_START && Geom::L2(force) < ERASER_EPSILON_START)) { - return FALSE; - } - - this->acc = force / mass; - - /* Calculate new velocity */ - this->vel += this->acc; - - if (Geom::L2(this->vel) > this->vel_max) - this->vel_max = Geom::L2(this->vel); - - /* Calculate angle of drawing tool */ - - double a1; - if (this->usetilt) { - // 1a. calculate nib angle from input device tilt: - gdouble length = std::sqrt(this->xtilt*this->xtilt + this->ytilt*this->ytilt);; - - if (length > 0) { - Geom::Point ang1 = Geom::Point(this->ytilt/length, this->xtilt/length); - a1 = atan2(ang1); - } - else - a1 = 0.0; - } - else { - // 1b. fixed dc->angle (absolutely flat nib): - double const radians = ( (this->angle - 90) / 180.0 ) * M_PI; - Geom::Point ang1 = Geom::Point(-sin(radians), cos(radians)); - a1 = atan2(ang1); - } - - // 2. perpendicular to dc->vel (absolutely non-flat nib): - gdouble const mag_vel = Geom::L2(this->vel); - if ( mag_vel < ERASER_EPSILON ) { - return FALSE; - } - Geom::Point ang2 = Geom::rot90(this->vel) / mag_vel; - - // 3. Average them using flatness parameter: - // calculate angles - double a2 = atan2(ang2); - // flip a2 to force it to be in the same half-circle as a1 - bool flipped = false; - if (fabs (a2-a1) > 0.5*M_PI) { - a2 += M_PI; - flipped = true; - } - // normalize a2 - if (a2 > M_PI) - a2 -= 2*M_PI; - if (a2 < -M_PI) - a2 += 2*M_PI; - // find the flatness-weighted bisector angle, unflip if a2 was flipped - // FIXME: when dc->vel is oscillating around the fixed angle, the new_ang flips back and forth. How to avoid this? - double new_ang = a1 + (1 - this->flatness) * (a2 - a1) - (flipped? M_PI : 0); - - // Try to detect a sudden flip when the new angle differs too much from the previous for the - // current velocity; in that case discard this move - double angle_delta = Geom::L2(Geom::Point (cos (new_ang), sin (new_ang)) - this->ang); - if ( angle_delta / Geom::L2(this->vel) > 4000 ) { - return FALSE; - } - - // convert to point - this->ang = Geom::Point (cos (new_ang), sin (new_ang)); - -// g_print ("force %g acc %g vel_max %g vel %g a1 %g a2 %g new_ang %g\n", Geom::L2(force), Geom::L2(dc->acc), dc->vel_max, Geom::L2(dc->vel), a1, a2, new_ang); - - /* Apply drag */ - this->vel *= 1.0 - drag; - - /* Update position */ - this->last = this->cur; - this->cur += this->vel; - - return TRUE; -} - -void EraserTool::brush() { - g_assert( this->npoints >= 0 && this->npoints < SAMPLING_SIZE ); - - // How much velocity thins strokestyle - double vel_thin = flerp (0, 160, this->vel_thin); - - // Influence of pressure on thickness - double pressure_thick = (this->usepressure ? this->pressure : 1.0); - - // get the real brush point, not the same as pointer (affected by hatch tracking and/or mass - // drag) - Geom::Point brush = getViewPoint(this->cur); - //Geom::Point brush_w = SP_EVENT_CONTEXT(dc)->desktop->d2w(brush); - - double trace_thick = 1; - - double width = (pressure_thick * trace_thick - vel_thin * Geom::L2(this->vel)) * this->width; - - double tremble_left = 0, tremble_right = 0; - if (this->tremor > 0) { - // obtain two normally distributed random variables, using polar Box-Muller transform - double x1, x2, w, y1, y2; - do { - x1 = 2.0 * g_random_double_range(0,1) - 1.0; - x2 = 2.0 * g_random_double_range(0,1) - 1.0; - w = x1 * x1 + x2 * x2; - } while ( w >= 1.0 ); - w = sqrt( (-2.0 * log( w ) ) / w ); - y1 = x1 * w; - y2 = x2 * w; - - // deflect both left and right edges randomly and independently, so that: - // (1) dc->tremor=1 corresponds to sigma=1, decreasing dc->tremor narrows the bell curve; - // (2) deflection depends on width, but is upped for small widths for better visual uniformity across widths; - // (3) deflection somewhat depends on speed, to prevent fast strokes looking - // comparatively smooth and slow ones excessively jittery - tremble_left = (y1)*this->tremor * (0.15 + 0.8*width) * (0.35 + 14*Geom::L2(this->vel)); - tremble_right = (y2)*this->tremor * (0.15 + 0.8*width) * (0.35 + 14*Geom::L2(this->vel)); - } - - if ( width < 0.02 * this->width ) { - width = 0.02 * this->width; - } - - double dezoomify_factor = 0.05 * 1000; - if (!this->abs_width) { - dezoomify_factor /= SP_EVENT_CONTEXT(this)->desktop->current_zoom(); - } - - Geom::Point del_left = dezoomify_factor * (width + tremble_left) * this->ang; - Geom::Point del_right = dezoomify_factor * (width + tremble_right) * this->ang; - - this->point1[this->npoints] = brush + del_left; - this->point2[this->npoints] = brush - del_right; - - this->del = 0.5*(del_left + del_right); - - this->npoints++; -} - -static void -sp_erc_update_toolbox (SPDesktop *desktop, const gchar *id, double value) -{ - desktop->setToolboxAdjustmentValue (id, value); -} - -void EraserTool::cancel() { - SPDesktop *desktop = SP_EVENT_CONTEXT(this)->desktop; - this->dragging = FALSE; - this->is_drawing = false; - sp_canvas_item_ungrab(SP_CANVAS_ITEM(desktop->acetate), 0); - /* Remove all temporary line segments */ - while (this->segments) { - sp_canvas_item_destroy(SP_CANVAS_ITEM(this->segments->data)); - this->segments = g_slist_remove(this->segments, this->segments->data); - } - /* reset accumulated curve */ - this->accumulated->reset(); - this->clear_current(); - if (this->repr) { - this->repr = NULL; - } -} - -bool EraserTool::root_handler(GdkEvent* event) { - gint ret = FALSE; - - switch (event->type) { - case GDK_BUTTON_PRESS: - if (event->button.button == 1 && !this->space_panning) { - if (Inkscape::have_viable_layer(desktop, this->message_context) == false) { - return TRUE; - } - - Geom::Point const button_w(event->button.x, event->button.y); - Geom::Point const button_dt(desktop->w2d(button_w)); - - this->reset(button_dt); - this->extinput(event); - this->apply(button_dt); - - this->accumulated->reset(); - - if (this->repr) { - this->repr = NULL; - } - - Inkscape::Rubberband::get(desktop)->start(desktop, button_dt); - Inkscape::Rubberband::get(desktop)->setMode(RUBBERBAND_MODE_TOUCHPATH); - - /* initialize first point */ - this->npoints = 0; - - sp_canvas_item_grab(SP_CANVAS_ITEM(desktop->acetate), - ( GDK_KEY_PRESS_MASK | - GDK_BUTTON_RELEASE_MASK | - GDK_POINTER_MOTION_MASK | - GDK_BUTTON_PRESS_MASK ), - NULL, - event->button.time); - - ret = TRUE; - - desktop->canvas->forceFullRedrawAfterInterruptions(3); - this->is_drawing = true; - } - break; - - case GDK_MOTION_NOTIFY: { - Geom::Point const motion_w(event->motion.x, event->motion.y); - Geom::Point motion_dt(desktop->w2d(motion_w) - ); - this->extinput(event); - - this->message_context->clear(); - - if ( this->is_drawing && (event->motion.state & GDK_BUTTON1_MASK) && !this->space_panning) { - this->dragging = TRUE; - - this->message_context->set(Inkscape::NORMAL_MESSAGE, _("Drawing an eraser stroke")); - - if (!this->apply(motion_dt)) { - ret = TRUE; - break; - } - - if ( this->cur != this->last ) { - this->brush(); - g_assert( this->npoints > 0 ); - this->fit_and_split(false); - } - - ret = TRUE; - } - - Inkscape::Rubberband::get(desktop)->move(motion_dt); - } - break; - - case GDK_BUTTON_RELEASE: { - Geom::Point const motion_w(event->button.x, event->button.y); - Geom::Point const motion_dt(desktop->w2d(motion_w)); - - sp_canvas_item_ungrab(SP_CANVAS_ITEM(desktop->acetate), event->button.time); - desktop->canvas->endForcedFullRedraws(); - this->is_drawing = false; - - if (this->dragging && event->button.button == 1 && !this->space_panning) { - this->dragging = FALSE; - - this->apply(motion_dt); - - /* Remove all temporary line segments */ - while (this->segments) { - sp_canvas_item_destroy(SP_CANVAS_ITEM(this->segments->data)); - this->segments = g_slist_remove(this->segments, this->segments->data); - } - - /* Create object */ - this->fit_and_split(true); - this->accumulate(); - this->set_to_accumulated(); // performs document_done - - /* reset accumulated curve */ - this->accumulated->reset(); - - this->clear_current(); - if (this->repr) { - this->repr = NULL; - } - - this->message_context->clear(); - ret = TRUE; - } - - if (Inkscape::Rubberband::get(desktop)->is_started()) { - Inkscape::Rubberband::get(desktop)->stop(); - } - - break; - } - - case GDK_KEY_PRESS: - switch (get_group0_keyval (&event->key)) { - case GDK_KEY_Up: - case GDK_KEY_KP_Up: - if (!MOD__CTRL_ONLY(event)) { - this->angle += 5.0; - - if (this->angle > 90.0) { - this->angle = 90.0; - } - - sp_erc_update_toolbox (desktop, "eraser-angle", this->angle); - ret = TRUE; - } - break; - - case GDK_KEY_Down: - case GDK_KEY_KP_Down: - if (!MOD__CTRL_ONLY(event)) { - this->angle -= 5.0; - - if (this->angle < -90.0) { - this->angle = -90.0; - } - - sp_erc_update_toolbox (desktop, "eraser-angle", this->angle); - ret = TRUE; - } - break; - - case GDK_KEY_Right: - case GDK_KEY_KP_Right: - if (!MOD__CTRL_ONLY(event)) { - this->width += 0.01; - - if (this->width > 1.0) { - this->width = 1.0; - } - - sp_erc_update_toolbox (desktop, "altx-eraser", this->width * 100); // the same spinbutton is for alt+x - ret = TRUE; - } - break; - - case GDK_KEY_Left: - case GDK_KEY_KP_Left: - if (!MOD__CTRL_ONLY(event)) { - this->width -= 0.01; - - if (this->width < 0.01) { - this->width = 0.01; - } - - sp_erc_update_toolbox (desktop, "altx-eraser", this->width * 100); - ret = TRUE; - } - break; - - case GDK_KEY_Home: - case GDK_KEY_KP_Home: - this->width = 0.01; - sp_erc_update_toolbox (desktop, "altx-eraser", this->width * 100); - ret = TRUE; - break; - - case GDK_KEY_End: - case GDK_KEY_KP_End: - this->width = 1.0; - sp_erc_update_toolbox (desktop, "altx-eraser", this->width * 100); - ret = TRUE; - break; - - case GDK_KEY_x: - case GDK_KEY_X: - if (MOD__ALT_ONLY(event)) { - desktop->setToolboxFocusTo ("altx-eraser"); - ret = TRUE; - } - break; - - case GDK_KEY_Escape: - Inkscape::Rubberband::get(desktop)->stop(); - - if (this->is_drawing) { - // if drawing, cancel, otherwise pass it up for deselecting - this->cancel(); - ret = TRUE; - } - break; - - case GDK_KEY_z: - case GDK_KEY_Z: - if (MOD__CTRL_ONLY(event) && this->is_drawing) { - // if drawing, cancel, otherwise pass it up for undo - this->cancel(); - ret = TRUE; - } - break; - - default: - break; - } - break; - - case GDK_KEY_RELEASE: - switch (get_group0_keyval(&event->key)) { - case GDK_KEY_Control_L: - case GDK_KEY_Control_R: - this->message_context->clear(); - break; - - default: - break; - } - break; - - default: - break; - } - - if (!ret) { - ret = SPCommonContext::root_handler(event); - } - - return ret; -} - -void EraserTool::clear_current() { - // reset bpath - sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(this->currentshape), NULL); - - // reset curve - this->currentcurve->reset(); - this->cal1->reset(); - this->cal2->reset(); - - // reset points - this->npoints = 0; -} - -void EraserTool::set_to_accumulated() { - bool workDone = false; - - if (!this->accumulated->is_empty()) { - if (!this->repr) { - /* Create object */ - Inkscape::XML::Document *xml_doc = desktop->doc()->getReprDoc(); - Inkscape::XML::Node *repr = xml_doc->createElement("svg:path"); - - /* Set style */ - sp_desktop_apply_style_tool (desktop, repr, "/tools/eraser", false); - - this->repr = repr; - - SPItem *item=SP_ITEM(desktop->currentLayer()->appendChildRepr(this->repr)); - Inkscape::GC::release(this->repr); - - item->transform = SP_ITEM(desktop->currentLayer())->i2doc_affine().inverse(); - item->updateRepr(); - } - - Geom::PathVector pathv = this->accumulated->get_pathvector() * desktop->dt2doc(); - gchar *str = sp_svg_write_path(pathv); - g_assert( str != NULL ); - this->repr->setAttribute("d", str); - g_free(str); - - if ( this->repr ) { - bool wasSelection = false; - Inkscape::Selection *selection = sp_desktop_selection(desktop); - Inkscape::Preferences *prefs = Inkscape::Preferences::get(); - - gint eraserMode = prefs->getBool("/tools/eraser/mode") ? 1 : 0; - Inkscape::XML::Document *xml_doc = desktop->doc()->getReprDoc(); - - SPItem* acid = SP_ITEM(desktop->doc()->getObjectByRepr(this->repr)); - Geom::OptRect eraserBbox = acid->visualBounds(); - Geom::Rect bounds = (*eraserBbox) * desktop->doc2dt(); - std::vector remainingItems; - GSList* toWorkOn = 0; - - if (selection->isEmpty()) { - if ( eraserMode ) { - toWorkOn = sp_desktop_document(desktop)->getItemsPartiallyInBox(desktop->dkey, bounds); - } else { - Inkscape::Rubberband *r = Inkscape::Rubberband::get(desktop); - toWorkOn = sp_desktop_document(desktop)->getItemsAtPoints(desktop->dkey, r->getPoints()); - } - - toWorkOn = g_slist_remove( toWorkOn, acid ); - } else { - toWorkOn = g_slist_copy(const_cast(selection->itemList())); - wasSelection = true; - } - - if ( g_slist_length(toWorkOn) > 0 ) { - if ( eraserMode ) { - for (GSList *i = toWorkOn ; i ; i = i->next ) { - SPItem *item = SP_ITEM(i->data); - - if ( eraserMode ) { - Geom::OptRect bbox = item->visualBounds(); - - if (bbox && bbox->intersects(*eraserBbox)) { - Inkscape::XML::Node* dup = this->repr->duplicate(xml_doc); - this->repr->parent()->appendChild(dup); - Inkscape::GC::release(dup); // parent takes over - - selection->set(item); - selection->add(dup); - sp_selected_path_diff_skip_undo(selection, desktop); - workDone = true; // TODO set this only if something was cut. - - if ( !selection->isEmpty() ) { - // If the item was not completely erased, track the new remainder. - GSList *nowSel = g_slist_copy(const_cast(selection->itemList())); - - for (GSList const *i2 = nowSel ; i2 ; i2 = i2->next ) { - remainingItems.push_back(SP_ITEM(i2->data)); - } - - g_slist_free(nowSel); - } - } else { - remainingItems.push_back(item); - } - } - } - } else { - for (GSList *i = toWorkOn ; i ; i = i->next ) { - sp_object_ref( SP_ITEM(i->data), 0 ); - } - - for (GSList *i = toWorkOn ; i ; i = i->next ) { - SPItem *item = SP_ITEM(i->data); - item->deleteObject(true); - sp_object_unref(item); - workDone = true; - } - } - - g_slist_free(toWorkOn); - - if ( !eraserMode ) { - //sp_selection_delete(desktop); - remainingItems.clear(); - } - - selection->clear(); - - if ( wasSelection ) { - if ( !remainingItems.empty() ) { - selection->add(remainingItems.begin(), remainingItems.end()); - } - } - } - - // Remove the eraser stroke itself: - sp_repr_unparent( this->repr ); - this->repr = 0; - } - } else { - if (this->repr) { - sp_repr_unparent(this->repr); - this->repr = 0; - } - } - - - if ( workDone ) { - DocumentUndo::done(sp_desktop_document(desktop), SP_VERB_CONTEXT_ERASER, _("Draw eraser stroke")); - } else { - DocumentUndo::cancel(sp_desktop_document(desktop)); - } -} - -static void -add_cap(SPCurve *curve, - Geom::Point const &pre, Geom::Point const &from, - Geom::Point const &to, Geom::Point const &post, - double rounding) -{ - Geom::Point vel = rounding * Geom::rot90( to - from ) / sqrt(2.0); - double mag = Geom::L2(vel); - - Geom::Point v_in = from - pre; - double mag_in = Geom::L2(v_in); - - if ( mag_in > ERASER_EPSILON ) { - v_in = mag * v_in / mag_in; - } else { - v_in = Geom::Point(0, 0); - } - - Geom::Point v_out = to - post; - double mag_out = Geom::L2(v_out); - - if ( mag_out > ERASER_EPSILON ) { - v_out = mag * v_out / mag_out; - } else { - v_out = Geom::Point(0, 0); - } - - if ( Geom::L2(v_in) > ERASER_EPSILON || Geom::L2(v_out) > ERASER_EPSILON ) { - curve->curveto(from + v_in, to + v_out, to); - } -} - -void EraserTool::accumulate() { - if ( !this->cal1->is_empty() && !this->cal2->is_empty() ) { - this->accumulated->reset(); /* Is this required ?? */ - SPCurve *rev_cal2 = this->cal2->create_reverse(); - - g_assert(this->cal1->get_segment_count() > 0); - g_assert(rev_cal2->get_segment_count() > 0); - g_assert( ! this->cal1->first_path()->closed() ); - g_assert( ! rev_cal2->first_path()->closed() ); - - Geom::CubicBezier const * dc_cal1_firstseg = dynamic_cast( this->cal1->first_segment() ); - Geom::CubicBezier const * rev_cal2_firstseg = dynamic_cast( rev_cal2->first_segment() ); - Geom::CubicBezier const * dc_cal1_lastseg = dynamic_cast( this->cal1->last_segment() ); - Geom::CubicBezier const * rev_cal2_lastseg = dynamic_cast( rev_cal2->last_segment() ); - - g_assert( dc_cal1_firstseg ); - g_assert( rev_cal2_firstseg ); - g_assert( dc_cal1_lastseg ); - g_assert( rev_cal2_lastseg ); - - this->accumulated->append(this->cal1, FALSE); - - add_cap(this->accumulated, (*dc_cal1_lastseg)[2], (*dc_cal1_lastseg)[3], (*rev_cal2_firstseg)[0], (*rev_cal2_firstseg)[1], this->cap_rounding); - - this->accumulated->append(rev_cal2, TRUE); - - add_cap(this->accumulated, (*rev_cal2_lastseg)[2], (*rev_cal2_lastseg)[3], (*dc_cal1_firstseg)[0], (*dc_cal1_firstseg)[1], this->cap_rounding); - - this->accumulated->closepath(); - - rev_cal2->unref(); - - this->cal1->reset(); - this->cal2->reset(); - } -} - -static double square(double const x) -{ - return x * x; -} - -void EraserTool::fit_and_split(bool release) { - double const tolerance_sq = square( desktop->w2d().descrim() * TOLERANCE_ERASER ); - -#ifdef ERASER_VERBOSE - g_print("[F&S:R=%c]", release?'T':'F'); -#endif - - if (!( this->npoints > 0 && this->npoints < SAMPLING_SIZE )) - return; // just clicked - - if ( this->npoints == SAMPLING_SIZE - 1 || release ) { -#define BEZIER_SIZE 4 -#define BEZIER_MAX_BEZIERS 8 -#define BEZIER_MAX_LENGTH ( BEZIER_SIZE * BEZIER_MAX_BEZIERS ) - -#ifdef ERASER_VERBOSE - g_print("[F&S:#] this->npoints:%d, release:%s\n", - dc->npoints, release ? "TRUE" : "FALSE"); -#endif - - /* Current eraser */ - if ( this->cal1->is_empty() || this->cal2->is_empty() ) { - /* dc->npoints > 0 */ - /* g_print("erasers(1|2) reset\n"); */ - this->cal1->reset(); - this->cal2->reset(); - - this->cal1->moveto(this->point1[0]); - this->cal2->moveto(this->point2[0]); - } - - Geom::Point b1[BEZIER_MAX_LENGTH]; - gint const nb1 = Geom::bezier_fit_cubic_r(b1, this->point1, this->npoints, tolerance_sq, BEZIER_MAX_BEZIERS); - g_assert( nb1 * BEZIER_SIZE <= gint(G_N_ELEMENTS(b1)) ); - - Geom::Point b2[BEZIER_MAX_LENGTH]; - gint const nb2 = Geom::bezier_fit_cubic_r(b2, this->point2, this->npoints, tolerance_sq, BEZIER_MAX_BEZIERS); - g_assert( nb2 * BEZIER_SIZE <= gint(G_N_ELEMENTS(b2)) ); - - if ( nb1 != -1 && nb2 != -1 ) { - /* Fit and draw and reset state */ -#ifdef ERASER_VERBOSE - g_print("nb1:%d nb2:%d\n", nb1, nb2); -#endif - - /* CanvasShape */ - if (! release) { - this->currentcurve->reset(); - this->currentcurve->moveto(b1[0]); - - for (Geom::Point *bp1 = b1; bp1 < b1 + BEZIER_SIZE * nb1; bp1 += BEZIER_SIZE) { - this->currentcurve->curveto(bp1[1], bp1[2], bp1[3]); - } - - this->currentcurve->lineto(b2[BEZIER_SIZE*(nb2-1) + 3]); - - for (Geom::Point *bp2 = b2 + BEZIER_SIZE * ( nb2 - 1 ); bp2 >= b2; bp2 -= BEZIER_SIZE) { - this->currentcurve->curveto(bp2[2], bp2[1], bp2[0]); - } - - // FIXME: this->segments is always NULL at this point?? - if (!this->segments) { // first segment - add_cap(this->currentcurve, b2[1], b2[0], b1[0], b1[1], this->cap_rounding); - } - - this->currentcurve->closepath(); - sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(this->currentshape), this->currentcurve); - } - - /* Current eraser */ - for (Geom::Point *bp1 = b1; bp1 < b1 + BEZIER_SIZE * nb1; bp1 += BEZIER_SIZE) { - this->cal1->curveto(bp1[1], bp1[2], bp1[3]); - } - - for (Geom::Point *bp2 = b2; bp2 < b2 + BEZIER_SIZE * nb2; bp2 += BEZIER_SIZE) { - this->cal2->curveto(bp2[1], bp2[2], bp2[3]); - } - } else { - /* fixme: ??? */ -#ifdef ERASER_VERBOSE - g_print("[fit_and_split] failed to fit-cubic.\n"); -#endif - this->draw_temporary_box(); - - for (gint i = 1; i < this->npoints; i++) { - this->cal1->lineto(this->point1[i]); - } - - for (gint i = 1; i < this->npoints; i++) { - this->cal2->lineto(this->point2[i]); - } - } - - /* Fit and draw and copy last point */ -#ifdef ERASER_VERBOSE - g_print("[%d]Yup\n", this->npoints); -#endif - if (!release) { - Inkscape::Preferences *prefs = Inkscape::Preferences::get(); - gint eraserMode = prefs->getBool("/tools/eraser/mode") ? 1 : 0; - g_assert(!this->currentcurve->is_empty()); - - SPCanvasItem *cbp = sp_canvas_item_new(sp_desktop_sketch(desktop), SP_TYPE_CANVAS_BPATH, NULL); - SPCurve *curve = this->currentcurve->copy(); - sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH (cbp), curve); - curve->unref(); - - guint32 fillColor = sp_desktop_get_color_tool (desktop, "/tools/eraser", true); - //guint32 strokeColor = sp_desktop_get_color_tool (desktop, "/tools/eraser", false); - double opacity = sp_desktop_get_master_opacity_tool (desktop, "/tools/eraser"); - double fillOpacity = sp_desktop_get_opacity_tool (desktop, "/tools/eraser", true); - //double strokeOpacity = sp_desktop_get_opacity_tool (desktop, "/tools/eraser", false); - sp_canvas_bpath_set_fill(SP_CANVAS_BPATH(cbp), ((fillColor & 0xffffff00) | SP_COLOR_F_TO_U(opacity*fillOpacity)), SP_WIND_RULE_EVENODD); - //on second thougtht don't do stroke yet because we don't have stoke-width yet and because stoke appears between segments while drawing - //sp_canvas_bpath_set_stroke(SP_CANVAS_BPATH(cbp), ((strokeColor & 0xffffff00) | SP_COLOR_F_TO_U(opacity*strokeOpacity)), 1.0, SP_STROKE_LINEJOIN_MITER, SP_STROKE_LINECAP_BUTT); - sp_canvas_bpath_set_stroke(SP_CANVAS_BPATH(cbp), 0x00000000, 1.0, SP_STROKE_LINEJOIN_MITER, SP_STROKE_LINECAP_BUTT); - /* fixme: Cannot we cascade it to root more clearly? */ - g_signal_connect(G_OBJECT(cbp), "event", G_CALLBACK(sp_desktop_root_handler), desktop); - - this->segments = g_slist_prepend(this->segments, cbp); - - if ( !eraserMode ) { - sp_canvas_item_hide(cbp); - sp_canvas_item_hide(this->currentshape); - } - } - - this->point1[0] = this->point1[this->npoints - 1]; - this->point2[0] = this->point2[this->npoints - 1]; - this->npoints = 1; - } else { - this->draw_temporary_box(); - } -} - -void EraserTool::draw_temporary_box() { - this->currentcurve->reset(); - - this->currentcurve->moveto(this->point1[this->npoints-1]); - - for (gint i = this->npoints-2; i >= 0; i--) { - this->currentcurve->lineto(this->point1[i]); - } - - for (gint i = 0; i < this->npoints; i++) { - this->currentcurve->lineto(this->point2[i]); - } - - if (this->npoints >= 2) { - add_cap(this->currentcurve, this->point2[this->npoints-2], this->point2[this->npoints-1], this->point1[this->npoints-1], this->point1[this->npoints-2], this->cap_rounding); - } - - this->currentcurve->closepath(); - sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(this->currentshape), this->currentcurve); -} - -} -} -} - -/* - Local Variables: - mode:c++ - c-file-style:"stroustrup" - c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) - indent-tabs-mode:nil - fill-column:99 - End: -*/ -// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/eraser-context.h b/src/eraser-context.h deleted file mode 100644 index a3ce4a241..000000000 --- a/src/eraser-context.h +++ /dev/null @@ -1,76 +0,0 @@ -#ifndef SP_ERASER_CONTEXT_H_SEEN -#define SP_ERASER_CONTEXT_H_SEEN - -/* - * Handwriting-like drawing mode - * - * Authors: - * Mitsuru Oka - * Lauris Kaplinski - * - * The original dynadraw code: - * Paul Haeberli - * - * Copyright (C) 1998 The Free Software Foundation - * Copyright (C) 1999-2002 authors - * Copyright (C) 2001-2002 Ximian, Inc. - * Copyright (C) 2008 Jon A. Cruz - * - * Released under GNU GPL, read the file 'COPYING' for more information - */ - -#include "common-context.h" - -#define ERC_MIN_PRESSURE 0.0 -#define ERC_MAX_PRESSURE 1.0 -#define ERC_DEFAULT_PRESSURE 1.0 - -#define ERC_MIN_TILT -1.0 -#define ERC_MAX_TILT 1.0 -#define ERC_DEFAULT_TILT 0.0 - -namespace Inkscape { -namespace UI { -namespace Tools { - -class EraserTool : public SPCommonContext { -public: - EraserTool(); - virtual ~EraserTool(); - - static const std::string prefsPath; - - virtual void setup(); - virtual bool root_handler(GdkEvent* event); - - virtual const std::string& getPrefsPath(); - -private: - void reset(Geom::Point p); - void extinput(GdkEvent *event); - bool apply(Geom::Point p); - void brush(); - void cancel(); - void clear_current(); - void set_to_accumulated(); - void accumulate(); - void fit_and_split(bool release); - void draw_temporary_box(); -}; - -} -} -} - -#endif // SP_ERASER_CONTEXT_H_SEEN - -/* - Local Variables: - mode:c++ - c-file-style:"stroustrup" - c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) - indent-tabs-mode:nil - fill-column:99 - End: -*/ -// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/event-context.cpp b/src/event-context.cpp deleted file mode 100644 index 70502ca64..000000000 --- a/src/event-context.cpp +++ /dev/null @@ -1,1541 +0,0 @@ -/* - * Main event handling, and related helper functions. - * - * Authors: - * Lauris Kaplinski - * Frank Felfe - * bulia byak - * Jon A. Cruz - * Kris De Gussem - * - * Copyright (C) 1999-2012 authors - * Copyright (C) 2001-2002 Ximian, Inc. - * - * Released under GNU GPL, read the file 'COPYING' for more information - */ - -#ifdef HAVE_CONFIG_H -# include "config.h" -#endif - -#if GLIBMM_DISABLE_DEPRECATED && HAVE_GLIBMM_THREADS_H -#include -#endif - -#include "shortcuts.h" -#include "file.h" -#include "event-context.h" - -#include -#include -#include -#include -#include -#include - -#include "display/sp-canvas.h" -#include "xml/node-event-vector.h" -#include "sp-cursor.h" -#include "desktop.h" -#include "desktop-handles.h" -#include "desktop-events.h" -#include "desktop-style.h" -#include "widgets/desktop-widget.h" -#include "sp-namedview.h" -#include "selection.h" -#include "interface.h" -#include "macros.h" -#include "tools-switch.h" -#include "preferences.h" -#include "message-context.h" -#include "gradient-drag.h" -#include "attributes.h" -#include "rubberband.h" -#include "selcue.h" -#include "lpe-tool-context.h" -#include "ui/tool/control-point.h" -#include "shape-editor.h" -#include "sp-guide.h" -#include "color.h" - -// globals for temporary switching to selector by space -static bool selector_toggled = FALSE; -static int switch_selector_to = 0; - -// globals for temporary switching to dropper by 'D' -static bool dropper_toggled = FALSE; -static int switch_dropper_to = 0; - -//static gint xp = 0, yp = 0; // where drag started -//static gint tolerance = 0; -//static bool within_tolerance = false; - -// globals for keeping track of keyboard scroll events in order to accelerate -static guint32 scroll_event_time = 0; -static gdouble scroll_multiply = 1; -static guint scroll_keyval = 0; - - -namespace Inkscape { -namespace UI { -namespace Tools { - -static void set_event_location(SPDesktop * desktop, GdkEvent * event); - - -void ToolBase::set(const Inkscape::Preferences::Entry& /*val*/) { -} - -void ToolBase::activate() { -} - -void ToolBase::deactivate() { -} - -void ToolBase::finish() { - this->enableSelectionCue(false); -} - -ToolBase::ToolBase() { - this->hot_y = 0; - this->xp = 0; - this->cursor_shape = 0; - this->pref_observer = 0; - this->hot_x = 0; - this->yp = 0; - this->within_tolerance = false; - this->tolerance = 0; - //this->key = 0; - this->item_to_select = 0; - - this->desktop = NULL; - this->cursor = NULL; - this->message_context = NULL; - this->_selcue = NULL; - this->_grdrag = NULL; - this->space_panning = false; - this->shape_editor = NULL; - this->_delayed_snap_event = NULL; - this->_dse_callback_in_process = false; - //this->tool_url = NULL; -} - -ToolBase::~ToolBase() { - if (this->message_context) { - delete this->message_context; - } - - if (this->cursor != NULL) { -#if GTK_CHECK_VERSION(3,0,0) - g_object_unref(this->cursor); -#else - gdk_cursor_unref(this->cursor); -#endif - this->cursor = NULL; - } - - if (this->desktop) { - this->desktop = NULL; - } - - if (this->pref_observer) { - delete this->pref_observer; - } - - if (this->_delayed_snap_event) { - delete this->_delayed_snap_event; - } -} - - -/** - * Set the cursor to a standard GDK cursor - */ -static void sp_event_context_set_cursor(ToolBase *event_context, GdkCursorType cursor_type) { - - GtkWidget *w = GTK_WIDGET(sp_desktop_canvas(event_context->desktop)); - GdkDisplay *display = gdk_display_get_default(); - GdkCursor *cursor = gdk_cursor_new_for_display(display, cursor_type); - -#if WITH_GTKMM_3_0 - if (cursor) { - gdk_window_set_cursor (gtk_widget_get_window (w), cursor); - g_object_unref (cursor); - } -#else - gdk_window_set_cursor (gtk_widget_get_window (w), cursor); - gdk_cursor_unref (cursor); -#endif - -} - -/** - * Recreates and draws cursor on desktop related to ToolBase. - */ -void ToolBase::sp_event_context_update_cursor() { - GtkWidget *w = GTK_WIDGET(sp_desktop_canvas(this->desktop)); - if (gtk_widget_get_window (w)) { - - GtkStyle *style = gtk_widget_get_style(w); - - /* fixme: */ - if (this->cursor_shape) { - GdkDisplay *display = gdk_display_get_default(); - if (gdk_display_supports_cursor_alpha(display) && gdk_display_supports_cursor_color(display)) { - bool fillHasColor=false, strokeHasColor=false; - guint32 fillColor = sp_desktop_get_color_tool(this->desktop, this->getPrefsPath(), true, &fillHasColor); - guint32 strokeColor = sp_desktop_get_color_tool(this->desktop, this->getPrefsPath(), false, &strokeHasColor); - double fillOpacity = fillHasColor ? sp_desktop_get_opacity_tool(this->desktop, this->getPrefsPath(), true) : 0; - double strokeOpacity = strokeHasColor ? sp_desktop_get_opacity_tool(this->desktop, this->getPrefsPath(), false) : 0; - - GdkPixbuf *pixbuf = sp_cursor_pixbuf_from_xpm( - this->cursor_shape, - style->black, style->white, - SP_RGBA32_U_COMPOSE(SP_RGBA32_R_U(fillColor),SP_RGBA32_G_U(fillColor),SP_RGBA32_B_U(fillColor),SP_COLOR_F_TO_U(fillOpacity)), - SP_RGBA32_U_COMPOSE(SP_RGBA32_R_U(strokeColor),SP_RGBA32_G_U(strokeColor),SP_RGBA32_B_U(strokeColor),SP_COLOR_F_TO_U(strokeOpacity)) - ); - if (pixbuf != NULL) { - if (this->cursor) { -#if GTK_CHECK_VERSION(3,0,0) - g_object_unref(this->cursor); -#else - gdk_cursor_unref(this->cursor); -#endif - } - this->cursor = gdk_cursor_new_from_pixbuf(display, pixbuf, this->hot_x, this->hot_y); - g_object_unref(pixbuf); - } - } else { - GdkPixbuf *pixbuf = gdk_pixbuf_new_from_xpm_data((const gchar **)this->cursor_shape); - - if (pixbuf) { - if (this->cursor) { -#if GTK_CHECK_VERSION(3,0,0) - g_object_unref(this->cursor); -#else - gdk_cursor_unref(this->cursor); -#endif - } - this->cursor = gdk_cursor_new_from_pixbuf(display, - pixbuf, this->hot_x, this->hot_y); - g_object_unref(pixbuf); - } - } - } - gdk_window_set_cursor(gtk_widget_get_window (w), this->cursor); - gdk_flush(); - } - this->desktop->waiting_cursor = false; -} - -/** - * Callback that gets called on initialization of ToolBase object. - * Redraws mouse cursor, at the moment. - */ - -/** - * When you override it, call this method first. - */ -void ToolBase::setup() { - this->pref_observer = new ToolPrefObserver(this->getPrefsPath(), this); - Inkscape::Preferences::get()->addObserver(*(this->pref_observer)); - - this->sp_event_context_update_cursor(); -} - -/** - * Gobbles next key events on the queue with the same keyval and mask. Returns the number of events consumed. - */ -gint gobble_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; -} - -/** - * Gobbles next motion notify events on the queue with the same mask. Returns the number of events consumed. - */ -gint gobble_motion_events(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_MOTION_NOTIFY - && (event_next->motion.state & mask)) { - // kill it - gdk_event_free(event_next); - // get next - event_next = gdk_event_get(); - i++; - } - // otherwise, put it back onto the queue - if (event_next) - gdk_event_put(event_next); - - return i; -} - -/** - * Toggles current tool between active tool and selector tool. - * Subroutine of sp_event_context_private_root_handler(). - */ -static void sp_toggle_selector(SPDesktop *dt) { - if (!dt->event_context) - return; - - if (tools_isactive(dt, TOOLS_SELECT)) { - if (selector_toggled) { - if (switch_selector_to) - tools_switch(dt, switch_selector_to); - selector_toggled = FALSE; - } else - return; - } else { - selector_toggled = TRUE; - switch_selector_to = tools_active(dt); - tools_switch(dt, TOOLS_SELECT); - } -} - -/** - * Toggles current tool between active tool and dropper tool. - * Subroutine of sp_event_context_private_root_handler(). - */ -void sp_toggle_dropper(SPDesktop *dt) { - if (!dt->event_context) - return; - - if (tools_isactive(dt, TOOLS_DROPPER)) { - if (dropper_toggled) { - if (switch_dropper_to) - tools_switch(dt, switch_dropper_to); - dropper_toggled = FALSE; - } else - return; - } else { - dropper_toggled = TRUE; - switch_dropper_to = tools_active(dt); - tools_switch(dt, TOOLS_DROPPER); - } -} - -/** - * Calculates and keeps track of scroll acceleration. - * Subroutine of sp_event_context_private_root_handler(). - */ -static gdouble accelerate_scroll(GdkEvent *event, gdouble acceleration, - SPCanvas */*canvas*/) { - guint32 time_diff = ((GdkEventKey *) event)->time - scroll_event_time; - - /* key pressed within 500ms ? (1/2 second) */ - if (time_diff > 500 || event->key.keyval != scroll_keyval) { - scroll_multiply = 1; // abort acceleration - } else { - scroll_multiply += acceleration; // continue acceleration - } - - scroll_event_time = ((GdkEventKey *) event)->time; - scroll_keyval = event->key.keyval; - - return scroll_multiply; -} - -/** - * Main event dispatch, gets called from Gdk. - */ -//static gint sp_event_context_private_root_handler( -// ToolBase *event_context, GdkEvent *event) { -// -// return event_context->ceventcontext->root_handler(event); -//} - -bool ToolBase::root_handler(GdkEvent* event) { - static Geom::Point button_w; - static unsigned int panning = 0; - static unsigned int panning_cursor = 0; - static unsigned int zoom_rb = 0; - - Inkscape::Preferences *prefs = Inkscape::Preferences::get(); - - /// @todo REmove redundant /value in preference keys - tolerance = prefs->getIntLimited("/options/dragtolerance/value", 0, 0, 100); - - gint ret = FALSE; - - switch (event->type) { - case GDK_2BUTTON_PRESS: - if (panning) { - panning = 0; - sp_canvas_item_ungrab(SP_CANVAS_ITEM(desktop->acetate), event->button.time); - ret = TRUE; - } else { - /* sp_desktop_dialog(); */ - } - break; - - case GDK_BUTTON_PRESS: - // save drag origin - xp = (gint) event->button.x; - yp = (gint) event->button.y; - within_tolerance = true; - - button_w = Geom::Point(event->button.x, event->button.y); - - switch (event->button.button) { - case 1: - if (this->space_panning) { - // When starting panning, make sure there are no snap events pending because these might disable the panning again - sp_event_context_discard_delayed_snap_event(this); - panning = 1; - - sp_canvas_item_grab(SP_CANVAS_ITEM(desktop->acetate), - GDK_KEY_RELEASE_MASK | GDK_BUTTON_RELEASE_MASK - | GDK_POINTER_MOTION_MASK - | GDK_POINTER_MOTION_HINT_MASK, NULL, - event->button.time - 1); - - ret = TRUE; - } - break; - - case 2: - if (event->button.state & GDK_SHIFT_MASK) { - zoom_rb = 2; - } else { - // When starting panning, make sure there are no snap events pending because these might disable the panning again - sp_event_context_discard_delayed_snap_event(this); - panning = 2; - - sp_canvas_item_grab(SP_CANVAS_ITEM(desktop->acetate), - GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK - | GDK_POINTER_MOTION_HINT_MASK, NULL, - event->button.time - 1); - - } - - ret = TRUE; - break; - - case 3: - if ((event->button.state & GDK_SHIFT_MASK) || (event->button.state & GDK_CONTROL_MASK)) { - // When starting panning, make sure there are no snap events pending because these might disable the panning again - sp_event_context_discard_delayed_snap_event(this); - panning = 3; - - sp_canvas_item_grab(SP_CANVAS_ITEM(desktop->acetate), - GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK - | GDK_POINTER_MOTION_HINT_MASK, NULL, - event->button.time); - - ret = TRUE; - } else { - sp_event_root_menu_popup(desktop, NULL, event); - } - break; - - default: - break; - } - break; - - case GDK_MOTION_NOTIFY: - if (panning) { - if (panning == 4 && !xp && !yp ) { - // + mouse panning started, save location and grab canvas - xp = event->motion.x; - yp = event->motion.y; - button_w = Geom::Point(event->motion.x, event->motion.y); - - sp_canvas_item_grab(SP_CANVAS_ITEM(desktop->acetate), - GDK_KEY_RELEASE_MASK | GDK_BUTTON_RELEASE_MASK - | GDK_POINTER_MOTION_MASK - | GDK_POINTER_MOTION_HINT_MASK, NULL, - event->motion.time - 1); - } - - if ((panning == 2 && !(event->motion.state & GDK_BUTTON2_MASK)) - || (panning == 1 && !(event->motion.state & GDK_BUTTON1_MASK)) - || (panning == 3 && !(event->motion.state & GDK_BUTTON3_MASK))) { - /* Gdk seems to lose button release for us sometimes :-( */ - panning = 0; - sp_canvas_item_ungrab(SP_CANVAS_ITEM(desktop->acetate), event->button.time); - ret = TRUE; - } else { - if (within_tolerance && (abs((gint) event->motion.x - xp) - < tolerance) && (abs((gint) event->motion.y - yp) - < tolerance)) { - // do not drag if we're within tolerance from origin - break; - } - - // Once the user has moved farther than tolerance from - // the original location (indicating they intend to move - // the object, not click), then always process the motion - // notify coordinates as given (no snapping back to origin) - within_tolerance = false; - - // gobble subsequent motion events to prevent "sticking" - // when scrolling is slow - gobble_motion_events(panning == 2 ? GDK_BUTTON2_MASK : (panning - == 1 ? GDK_BUTTON1_MASK : GDK_BUTTON3_MASK)); - - if (panning_cursor == 0) { - panning_cursor = 1; - sp_event_context_set_cursor(this, GDK_FLEUR); - } - - Geom::Point const motion_w(event->motion.x, event->motion.y); - Geom::Point const moved_w(motion_w - button_w); - this->desktop->scroll_world(moved_w, true); // we're still scrolling, do not redraw - ret = TRUE; - } - } else if (zoom_rb) { - Geom::Point const motion_w(event->motion.x, event->motion.y); - Geom::Point const motion_dt(desktop->w2d(motion_w)); - - if (within_tolerance && (abs((gint) event->motion.x - xp) - < tolerance) && (abs((gint) event->motion.y - yp) - < tolerance)) { - break; // do not drag if we're within tolerance from origin - } - - // Once the user has moved farther than tolerance from the original location - // (indicating they intend to move the object, not click), then always process the - // motion notify coordinates as given (no snapping back to origin) - within_tolerance = false; - - if (Inkscape::Rubberband::get(desktop)->is_started()) { - Inkscape::Rubberband::get(desktop)->move(motion_dt); - } else { - Inkscape::Rubberband::get(desktop)->start(desktop, motion_dt); - } - - if (zoom_rb == 2) { - gobble_motion_events(GDK_BUTTON2_MASK); - } - } - break; - - case GDK_BUTTON_RELEASE: - xp = yp = 0; - - if (panning_cursor == 1) { - panning_cursor = 0; - GtkWidget *w = GTK_WIDGET(sp_desktop_canvas(this->desktop)); - gdk_window_set_cursor(gtk_widget_get_window (w), this->cursor); - } - - if (within_tolerance && (panning || zoom_rb)) { - zoom_rb = 0; - - if (panning) { - panning = 0; - sp_canvas_item_ungrab(SP_CANVAS_ITEM(desktop->acetate), - event->button.time); - } - - Geom::Point const event_w(event->button.x, event->button.y); - Geom::Point const event_dt(desktop->w2d(event_w)); - - double const zoom_inc = prefs->getDoubleLimited( - "/options/zoomincrement/value", M_SQRT2, 1.01, 10); - - desktop->zoom_relative_keep_point(event_dt, (event->button.state - & GDK_SHIFT_MASK) ? 1 / zoom_inc : zoom_inc); - - desktop->updateNow(); - ret = TRUE; - } else if (panning == event->button.button) { - panning = 0; - sp_canvas_item_ungrab(SP_CANVAS_ITEM(desktop->acetate), - event->button.time); - - // in slow complex drawings, some of the motion events are lost; - // to make up for this, we scroll it once again to the button-up event coordinates - // (i.e. canvas will always get scrolled all the way to the mouse release point, - // even if few intermediate steps were visible) - Geom::Point const motion_w(event->button.x, event->button.y); - Geom::Point const moved_w(motion_w - button_w); - - this->desktop->scroll_world(moved_w); - desktop->updateNow(); - ret = TRUE; - } else if (zoom_rb == event->button.button) { - zoom_rb = 0; - - Geom::OptRect const b = Inkscape::Rubberband::get(desktop)->getRectangle(); - Inkscape::Rubberband::get(desktop)->stop(); - - if (b && !within_tolerance) { - desktop->set_display_area(*b, 10); - } - - ret = TRUE; - } - break; - - case GDK_KEY_PRESS: { - double const acceleration = prefs->getDoubleLimited( - "/options/scrollingacceleration/value", 0, 0, 6); - - int const key_scroll = prefs->getIntLimited("/options/keyscroll/value", - 10, 0, 1000); - - switch (get_group0_keyval(&event->key)) { - // GDK insists on stealing these keys (F1 for no idea what, tab for cycling widgets - // in the editing window). So we resteal them back and run our regular shortcut - // invoker on them. - unsigned int shortcut; - case GDK_KEY_Tab: - case GDK_KEY_ISO_Left_Tab: - case GDK_KEY_F1: - shortcut = get_group0_keyval(&event->key); - - if (event->key.state & GDK_SHIFT_MASK) { - shortcut |= SP_SHORTCUT_SHIFT_MASK; - } - - if (event->key.state & GDK_CONTROL_MASK) { - shortcut |= SP_SHORTCUT_CONTROL_MASK; - } - - if (event->key.state & GDK_MOD1_MASK) { - shortcut |= SP_SHORTCUT_ALT_MASK; - } - - ret = sp_shortcut_invoke(shortcut, desktop); - break; - - case GDK_KEY_Q: - case GDK_KEY_q: - if (desktop->quick_zoomed()) { - ret = TRUE; - } - if (!MOD__SHIFT(event) && !MOD__CTRL(event) && !MOD__ALT(event)) { - desktop->zoom_quick(true); - ret = TRUE; - } - break; - - case GDK_KEY_W: - case GDK_KEY_w: - case GDK_KEY_F4: - /* Close view */ - if (MOD__CTRL_ONLY(event)) { - sp_ui_close_view(NULL); - ret = TRUE; - } - break; - - case GDK_KEY_Left: // Ctrl Left - case GDK_KEY_KP_Left: - case GDK_KEY_KP_4: - if (MOD__CTRL_ONLY(event)) { - int i = (int) floor(key_scroll * accelerate_scroll(event, - acceleration, sp_desktop_canvas(desktop))); - - gobble_key_events(get_group0_keyval(&event->key), GDK_CONTROL_MASK); - this->desktop->scroll_world(i, 0); - ret = TRUE; - } - break; - - case GDK_KEY_Up: // Ctrl Up - case GDK_KEY_KP_Up: - case GDK_KEY_KP_8: - if (MOD__CTRL_ONLY(event)) { - int i = (int) floor(key_scroll * accelerate_scroll(event, - acceleration, sp_desktop_canvas(desktop))); - - gobble_key_events(get_group0_keyval(&event->key), GDK_CONTROL_MASK); - this->desktop->scroll_world(0, i); - ret = TRUE; - } - break; - - case GDK_KEY_Right: // Ctrl Right - case GDK_KEY_KP_Right: - case GDK_KEY_KP_6: - if (MOD__CTRL_ONLY(event)) { - int i = (int) floor(key_scroll * accelerate_scroll(event, - acceleration, sp_desktop_canvas(desktop))); - - gobble_key_events(get_group0_keyval(&event->key), GDK_CONTROL_MASK); - this->desktop->scroll_world(-i, 0); - ret = TRUE; - } - break; - - case GDK_KEY_Down: // Ctrl Down - case GDK_KEY_KP_Down: - case GDK_KEY_KP_2: - if (MOD__CTRL_ONLY(event)) { - int i = (int) floor(key_scroll * accelerate_scroll(event, - acceleration, sp_desktop_canvas(desktop))); - - gobble_key_events(get_group0_keyval(&event->key), GDK_CONTROL_MASK); - this->desktop->scroll_world(0, -i); - ret = TRUE; - } - break; - - case GDK_KEY_F10: - if (MOD__SHIFT_ONLY(event)) { - sp_event_root_menu_popup(desktop, NULL, event); - ret = TRUE; - } - break; - - case GDK_KEY_space: - xp = yp = 0; - within_tolerance = true; - panning = 4; - - this->space_panning = true; - this->message_context->set(Inkscape::INFORMATION_MESSAGE, - _("Space+mouse move to pan canvas")); - - ret = TRUE; - break; - - case GDK_KEY_z: - case GDK_KEY_Z: - if (MOD__ALT_ONLY(event)) { - desktop->zoom_grab_focus(); - ret = TRUE; - } - break; - - default: - break; - } - } - break; - - case GDK_KEY_RELEASE: - // Stop panning on any key release - if (this->space_panning) { - this->space_panning = false; - this->message_context->clear(); - } - - if (panning) { - panning = 0; - xp = yp = 0; - - sp_canvas_item_ungrab(SP_CANVAS_ITEM(desktop->acetate), - event->key.time); - - desktop->updateNow(); - } - - if (panning_cursor == 1) { - panning_cursor = 0; - GtkWidget *w = GTK_WIDGET(sp_desktop_canvas(this->desktop)); - gdk_window_set_cursor(gtk_widget_get_window (w), this->cursor); - } - - switch (get_group0_keyval(&event->key)) { - case GDK_KEY_space: - if (within_tolerance == true) { - // Space was pressed, but not panned - sp_toggle_selector(desktop); - ret = TRUE; - } - - within_tolerance = false; - break; - - case GDK_KEY_Q: - case GDK_KEY_q: - if (desktop->quick_zoomed()) { - desktop->zoom_quick(false); - ret = TRUE; - } - break; - - default: - break; - } - break; - - case GDK_SCROLL: { - bool ctrl = (event->scroll.state & GDK_CONTROL_MASK); - bool wheelzooms = prefs->getBool("/options/wheelzooms/value"); - - int const wheel_scroll = prefs->getIntLimited( - "/options/wheelscroll/value", 40, 0, 1000); - -#if GTK_CHECK_VERSION(3,0,0) - // Size of smooth-scrolls (only used in GTK+ 3) - gdouble delta_x = 0; - gdouble delta_y = 0; -#endif - - /* shift + wheel, pan left--right */ - if (event->scroll.state & GDK_SHIFT_MASK) { - switch (event->scroll.direction) { - case GDK_SCROLL_UP: - desktop->scroll_world(wheel_scroll, 0); - break; - - case GDK_SCROLL_DOWN: - desktop->scroll_world(-wheel_scroll, 0); - break; - - default: - break; - } - - /* ctrl + wheel, zoom in--out */ - } else if ((ctrl && !wheelzooms) || (!ctrl && wheelzooms)) { - double rel_zoom; - double const zoom_inc = prefs->getDoubleLimited( - "/options/zoomincrement/value", M_SQRT2, 1.01, 10); - - switch (event->scroll.direction) { - case GDK_SCROLL_UP: - rel_zoom = zoom_inc; - break; - - case GDK_SCROLL_DOWN: - rel_zoom = 1 / zoom_inc; - break; - - default: - rel_zoom = 0.0; - break; - } - - if (rel_zoom != 0.0) { - Geom::Point const scroll_dt = desktop->point(); - desktop->zoom_relative_keep_point(scroll_dt, rel_zoom); - } - - /* no modifier, pan up--down (left--right on multiwheel mice?) */ - } else { - switch (event->scroll.direction) { - case GDK_SCROLL_UP: - desktop->scroll_world(0, wheel_scroll); - break; - - case GDK_SCROLL_DOWN: - desktop->scroll_world(0, -wheel_scroll); - break; - - case GDK_SCROLL_LEFT: - desktop->scroll_world(wheel_scroll, 0); - break; - - case GDK_SCROLL_RIGHT: - desktop->scroll_world(-wheel_scroll, 0); - break; - -#if GTK_CHECK_VERSION(3,0,0) - case GDK_SCROLL_SMOOTH: - gdk_event_get_scroll_deltas(event, &delta_x, &delta_y); - desktop->scroll_world(delta_x, delta_y); - break; -#endif - } - } - break; - } - default: - break; - } - - return ret; -} - -/** - * Handles item specific events. Gets called from Gdk. - * - * Only reacts to right mouse button at the moment. - * \todo Fixme: do context sensitive popup menu on items. - */ -//gint sp_event_context_private_item_handler(ToolBase *ec, SPItem *item, -// GdkEvent *event) { -// -// return ec->ceventcontext->item_handler(item, event); -//} - -bool ToolBase::item_handler(SPItem* item, GdkEvent* event) { - int ret = FALSE; - - switch (event->type) { - case GDK_BUTTON_PRESS: - if ((event->button.button == 3) && !((event->button.state & GDK_SHIFT_MASK) || (event->button.state & GDK_CONTROL_MASK))) { - sp_event_root_menu_popup(this->desktop, item, event); - ret = TRUE; - } - break; - - default: - break; - } - - return ret; -} - -/** - * Returns true if we're hovering above a knot (needed because we don't want to pre-snap in that case). - */ -bool sp_event_context_knot_mouseover(ToolBase *ec) -{ - if (ec->shape_editor) { - return ec->shape_editor->knot_mouseover(); - } - - return false; -} - -/** - * Creates new ToolBase object and calls its virtual setup() function. - * @todo This is bogus. pref_path should be a private property of the inheriting objects. - */ -//ToolBase * -//sp_event_context_new(GType type, SPDesktop *desktop, gchar const *pref_path, -// unsigned int key) { -// g_return_val_if_fail(g_type_is_a(type, SP_TYPE_EVENT_CONTEXT), NULL); -// g_return_val_if_fail(desktop != NULL, NULL); -// -// ToolBase * const ec = static_cast(g_object_new(type, NULL)); -// -// ec->desktop = desktop; -// ec->_message_context -// = new Inkscape::MessageContext(desktop->messageStack()); -// ec->key = key; -// ec->pref_observer = NULL; -// -// if (pref_path) { -// ec->pref_observer = new ToolPrefObserver(pref_path, ec); -// -// Inkscape::Preferences *prefs = Inkscape::Preferences::get(); -// prefs->addObserver(*(ec->pref_observer)); -// } -// -//// if ((SP_EVENT_CONTEXT_CLASS(G_OBJECT_GET_CLASS(ec)))->setup) -//// (SP_EVENT_CONTEXT_CLASS(G_OBJECT_GET_CLASS(ec)))->setup(ec); -// ec->ceventcontext->setup(); -// -// return ec; -//} - -/** - * Finishes ToolBase. - */ -//void sp_event_context_finish(ToolBase *ec) { -// g_return_if_fail(ec != NULL); -// g_return_if_fail(SP_IS_EVENT_CONTEXT(ec)); -// -// ec->enableSelectionCue(false); -// -//// if (ec->next) { -//// g_warning("Finishing event context with active link\n"); -//// } -// -//// if ((SP_EVENT_CONTEXT_CLASS(G_OBJECT_GET_CLASS(ec)))->finish) -//// (SP_EVENT_CONTEXT_CLASS(G_OBJECT_GET_CLASS(ec)))->finish(ec); -// ec->finish(); -//} - -//-------------------------------member functions - -/** - * Enables/disables the ToolBase's SelCue. - */ -void ToolBase::enableSelectionCue(bool enable) { - if (enable) { - if (!_selcue) { - _selcue = new Inkscape::SelCue(desktop); - } - } else { - delete _selcue; - _selcue = NULL; - } -} - -/** - * Enables/disables the ToolBase's GrDrag. - */ -void ToolBase::enableGrDrag(bool enable) { - if (enable) { - if (!_grdrag) { - _grdrag = new GrDrag(desktop); - } - } else { - if (_grdrag) { - delete _grdrag; - _grdrag = NULL; - } - } -} - -/** - * Delete a selected GrDrag point - */ -bool ToolBase::deleteSelectedDrag(bool just_one) { - - if (_grdrag && _grdrag->selected) { - _grdrag->deleteSelected(just_one); - return TRUE; - } - - return FALSE; -} - -/** - * Calls virtual set() function of ToolBase. - */ -void sp_event_context_read(ToolBase *ec, gchar const *key) { - g_return_if_fail(ec != NULL); - g_return_if_fail(SP_IS_EVENT_CONTEXT(ec)); - g_return_if_fail(key != NULL); - -// if ((SP_EVENT_CONTEXT_CLASS(G_OBJECT_GET_CLASS(ec)))->set) { -// Inkscape::Preferences *prefs = Inkscape::Preferences::get(); -// Inkscape::Preferences::Entry val = prefs->getEntry( -// ec->pref_observer->observed_path + '/' + key); -// (SP_EVENT_CONTEXT_CLASS(G_OBJECT_GET_CLASS(ec)))->set(ec, &val); -// } - - Inkscape::Preferences *prefs = Inkscape::Preferences::get(); - Inkscape::Preferences::Entry val = prefs->getEntry(ec->pref_observer->observed_path + '/' + key); - ec->set(val); -} - -/** - * Calls virtual activate() function of ToolBase. - */ -void sp_event_context_activate(ToolBase *ec) { - g_return_if_fail(ec != NULL); - g_return_if_fail(SP_IS_EVENT_CONTEXT(ec)); - - // Make sure no delayed snapping events are carried over after switching contexts - // (this is only an additional safety measure against sloppy coding, because each - // context should take care of this by itself. - sp_event_context_discard_delayed_snap_event(ec); - -// if ((SP_EVENT_CONTEXT_CLASS(G_OBJECT_GET_CLASS(ec)))->activate) -// (SP_EVENT_CONTEXT_CLASS(G_OBJECT_GET_CLASS(ec)))->activate(ec); - ec->activate(); -} - -/** - * Calls virtual deactivate() function of ToolBase. - */ -//void sp_event_context_deactivate(ToolBase *ec) { -// g_return_if_fail(ec != NULL); -// g_return_if_fail(SP_IS_EVENT_CONTEXT(ec)); -// -//// if ((SP_EVENT_CONTEXT_CLASS(G_OBJECT_GET_CLASS(ec)))->deactivate) -//// (SP_EVENT_CONTEXT_CLASS(G_OBJECT_GET_CLASS(ec)))->deactivate(ec); -// ec->deactivate(); -//} - -/** - * Calls virtual root_handler(), the main event handling function. - */ -gint sp_event_context_root_handler(ToolBase * event_context, - GdkEvent * event) -{ - switch (event->type) { - case GDK_MOTION_NOTIFY: - sp_event_context_snap_delay_handler(event_context, NULL, NULL, - (GdkEventMotion *) event, - DelayedSnapEvent::EVENTCONTEXT_ROOT_HANDLER); - break; - case GDK_BUTTON_RELEASE: - if (event_context && event_context->_delayed_snap_event) { - // If we have any pending snapping action, then invoke it now - sp_event_context_snap_watchdog_callback( - event_context->_delayed_snap_event); - } - break; - case GDK_BUTTON_PRESS: - case GDK_2BUTTON_PRESS: - case GDK_3BUTTON_PRESS: - // Snapping will be on hold if we're moving the mouse at high speeds. When starting - // drawing a new shape we really should snap though. - event_context->desktop->namedview->snap_manager.snapprefs.setSnapPostponedGlobally( - false); - break; - default: - break; - } - - return sp_event_context_virtual_root_handler(event_context, event); -} - -gint sp_event_context_virtual_root_handler(ToolBase * event_context, GdkEvent * event) { - gint ret = false; - if (event_context) { // If no event-context is available then do nothing, otherwise Inkscape would crash - // (see the comment in SPDesktop::set_event_context, and bug LP #622350) - //ret = (SP_EVENT_CONTEXT_CLASS(G_OBJECT_GET_CLASS(event_context)))->root_handler(event_context, event); - ret = event_context->root_handler(event); - - set_event_location(event_context->desktop, event); - } - return ret; -} - -/** - * Calls virtual item_handler(), the item event handling function. - */ -gint sp_event_context_item_handler(ToolBase * event_context, - SPItem * item, GdkEvent * event) { - switch (event->type) { - case GDK_MOTION_NOTIFY: - sp_event_context_snap_delay_handler(event_context, (gpointer) item, NULL, (GdkEventMotion *) event, DelayedSnapEvent::EVENTCONTEXT_ITEM_HANDLER); - break; - case GDK_BUTTON_RELEASE: - if (event_context && event_context->_delayed_snap_event) { - // If we have any pending snapping action, then invoke it now - sp_event_context_snap_watchdog_callback(event_context->_delayed_snap_event); - } - break; - case GDK_BUTTON_PRESS: - case GDK_2BUTTON_PRESS: - case GDK_3BUTTON_PRESS: - // Snapping will be on hold if we're moving the mouse at high speeds. When starting - // drawing a new shape we really should snap though. - event_context->desktop->namedview->snap_manager.snapprefs.setSnapPostponedGlobally(false); - break; - default: - break; - } - - return sp_event_context_virtual_item_handler(event_context, item, event); -} - -gint sp_event_context_virtual_item_handler(ToolBase * event_context, SPItem * item, GdkEvent * event) { - gint ret = false; - if (event_context) { // If no event-context is available then do nothing, otherwise Inkscape would crash - // (see the comment in SPDesktop::set_event_context, and bug LP #622350) - //ret = (SP_EVENT_CONTEXT_CLASS(G_OBJECT_GET_CLASS(event_context)))->item_handler(event_context, item, event); - ret = event_context->item_handler(item, event); - - if (!ret) { - ret = sp_event_context_virtual_root_handler(event_context, event); - } else { - set_event_location(event_context->desktop, event); - } - } - - return ret; -} - -/** - * Shows coordinates on status bar. - */ -static void set_event_location(SPDesktop *desktop, GdkEvent *event) { - if (event->type != GDK_MOTION_NOTIFY) { - return; - } - - Geom::Point const button_w(event->button.x, event->button.y); - Geom::Point const button_dt(desktop->w2d(button_w)); - desktop->set_coordinate_status(button_dt); -} - -//------------------------------------------------------------------- -/** - * Create popup menu and tell Gtk to show it. - */ -void sp_event_root_menu_popup(SPDesktop *desktop, SPItem *item, GdkEvent *event) { - - // It seems the param item is the SPItem at the bottom of the z-order - // Using the same function call used on left click in sp_select_context_item_handler() to get top of z-order - // fixme: sp_canvas_arena should set the top z-order object as arena->active - item = sp_event_context_find_item (desktop, - Geom::Point(event->button.x, event->button.y), FALSE, FALSE); - - /* fixme: This is not what I want but works for now (Lauris) */ - if (event->type == GDK_KEY_PRESS) { - item = sp_desktop_selection(desktop)->singleItem(); - } - - ContextMenu* CM = new ContextMenu(desktop, item); - CM->show(); - - switch (event->type) { - case GDK_BUTTON_PRESS: - CM->popup(event->button.button, event->button.time); - break; - case GDK_KEY_PRESS: - CM->popup(0, event->key.time); - break; - default: - break; - } -} - -/** - * Show tool context specific modifier tip. - */ -void sp_event_show_modifier_tip(Inkscape::MessageContext *message_context, - GdkEvent *event, gchar const *ctrl_tip, gchar const *shift_tip, - gchar const *alt_tip) { - guint keyval = get_group0_keyval(&event->key); - - bool ctrl = ctrl_tip && (MOD__CTRL(event) || (keyval == GDK_KEY_Control_L) || (keyval - == GDK_KEY_Control_R)); - bool shift = shift_tip && (MOD__SHIFT(event) || (keyval == GDK_KEY_Shift_L) || (keyval - == GDK_KEY_Shift_R)); - bool alt = alt_tip && (MOD__ALT(event) || (keyval == GDK_KEY_Alt_L) || (keyval - == GDK_KEY_Alt_R) || (keyval == GDK_KEY_Meta_L) || (keyval == GDK_KEY_Meta_R)); - - gchar *tip = g_strdup_printf("%s%s%s%s%s", (ctrl ? ctrl_tip : ""), (ctrl - && (shift || alt) ? "; " : ""), (shift ? shift_tip : ""), ((ctrl - || shift) && alt ? "; " : ""), (alt ? alt_tip : "")); - - if (strlen(tip) > 0) { - message_context->flash(Inkscape::INFORMATION_MESSAGE, tip); - } - - g_free(tip); -} - -/** - * Return the keyval corresponding to the key event in group 0, i.e., - * in the main (English) layout. - * - * Use this instead of simply event->keyval, so that your keyboard shortcuts - * work regardless of layouts (e.g., in Cyrillic). - */ -guint get_group0_keyval(GdkEventKey *event) { - guint keyval = 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*/, &keyval, - NULL, NULL, NULL); - - return keyval; -} - -/** - * Returns item at point p in desktop. - * - * If state includes alt key mask, cyclically selects under; honors - * into_groups. - */ -SPItem *sp_event_context_find_item(SPDesktop *desktop, Geom::Point const &p, - bool select_under, bool into_groups) -{ - SPItem *item = 0; - - if (select_under) { - SPItem *selected_at_point = desktop->getItemFromListAtPointBottom( - desktop->selection->itemList(), p); - item = desktop->getItemAtPoint(p, into_groups, selected_at_point); - if (item == NULL) { // we may have reached bottom, flip over to the top - item = desktop->getItemAtPoint(p, into_groups, NULL); - } - } else { - item = desktop->getItemAtPoint(p, into_groups, NULL); - } - - return item; -} - -/** - * Returns item if it is under point p in desktop, at any depth; otherwise returns NULL. - * - * Honors into_groups. - */ -SPItem * -sp_event_context_over_item(SPDesktop *desktop, SPItem *item, - Geom::Point const &p) { - GSList *temp = NULL; - temp = g_slist_prepend(temp, item); - SPItem *item_at_point = desktop->getItemFromListAtPointBottom(temp, p); - g_slist_free(temp); - - return item_at_point; -} - -ShapeEditor * -sp_event_context_get_shape_editor(ToolBase *ec) { - return ec->shape_editor; -} - -void event_context_print_event_info(GdkEvent *event, bool print_return) { - switch (event->type) { - case GDK_BUTTON_PRESS: - g_print("GDK_BUTTON_PRESS"); - break; - case GDK_2BUTTON_PRESS: - g_print("GDK_2BUTTON_PRESS"); - break; - case GDK_3BUTTON_PRESS: - g_print("GDK_3BUTTON_PRESS"); - break; - - case GDK_MOTION_NOTIFY: - g_print("GDK_MOTION_NOTIFY"); - break; - case GDK_ENTER_NOTIFY: - g_print("GDK_ENTER_NOTIFY"); - break; - - case GDK_LEAVE_NOTIFY: - g_print("GDK_LEAVE_NOTIFY"); - break; - case GDK_BUTTON_RELEASE: - g_print("GDK_BUTTON_RELEASE"); - break; - - case GDK_KEY_PRESS: - g_print("GDK_KEY_PRESS: %d", get_group0_keyval(&event->key)); - break; - case GDK_KEY_RELEASE: - g_print("GDK_KEY_RELEASE: %d", get_group0_keyval(&event->key)); - break; - default: - //g_print ("even type not recognized"); - break; - } - - if (print_return) { - g_print("\n"); - } -} - -/** - * Analyses the current event, calculates the mouse speed, turns snapping off (temporarily) if the - * mouse speed is above a threshold, and stores the current event such that it can be re-triggered when needed - * (re-triggering is controlled by a watchdog timer). - * - * @param ec Pointer to the event context. - * @param dse_item Pointer that store a reference to a canvas or to an item. - * @param dse_item2 Another pointer, storing a reference to a knot or controlpoint. - * @param event Pointer to the motion event. - * @param origin Identifier (enum) specifying where the delay (and the call to this method) were initiated. - */ -void sp_event_context_snap_delay_handler(ToolBase *ec, - gpointer const dse_item, gpointer const dse_item2, GdkEventMotion *event, - DelayedSnapEvent::DelayedSnapEventOrigin origin) -{ - static guint32 prev_time; - static boost::optional prev_pos; - - if (ec->_dse_callback_in_process) { - return; - } - - // Snapping occurs when dragging with the left mouse button down, or when hovering e.g. in the pen tool with left mouse button up - bool const c1 = event->state & GDK_BUTTON2_MASK; // We shouldn't hold back any events when other mouse buttons have been - bool const c2 = event->state & GDK_BUTTON3_MASK; // pressed, e.g. when scrolling with the middle mouse button; if we do then - // Inkscape will get stuck in an unresponsive state - bool const c3 = tools_isactive(ec->desktop, TOOLS_CALLIGRAPHIC); - // The snap delay will repeat the last motion event, which will lead to - // erroneous points in the calligraphy context. And because we don't snap - // in this context, we might just as well disable the snap delay all together - - if (c1 || c2 || c3) { - // Make sure that we don't send any pending snap events to a context if we know in advance - // that we're not going to snap any way (e.g. while scrolling with middle mouse button) - // Any motion event might affect the state of the context, leading to unexpected behavior - sp_event_context_discard_delayed_snap_event(ec); - } else if (ec->desktop - && ec->desktop->namedview->snap_manager.snapprefs.getSnapEnabledGlobally()) { - // Snap when speed drops below e.g. 0.02 px/msec, or when no motion events have occurred for some period. - // i.e. snap when we're at stand still. A speed threshold enforces snapping for tablets, which might never - // be fully at stand still and might keep spitting out motion events. - ec->desktop->namedview->snap_manager.snapprefs.setSnapPostponedGlobally(true); // put snapping on hold - - Geom::Point event_pos(event->x, event->y); - guint32 event_t = gdk_event_get_time((GdkEvent *) event); - - if (prev_pos) { - Geom::Coord dist = Geom::L2(event_pos - *prev_pos); - guint32 delta_t = event_t - prev_time; - gdouble speed = delta_t > 0 ? dist / delta_t : 1000; - //std::cout << "Mouse speed = " << speed << " px/msec " << std::endl; - if (speed > 0.02) { // Jitter threshold, might be needed for tablets - // We're moving fast, so postpone any snapping until the next GDK_MOTION_NOTIFY event. We - // will keep on postponing the snapping as long as the speed is high. - // We must snap at some point in time though, so set a watchdog timer at some time from - // now, just in case there's no future motion event that drops under the speed limit (when - // stopping abruptly) - delete ec->_delayed_snap_event; - ec->_delayed_snap_event = new DelayedSnapEvent(ec, dse_item, dse_item2, - event, origin); // watchdog is reset, i.e. pushed forward in time - // If the watchdog expires before a new motion event is received, we will snap (as explained - // above). This means however that when the timer is too short, we will always snap and that the - // speed threshold is ineffective. In the extreme case the delay is set to zero, and snapping will - // be immediate, as it used to be in the old days ;-). - } else { // Speed is very low, so we're virtually at stand still - // But if we're really standing still, then we should snap now. We could use some low-pass filtering, - // otherwise snapping occurs for each jitter movement. For this filtering we'll leave the watchdog to expire, - // snap, and set a new watchdog again. - if (ec->_delayed_snap_event == NULL) { // no watchdog has been set - // it might have already expired, so we'll set a new one; the snapping frequency will be limited this way - ec->_delayed_snap_event = new DelayedSnapEvent(ec, dse_item, - dse_item2, event, origin); - } // else: watchdog has been set before and we'll wait for it to expire - } - } else { - // This is the first GDK_MOTION_NOTIFY event, so postpone snapping and set the watchdog - g_assert(ec->_delayed_snap_event == NULL); - ec->_delayed_snap_event = new DelayedSnapEvent(ec, dse_item, dse_item2, - event, origin); - } - - prev_pos = event_pos; - prev_time = event_t; - } -} - -/** - * When the snap delay watchdog timer barks, this method will be called and will re-inject the last motion - * event in an appropriate place, with snapping being turned on again. - */ -gboolean sp_event_context_snap_watchdog_callback(gpointer data) { - // Snap NOW! For this the "postponed" flag will be reset and the last motion event will be repeated - DelayedSnapEvent *dse = reinterpret_cast (data); - - if (dse == NULL) { - // This might occur when this method is called directly, i.e. not through the timer - // E.g. on GDK_BUTTON_RELEASE in sp_event_context_root_handler() - return FALSE; - } - - ToolBase *ec = dse->getEventContext(); - if (ec == NULL) { - delete dse; - return false; - } - if (ec->desktop == NULL) { - ec->_delayed_snap_event = NULL; - delete dse; - return false; - } - - ec->_dse_callback_in_process = true; - - SPDesktop *dt = ec->desktop; - dt->namedview->snap_manager.snapprefs.setSnapPostponedGlobally(false); - - // Depending on where the delayed snap event originated from, we will inject it back at it's origin - // The switch below takes care of that and prepares the relevant parameters - switch (dse->getOrigin()) { - case DelayedSnapEvent::EVENTCONTEXT_ROOT_HANDLER: - sp_event_context_virtual_root_handler(ec, dse->getEvent()); - break; - case DelayedSnapEvent::EVENTCONTEXT_ITEM_HANDLER: { - gpointer item = dse->getItem(); - if (item && SP_IS_ITEM(item)) { - sp_event_context_virtual_item_handler(ec, SP_ITEM(item), dse->getEvent()); - } - } - break; - case DelayedSnapEvent::KNOT_HANDLER: { - gpointer knot = dse->getItem2(); - if (knot && SP_IS_KNOT(knot)) { - sp_knot_handler_request_position(dse->getEvent(), SP_KNOT(knot)); - } - } - break; - case DelayedSnapEvent::CONTROL_POINT_HANDLER: { - using Inkscape::UI::ControlPoint; - gpointer pitem2 = dse->getItem2(); - if (!pitem2) - { - ec->_delayed_snap_event = NULL; - delete dse; - return false; - } - ControlPoint *point = reinterpret_cast (pitem2); - if (point) { - if (point->position().isFinite() && (dt == point->_desktop)) { - point->_eventHandler(ec, dse->getEvent()); - } - else { - //workaround: - //[Bug 781893] Crash after moving a Bezier node after Knot path effect? - // --> at some time, some point with X = 0 and Y = nan (not a number) is created ... - // even so, the desktop pointer is invalid and equal to 0xff - g_warning ("encountered non finite point when evaluating snapping callback"); - } - } - } - break; - case DelayedSnapEvent::GUIDE_HANDLER: { - gpointer item = dse->getItem(); - gpointer item2 = dse->getItem2(); - if (item && item2) { - g_assert(SP_IS_CANVAS_ITEM(item)); - g_assert(SP_IS_GUIDE(item2)); - sp_dt_guide_event(SP_CANVAS_ITEM(item), dse->getEvent(), item2); - } - } - break; - case DelayedSnapEvent::GUIDE_HRULER: - case DelayedSnapEvent::GUIDE_VRULER: { - gpointer item = dse->getItem(); - gpointer item2 = dse->getItem2(); - if (item && item2) { - g_assert(GTK_IS_WIDGET(item)); - g_assert(SP_IS_DESKTOP_WIDGET(item2)); - if (dse->getOrigin() == DelayedSnapEvent::GUIDE_HRULER) { - sp_dt_hruler_event(GTK_WIDGET(item), dse->getEvent(), SP_DESKTOP_WIDGET(item2)); - } else { - sp_dt_vruler_event(GTK_WIDGET(item), dse->getEvent(), SP_DESKTOP_WIDGET(item2)); - } - } - } - break; - default: - g_warning("Origin of snap-delay event has not been defined!;"); - break; - } - - ec->_delayed_snap_event = NULL; - delete dse; - - ec->_dse_callback_in_process = false; - - return FALSE; //Kills the timer and stops it from executing this callback over and over again. -} - -void sp_event_context_discard_delayed_snap_event(ToolBase *ec) { - delete ec->_delayed_snap_event; - ec->_delayed_snap_event = NULL; - ec->desktop->namedview->snap_manager.snapprefs.setSnapPostponedGlobally(false); -} - -} -} -} - -/* - Local Variables: - mode:c++ - c-file-style:"stroustrup" - c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) - indent-tabs-mode:nil - fill-column:99 - End: - */ -// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/event-context.h b/src/event-context.h deleted file mode 100644 index 5185f89b1..000000000 --- a/src/event-context.h +++ /dev/null @@ -1,244 +0,0 @@ -#ifndef SEEN_SP_EVENT_CONTEXT_H -#define SEEN_SP_EVENT_CONTEXT_H - -/* - * Authors: - * Lauris Kaplinski - * Frank Felfe - * - * Copyright (C) 1999-2002 authors - * Copyright (C) 2001-2002 Ximian, Inc. - * - * Released under GNU GPL, read the file 'COPYING' for more information - */ - -#include -#include -#include "knot.h" - -#include "2geom/forward.h" -#include "preferences.h" - -class GrDrag; -class SPDesktop; -class SPItem; -class ShapeEditor; - -namespace Inkscape { - class MessageContext; - class SelCue; - namespace XML { - class Node; - } -} - -#define SP_EVENT_CONTEXT(obj) (dynamic_cast((Inkscape::UI::Tools::ToolBase*)obj)) -#define SP_IS_EVENT_CONTEXT(obj) (dynamic_cast((const Inkscape::UI::Tools::ToolBase*)obj) != NULL) - -namespace Inkscape { -namespace UI { -namespace Tools { - -class ToolBase; - -gboolean sp_event_context_snap_watchdog_callback(gpointer data); -void sp_event_context_discard_delayed_snap_event(ToolBase *ec); - -class DelayedSnapEvent -{ -public: - enum DelayedSnapEventOrigin { - UNDEFINED_HANDLER = 0, - EVENTCONTEXT_ROOT_HANDLER, - EVENTCONTEXT_ITEM_HANDLER, - KNOT_HANDLER, - CONTROL_POINT_HANDLER, - GUIDE_HANDLER, - GUIDE_HRULER, - GUIDE_VRULER - }; - - DelayedSnapEvent(ToolBase *event_context, gpointer const dse_item, gpointer dse_item2, GdkEventMotion const *event, DelayedSnapEvent::DelayedSnapEventOrigin const origin) - : _timer_id(0), _event(NULL), _item(dse_item), _item2(dse_item2), _origin(origin), _event_context(event_context) - { - Inkscape::Preferences *prefs = Inkscape::Preferences::get(); - double value = prefs->getDoubleLimited("/options/snapdelay/value", 0, 0, 1000); - _timer_id = g_timeout_add(value, &sp_event_context_snap_watchdog_callback, this); - _event = gdk_event_copy((GdkEvent*) event); - ((GdkEventMotion *)_event)->time = GDK_CURRENT_TIME; - } - - ~DelayedSnapEvent() { - if (_timer_id > 0) g_source_remove(_timer_id); // Kill the watchdog - if (_event != NULL) gdk_event_free(_event); // Remove the copy of the original event - } - - ToolBase* getEventContext() {return _event_context;} - DelayedSnapEventOrigin getOrigin() {return _origin;} - GdkEvent* getEvent() {return _event;} - gpointer getItem() {return _item;} - gpointer getItem2() {return _item2;} - -private: - guint _timer_id; - GdkEvent* _event; - gpointer _item; - gpointer _item2; - DelayedSnapEventOrigin _origin; - ToolBase* _event_context; -}; - -void sp_event_context_snap_delay_handler(ToolBase *ec, gpointer const dse_item, gpointer const dse_item2, GdkEventMotion *event, DelayedSnapEvent::DelayedSnapEventOrigin origin); - - -/** - * Base class for Event processors. - * - * This is per desktop object, which (its derivatives) implements - * different actions bound to mouse events. - * - * ToolBase is an abstract base class of all tools. As the name - * indicates, event context implementations process UI events (mouse - * movements and keypresses) and take actions (like creating or modifying - * objects). There is one event context implementation for each tool, - * plus few abstract base classes. Writing a new tool involves - * subclassing ToolBase. - */ -class ToolBase { -public: - void enableSelectionCue (bool enable=true); - void enableGrDrag (bool enable=true); - bool deleteSelectedDrag(bool just_one); - - ToolBase(); - virtual ~ToolBase(); - - SPDesktop *desktop; - Inkscape::Preferences::Observer *pref_observer; - gchar const *const *cursor_shape; - gint hot_x, hot_y; ///< indicates the cursor's hot spot - GdkCursor *cursor; - - gint xp, yp; ///< where drag started - gint tolerance; - bool within_tolerance; ///< are we still within tolerance of origin - - SPItem *item_to_select; ///< the item where mouse_press occurred, to - ///< be selected if this is a click not drag - - Inkscape::MessageContext *defaultMessageContext() { - return message_context; - } - - Inkscape::MessageContext *message_context; - - Inkscape::SelCue *_selcue; - - GrDrag *_grdrag; - GrDrag *get_drag () {return _grdrag;} - - ShapeEditor* shape_editor; - - bool space_panning; - - DelayedSnapEvent *_delayed_snap_event; - bool _dse_callback_in_process; - - virtual void setup(); - virtual void finish(); - - // Is called by our pref_observer if a preference has been changed. - virtual void set(const Inkscape::Preferences::Entry& val); - - virtual void activate(); - virtual void deactivate(); - - virtual bool root_handler(GdkEvent* event); - virtual bool item_handler(SPItem* item, GdkEvent* event); - - virtual const std::string& getPrefsPath() = 0; - - /** - * An observer that relays pref changes to the derived classes. - */ - class ToolPrefObserver: public Inkscape::Preferences::Observer { - public: - ToolPrefObserver(Glib::ustring const &path, ToolBase *ec) : - Inkscape::Preferences::Observer(path), ec(ec) { - } - - virtual void notify(Inkscape::Preferences::Entry const &val) { - ec->set(val); - } - - private: - ToolBase * const ec; - }; - -//protected: - void sp_event_context_update_cursor(); - -private: - ToolBase(const ToolBase&); - ToolBase& operator=(const ToolBase&); -}; - -#define SP_EVENT_CONTEXT_DESKTOP(e) (SP_EVENT_CONTEXT(e)->desktop) -#define SP_EVENT_CONTEXT_DOCUMENT(e) ((SP_EVENT_CONTEXT_DESKTOP(e))->doc()) - -#define SP_EVENT_CONTEXT_STATIC 0 - -//ToolBase *sp_event_context_new(GType type, SPDesktop *desktop, gchar const *pref_path, unsigned key); -//void sp_event_context_finish(ToolBase *ec); -void sp_event_context_read(ToolBase *ec, gchar const *key); -void sp_event_context_activate(ToolBase *ec); -//void sp_event_context_deactivate(ToolBase *ec); - -gint sp_event_context_root_handler(ToolBase *ec, GdkEvent *event); -gint sp_event_context_virtual_root_handler(ToolBase *ec, GdkEvent *event); -gint sp_event_context_item_handler(ToolBase *ec, SPItem *item, GdkEvent *event); -gint sp_event_context_virtual_item_handler(ToolBase *ec, SPItem *item, GdkEvent *event); - -void sp_event_root_menu_popup(SPDesktop *desktop, SPItem *item, GdkEvent *event); - -gint gobble_key_events(guint keyval, gint mask); -gint gobble_motion_events(gint mask); - -//void sp_event_context_update_cursor(ToolBase *ec); - -void sp_event_show_modifier_tip(Inkscape::MessageContext *message_context, GdkEvent *event, - gchar const *ctrl_tip, gchar const *shift_tip, gchar const *alt_tip); - -guint get_group0_keyval(GdkEventKey *event); - -SPItem *sp_event_context_find_item (SPDesktop *desktop, Geom::Point const &p, bool select_under, bool into_groups); -SPItem *sp_event_context_over_item (SPDesktop *desktop, SPItem *item, Geom::Point const &p); - -void sp_toggle_dropper(SPDesktop *dt); - -//ShapeEditor *sp_event_context_get_shape_editor (ToolBase *ec); -bool sp_event_context_knot_mouseover(ToolBase *ec); - -//void ec_shape_event_attr_changed(Inkscape::XML::Node *shape_repr, -// gchar const *name, gchar const *old_value, gchar const *new_value, -// bool const is_interactive, gpointer const data); -// -//void event_context_print_event_info(GdkEvent *event, bool print_return = true); - -} -} -} - -#endif // SEEN_SP_EVENT_CONTEXT_H - - -/* - Local Variables: - mode:c++ - c-file-style:"stroustrup" - c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) - indent-tabs-mode:nil - fill-column:99 - End: -*/ -// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/file.cpp b/src/file.cpp index f978ec66e..cec634c9b 100644 --- a/src/file.cpp +++ b/src/file.cpp @@ -36,7 +36,7 @@ #include "dir-util.h" #include "document-private.h" #include "document-undo.h" -#include "event-context.h" +#include "ui/tools/tool-base.h" #include "extension/db.h" #include "extension/input.h" #include "extension/output.h" diff --git a/src/flood-context.cpp b/src/flood-context.cpp deleted file mode 100644 index 1f45f1d63..000000000 --- a/src/flood-context.cpp +++ /dev/null @@ -1,1263 +0,0 @@ -/** - * @file - * Bucket fill drawing context, works by bitmap filling an area on a rendered version - * of the current display and then tracing the result using potrace. - */ -/* Author: - * Lauris Kaplinski - * bulia byak - * John Bintz - * Jon A. Cruz - * Abhishek Sharma - * - * Copyright (C) 2006 Johan Engelen - * Copyright (C) 2000-2005 authors - * Copyright (C) 2000-2001 Ximian, Inc. - * - * Released under GNU GPL, read the file 'COPYING' for more information - */ - -#ifdef HAVE_CONFIG_H -#include "config.h" -#endif - -#include "trace/potrace/inkscape-potrace.h" -#include <2geom/pathvector.h> -#include -#include -#include -#include - -#include "color.h" -#include "context-fns.h" -#include "desktop.h" -#include "desktop-handles.h" -#include "desktop-style.h" -#include "display/cairo-utils.h" -#include "display/drawing-context.h" -#include "display/drawing-image.h" -#include "display/drawing-item.h" -#include "display/drawing.h" -#include "display/sp-canvas.h" -#include "document.h" -#include "document-undo.h" -#include "flood-context.h" -#include "livarot/Path.h" -#include "livarot/Shape.h" -#include "macros.h" -#include "message-context.h" -#include "message-stack.h" -#include "preferences.h" -#include "rubberband.h" -#include "selection.h" -#include "shape-editor.h" -#include "sp-defs.h" -#include "sp-item.h" -#include "splivarot.h" -#include "sp-namedview.h" -#include "sp-object.h" -#include "sp-path.h" -#include "sp-rect.h" -#include "sp-root.h" -#include "svg/svg.h" -#include "trace/imagemap.h" -#include "trace/trace.h" -#include "xml/node-event-vector.h" -#include "xml/repr.h" -#include "verbs.h" - -#include "pixmaps/cursor-paintbucket.xpm" - -using Inkscape::DocumentUndo; - -using Inkscape::Display::ExtractARGB32; -using Inkscape::Display::ExtractRGB32; -using Inkscape::Display::AssembleARGB32; - -#include "tool-factory.h" - -namespace Inkscape { -namespace UI { -namespace Tools { - -namespace { - ToolBase* createPaintbucketContext() { - return new FloodTool(); - } - - bool paintbucketContextRegistered = ToolFactory::instance().registerObject("/tools/paintbucket", createPaintbucketContext); -} - -const std::string& FloodTool::getPrefsPath() { - return FloodTool::prefsPath; -} - -const std::string FloodTool::prefsPath = "/tools/paintbucket"; - -FloodTool::FloodTool() : ToolBase() { - this->cursor_shape = cursor_paintbucket_xpm; - this->hot_x = 11; - this->hot_y = 30; - this->xp = 0; - this->yp = 0; - this->tolerance = 4; - this->within_tolerance = false; - this->item_to_select = NULL; - - this->item = NULL; -} - -FloodTool::~FloodTool() { - this->sel_changed_connection.disconnect(); - - delete this->shape_editor; - this->shape_editor = NULL; - - /* fixme: This is necessary because we do not grab */ - if (this->item) { - this->finishItem(); - } -} - -/** - * Callback that processes the "changed" signal on the selection; - * destroys old and creates new knotholder. - */ -void FloodTool::selection_changed(Inkscape::Selection* selection) { - this->shape_editor->unset_item(SH_KNOTHOLDER); - this->shape_editor->set_item(selection->singleItem(), SH_KNOTHOLDER); -} - -void FloodTool::setup() { - ToolBase::setup(); - - this->shape_editor = new ShapeEditor(this->desktop); - - SPItem *item = sp_desktop_selection(this->desktop)->singleItem(); - if (item) { - this->shape_editor->set_item(item, SH_KNOTHOLDER); - } - - this->sel_changed_connection.disconnect(); - this->sel_changed_connection = sp_desktop_selection(this->desktop)->connectChanged( - sigc::mem_fun(this, &FloodTool::selection_changed) - ); - - Inkscape::Preferences *prefs = Inkscape::Preferences::get(); - - if (prefs->getBool("/tools/paintbucket/selcue")) { - this->enableSelectionCue(); - } -} - - -// Changes from 0.48 -> 0.49 (Cairo) -// 0.49: Ignores alpha in background -// 0.48: RGBA, 0.49 ARGB -// 0.49: premultiplied alpha -inline static guint32 compose_onto(guint32 px, guint32 bg) -{ - guint ap = 0, rp = 0, gp = 0, bp = 0; - guint rb = 0, gb = 0, bb = 0; - ExtractARGB32(px, ap, rp, gp, bp); - ExtractRGB32(bg, rb, gb, bb); - - // guint ao = 255*255 - (255-ap)*(255-bp); ao = (ao + 127) / 255; - // guint ao = (255-ap)*ab + 255*ap; ao = (ao + 127) / 255; - guint ao = 255; // Cairo version doesn't allow background to have alpha != 1. - guint ro = (255-ap)*rb + 255*rp; ro = (ro + 127) / 255; - guint go = (255-ap)*gb + 255*gp; go = (go + 127) / 255; - guint bo = (255-ap)*bb + 255*bp; bo = (bo + 127) / 255; - - guint pxout = AssembleARGB32(ao, ro, go, bo); - return pxout; -} - -/** - * Get the pointer to a pixel in a pixel buffer. - * @param px The pixel buffer. - * @param x The X coordinate. - * @param y The Y coordinate. - * @param stride The rowstride of the pixel buffer. - */ -inline guint32 get_pixel(guchar *px, int x, int y, int stride) { - return *reinterpret_cast(px + y * stride + x * 4); -} - -inline unsigned char * get_trace_pixel(guchar *trace_px, int x, int y, int width) { - return trace_px + (x + y * width); -} - -/** - * Generate the list of trace channel selection entries. - */ -GList * flood_channels_dropdown_items_list() { - GList *glist = NULL; - - glist = g_list_append (glist, _("Visible Colors")); - glist = g_list_append (glist, _("Red")); - glist = g_list_append (glist, _("Green")); - glist = g_list_append (glist, _("Blue")); - glist = g_list_append (glist, _("Hue")); - glist = g_list_append (glist, _("Saturation")); - glist = g_list_append (glist, _("Lightness")); - glist = g_list_append (glist, _("Alpha")); - - return glist; -} - -/** - * Generate the list of autogap selection entries. - */ -GList * flood_autogap_dropdown_items_list() { - GList *glist = NULL; - - glist = g_list_append (glist, (void*) C_("Flood autogap", "None")); - glist = g_list_append (glist, (void*) C_("Flood autogap", "Small")); - glist = g_list_append (glist, (void*) C_("Flood autogap", "Medium")); - glist = g_list_append (glist, (void*) C_("Flood autogap", "Large")); - - return glist; -} - -/** - * Compare a pixel in a pixel buffer with another pixel to determine if a point should be included in the fill operation. - * @param check The pixel in the pixel buffer to check. - * @param orig The original selected pixel to use as the fill target color. - * @param merged_orig_pixel The original pixel merged with the background. - * @param dtc The desktop background color. - * @param threshold The fill threshold. - * @param method The fill method to use as defined in PaintBucketChannels. - */ -static bool compare_pixels(guint32 check, guint32 orig, guint32 merged_orig_pixel, guint32 dtc, int threshold, PaintBucketChannels method) -{ - int diff = 0; - float hsl_check[3] = {0,0,0}, hsl_orig[3] = {0,0,0}; - - guint32 ac = 0, rc = 0, gc = 0, bc = 0; - ExtractARGB32(check, ac, rc, gc, bc); - - guint32 ao = 0, ro = 0, go = 0, bo = 0; - ExtractARGB32(orig, ao, ro, go, bo); - - guint32 ad = 0, rd = 0, gd = 0, bd = 0; - ExtractARGB32(dtc, ad, rd, gd, bd); - - guint32 amop = 0, rmop = 0, gmop = 0, bmop = 0; - ExtractARGB32(merged_orig_pixel, amop, rmop, gmop, bmop); - - if ((method == FLOOD_CHANNELS_H) || - (method == FLOOD_CHANNELS_S) || - (method == FLOOD_CHANNELS_L)) { - double dac = ac; - double dao = ao; - sp_color_rgb_to_hsl_floatv(hsl_check, rc / dac, gc / dac, bc / dac); - sp_color_rgb_to_hsl_floatv(hsl_orig, ro / dao, go / dao, bo / dao); - } - - switch (method) { - case FLOOD_CHANNELS_ALPHA: - return abs(static_cast(ac) - ao) <= threshold; - case FLOOD_CHANNELS_R: - return abs(static_cast(ac ? unpremul_alpha(rc, ac) : 0) - (ao ? unpremul_alpha(ro, ao) : 0)) <= threshold; - case FLOOD_CHANNELS_G: - return abs(static_cast(ac ? unpremul_alpha(gc, ac) : 0) - (ao ? unpremul_alpha(go, ao) : 0)) <= threshold; - case FLOOD_CHANNELS_B: - return abs(static_cast(ac ? unpremul_alpha(bc, ac) : 0) - (ao ? unpremul_alpha(bo, ao) : 0)) <= threshold; - case FLOOD_CHANNELS_RGB: - guint32 amc, rmc, bmc, gmc; - //amc = 255*255 - (255-ac)*(255-ad); amc = (amc + 127) / 255; - //amc = (255-ac)*ad + 255*ac; amc = (amc + 127) / 255; - amc = 255; // Why are we looking at desktop? Cairo version ignores destop alpha - rmc = (255-ac)*rd + 255*rc; rmc = (rmc + 127) / 255; - gmc = (255-ac)*gd + 255*gc; gmc = (gmc + 127) / 255; - bmc = (255-ac)*bd + 255*bc; bmc = (bmc + 127) / 255; - - diff += abs(static_cast(amc ? unpremul_alpha(rmc, amc) : 0) - (amop ? unpremul_alpha(rmop, amop) : 0)); - diff += abs(static_cast(amc ? unpremul_alpha(gmc, amc) : 0) - (amop ? unpremul_alpha(gmop, amop) : 0)); - diff += abs(static_cast(amc ? unpremul_alpha(bmc, amc) : 0) - (amop ? unpremul_alpha(bmop, amop) : 0)); - return ((diff / 3) <= ((threshold * 3) / 4)); - - case FLOOD_CHANNELS_H: - return ((int)(fabs(hsl_check[0] - hsl_orig[0]) * 100.0) <= threshold); - case FLOOD_CHANNELS_S: - return ((int)(fabs(hsl_check[1] - hsl_orig[1]) * 100.0) <= threshold); - case FLOOD_CHANNELS_L: - return ((int)(fabs(hsl_check[2] - hsl_orig[2]) * 100.0) <= threshold); - } - - return false; -} - -enum { - PIXEL_CHECKED = 1, - PIXEL_QUEUED = 2, - PIXEL_PAINTABLE = 4, - PIXEL_NOT_PAINTABLE = 8, - PIXEL_COLORED = 16 -}; - -static inline bool is_pixel_checked(unsigned char *t) { return (*t & PIXEL_CHECKED) == PIXEL_CHECKED; } -static inline bool is_pixel_queued(unsigned char *t) { return (*t & PIXEL_QUEUED) == PIXEL_QUEUED; } -static inline bool is_pixel_paintability_checked(unsigned char *t) { - return !((*t & PIXEL_PAINTABLE) == 0) && ((*t & PIXEL_NOT_PAINTABLE) == 0); -} -static inline bool is_pixel_paintable(unsigned char *t) { return (*t & PIXEL_PAINTABLE) == PIXEL_PAINTABLE; } -static inline bool is_pixel_colored(unsigned char *t) { return (*t & PIXEL_COLORED) == PIXEL_COLORED; } - -static inline void mark_pixel_checked(unsigned char *t) { *t |= PIXEL_CHECKED; } -static inline void mark_pixel_unchecked(unsigned char *t) { *t ^= PIXEL_CHECKED; } -static inline void mark_pixel_queued(unsigned char *t) { *t |= PIXEL_QUEUED; } -static inline void mark_pixel_paintable(unsigned char *t) { *t |= PIXEL_PAINTABLE; *t ^= PIXEL_NOT_PAINTABLE; } -static inline void mark_pixel_not_paintable(unsigned char *t) { *t |= PIXEL_NOT_PAINTABLE; *t ^= PIXEL_PAINTABLE; } -static inline void mark_pixel_colored(unsigned char *t) { *t |= PIXEL_COLORED; } - -static inline void clear_pixel_paintability(unsigned char *t) { *t ^= PIXEL_PAINTABLE; *t ^= PIXEL_NOT_PAINTABLE; } - -struct bitmap_coords_info { - bool is_left; - unsigned int x; - unsigned int y; - int y_limit; - unsigned int width; - unsigned int height; - unsigned int stride; - unsigned int threshold; - unsigned int radius; - PaintBucketChannels method; - guint32 dtc; - guint32 merged_orig_pixel; - Geom::Rect bbox; - Geom::Rect screen; - unsigned int max_queue_size; - unsigned int current_step; -}; - -/** - * Check if a pixel can be included in the fill. - * @param px The rendered pixel buffer to check. - * @param trace_t The pixel in the trace pixel buffer to check or mark. - * @param x The X coordinate. - * @param y The y coordinate. - * @param orig_color The original selected pixel to use as the fill target color. - * @param bci The bitmap_coords_info structure. - */ -inline static bool check_if_pixel_is_paintable(guchar *px, unsigned char *trace_t, int x, int y, guint32 orig_color, bitmap_coords_info bci) { - if (is_pixel_paintability_checked(trace_t)) { - return is_pixel_paintable(trace_t); - } else { - guint32 pixel = get_pixel(px, x, y, bci.stride); - if (compare_pixels(pixel, orig_color, bci.merged_orig_pixel, bci.dtc, bci.threshold, bci.method)) { - mark_pixel_paintable(trace_t); - return true; - } else { - mark_pixel_not_paintable(trace_t); - return false; - } - } -} - -/** - * Perform the bitmap-to-vector tracing and place the traced path onto the document. - * @param px The trace pixel buffer to trace to SVG. - * @param desktop The desktop on which to place the final SVG path. - * @param transform The transform to apply to the final SVG path. - * @param union_with_selection If true, merge the final SVG path with the current selection. - */ -static void do_trace(bitmap_coords_info bci, guchar *trace_px, SPDesktop *desktop, Geom::Affine transform, unsigned int min_x, unsigned int max_x, unsigned int min_y, unsigned int max_y, bool union_with_selection) { - SPDocument *document = sp_desktop_document(desktop); - - unsigned char *trace_t; - - GrayMap *gray_map = GrayMapCreate((max_x - min_x + 1), (max_y - min_y + 1)); - unsigned int gray_map_y = 0; - for (unsigned int y = min_y; y <= max_y; y++) { - unsigned long *gray_map_t = gray_map->rows[gray_map_y]; - - trace_t = get_trace_pixel(trace_px, min_x, y, bci.width); - for (unsigned int x = min_x; x <= max_x; x++) { - *gray_map_t = is_pixel_colored(trace_t) ? GRAYMAP_BLACK : GRAYMAP_WHITE; - gray_map_t++; - trace_t++; - } - gray_map_y++; - } - - Inkscape::Trace::Potrace::PotraceTracingEngine pte; - pte.keepGoing = 1; - std::vector results = pte.traceGrayMap(gray_map); - gray_map->destroy(gray_map); - - //XML Tree being used here directly while it shouldn't be...." - Inkscape::XML::Document *xml_doc = desktop->doc()->getReprDoc(); - - long totalNodeCount = 0L; - - Inkscape::Preferences *prefs = Inkscape::Preferences::get(); - double offset = prefs->getDouble("/tools/paintbucket/offset", 0.0); - - for (unsigned int i=0 ; icreateElement("svg:path"); - /* Set style */ - sp_desktop_apply_style_tool (desktop, pathRepr, "/tools/paintbucket", false); - - Geom::PathVector pathv = sp_svg_read_pathv(result.getPathData().c_str()); - Path *path = new Path; - path->LoadPathVector(pathv); - - if (offset != 0) { - - Shape *path_shape = new Shape(); - - path->ConvertWithBackData(0.03); - path->Fill(path_shape, 0); - delete path; - - Shape *expanded_path_shape = new Shape(); - - expanded_path_shape->ConvertToShape(path_shape, fill_nonZero); - path_shape->MakeOffset(expanded_path_shape, offset * desktop->current_zoom(), join_round, 4); - expanded_path_shape->ConvertToShape(path_shape, fill_positive); - - Path *expanded_path = new Path(); - - expanded_path->Reset(); - expanded_path_shape->ConvertToForme(expanded_path); - expanded_path->ConvertEvenLines(1.0); - expanded_path->Simplify(1.0); - - delete path_shape; - delete expanded_path_shape; - - gchar *str = expanded_path->svg_dump_path(); - if (str && *str) { - pathRepr->setAttribute("d", str); - g_free(str); - } else { - desktop->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Too much inset, the result is empty.")); - Inkscape::GC::release(pathRepr); - g_free(str); - return; - } - - delete expanded_path; - - } else { - gchar *str = path->svg_dump_path(); - delete path; - pathRepr->setAttribute("d", str); - g_free(str); - } - - desktop->currentLayer()->addChild(pathRepr,NULL); - - SPObject *reprobj = document->getObjectByRepr(pathRepr); - if (reprobj) { - SP_ITEM(reprobj)->doWriteTransform(pathRepr, transform, NULL); - - // premultiply the item transform by the accumulated parent transform in the paste layer - Geom::Affine local (SP_GROUP(desktop->currentLayer())->i2doc_affine()); - if (!local.isIdentity()) { - gchar const *t_str = pathRepr->attribute("transform"); - Geom::Affine item_t (Geom::identity()); - if (t_str) - sp_svg_transform_read(t_str, &item_t); - item_t *= local.inverse(); - // (we're dealing with unattached repr, so we write to its attr instead of using sp_item_set_transform) - gchar *affinestr=sp_svg_transform_write(item_t); - pathRepr->setAttribute("transform", affinestr); - g_free(affinestr); - } - - Inkscape::Selection *selection = sp_desktop_selection(desktop); - - pathRepr->setPosition(-1); - - if (union_with_selection) { - desktop->messageStack()->flashF( Inkscape::WARNING_MESSAGE, - ngettext("Area filled, path with %d node created and unioned with selection.","Area filled, path with %d nodes created and unioned with selection.", - SP_PATH(reprobj)->nodesInPath()), SP_PATH(reprobj)->nodesInPath() ); - selection->add(reprobj); - sp_selected_path_union_skip_undo(sp_desktop_selection(desktop), desktop); - } else { - desktop->messageStack()->flashF( Inkscape::WARNING_MESSAGE, - ngettext("Area filled, path with %d node created.","Area filled, path with %d nodes created.", - SP_PATH(reprobj)->nodesInPath()), SP_PATH(reprobj)->nodesInPath() ); - selection->set(reprobj); - } - - } - - Inkscape::GC::release(pathRepr); - - } -} - -/** - * The possible return states of perform_bitmap_scanline_check(). - */ -enum ScanlineCheckResult { - SCANLINE_CHECK_OK, - SCANLINE_CHECK_ABORTED, - SCANLINE_CHECK_BOUNDARY -}; - -/** - * Determine if the provided coordinates are within the pixel buffer limits. - * @param x The X coordinate. - * @param y The Y coordinate. - * @param bci The bitmap_coords_info structure. - */ -inline static bool coords_in_range(unsigned int x, unsigned int y, bitmap_coords_info bci) { - return (x < bci.width) && - (y < bci.height); -} - -#define PAINT_DIRECTION_LEFT 1 -#define PAINT_DIRECTION_RIGHT 2 -#define PAINT_DIRECTION_UP 4 -#define PAINT_DIRECTION_DOWN 8 -#define PAINT_DIRECTION_ALL 15 - -/** - * Paint a pixel or a square (if autogap is enabled) on the trace pixel buffer. - * @param px The rendered pixel buffer to check. - * @param trace_px The trace pixel buffer. - * @param orig_color The original selected pixel to use as the fill target color. - * @param bci The bitmap_coords_info structure. - * @param original_point_trace_t The original pixel in the trace pixel buffer to check. - */ -inline static unsigned int paint_pixel(guchar *px, guchar *trace_px, guint32 orig_color, bitmap_coords_info bci, unsigned char *original_point_trace_t) { - if (bci.radius == 0) { - mark_pixel_colored(original_point_trace_t); - return PAINT_DIRECTION_ALL; - } else { - unsigned char *trace_t; - - bool can_paint_up = true; - bool can_paint_down = true; - bool can_paint_left = true; - bool can_paint_right = true; - - for (unsigned int ty = bci.y - bci.radius; ty <= bci.y + bci.radius; ty++) { - for (unsigned int tx = bci.x - bci.radius; tx <= bci.x + bci.radius; tx++) { - if (coords_in_range(tx, ty, bci)) { - trace_t = get_trace_pixel(trace_px, tx, ty, bci.width); - if (!is_pixel_colored(trace_t)) { - if (check_if_pixel_is_paintable(px, trace_t, tx, ty, orig_color, bci)) { - mark_pixel_colored(trace_t); - } else { - if (tx < bci.x) { can_paint_left = false; } - if (tx > bci.x) { can_paint_right = false; } - if (ty < bci.y) { can_paint_up = false; } - if (ty > bci.y) { can_paint_down = false; } - } - } - } - } - } - - unsigned int paint_directions = 0; - if (can_paint_left) { paint_directions += PAINT_DIRECTION_LEFT; } - if (can_paint_right) { paint_directions += PAINT_DIRECTION_RIGHT; } - if (can_paint_up) { paint_directions += PAINT_DIRECTION_UP; } - if (can_paint_down) { paint_directions += PAINT_DIRECTION_DOWN; } - - return paint_directions; - } -} - -/** - * Push a point to be checked onto the bottom of the rendered pixel buffer check queue. - * @param fill_queue The fill queue to add the point to. - * @param max_queue_size The maximum size of the fill queue. - * @param trace_t The trace pixel buffer pixel. - * @param x The X coordinate. - * @param y The Y coordinate. - */ -static void push_point_onto_queue(std::deque *fill_queue, unsigned int max_queue_size, unsigned char *trace_t, unsigned int x, unsigned int y) { - if (!is_pixel_queued(trace_t)) { - if ((fill_queue->size() < max_queue_size)) { - fill_queue->push_back(Geom::Point(x, y)); - mark_pixel_queued(trace_t); - } - } -} - -/** - * Shift a point to be checked onto the top of the rendered pixel buffer check queue. - * @param fill_queue The fill queue to add the point to. - * @param max_queue_size The maximum size of the fill queue. - * @param trace_t The trace pixel buffer pixel. - * @param x The X coordinate. - * @param y The Y coordinate. - */ -static void shift_point_onto_queue(std::deque *fill_queue, unsigned int max_queue_size, unsigned char *trace_t, unsigned int x, unsigned int y) { - if (!is_pixel_queued(trace_t)) { - if ((fill_queue->size() < max_queue_size)) { - fill_queue->push_front(Geom::Point(x, y)); - mark_pixel_queued(trace_t); - } - } -} - -/** - * Scan a row in the rendered pixel buffer and add points to the fill queue as necessary. - * @param fill_queue The fill queue to add the point to. - * @param px The rendered pixel buffer. - * @param trace_px The trace pixel buffer. - * @param orig_color The original selected pixel to use as the fill target color. - * @param bci The bitmap_coords_info structure. - */ -static ScanlineCheckResult perform_bitmap_scanline_check(std::deque *fill_queue, guchar *px, guchar *trace_px, guint32 orig_color, bitmap_coords_info bci, unsigned int *min_x, unsigned int *max_x) { - bool aborted = false; - bool reached_screen_boundary = false; - bool ok; - - bool keep_tracing; - bool initial_paint = true; - - unsigned char *current_trace_t = get_trace_pixel(trace_px, bci.x, bci.y, bci.width); - unsigned int paint_directions; - - bool currently_painting_top = false; - bool currently_painting_bottom = false; - - unsigned int top_ty = bci.y - 1; - unsigned int bottom_ty = bci.y + 1; - - bool can_paint_top = (top_ty > 0); - bool can_paint_bottom = (bottom_ty < bci.height); - - Geom::Point t = fill_queue->front(); - - do { - ok = false; - if (bci.is_left) { - keep_tracing = (bci.x != 0); - } else { - keep_tracing = (bci.x < bci.width); - } - - *min_x = MIN(*min_x, bci.x); - *max_x = MAX(*max_x, bci.x); - - if (keep_tracing) { - if (check_if_pixel_is_paintable(px, current_trace_t, bci.x, bci.y, orig_color, bci)) { - paint_directions = paint_pixel(px, trace_px, orig_color, bci, current_trace_t); - if (bci.radius == 0) { - mark_pixel_checked(current_trace_t); - if ((t[Geom::X] == bci.x) && (t[Geom::Y] == bci.y)) { - fill_queue->pop_front(); t = fill_queue->front(); - } - } - - if (can_paint_top) { - if (paint_directions & PAINT_DIRECTION_UP) { - unsigned char *trace_t = current_trace_t - bci.width; - if (!is_pixel_queued(trace_t)) { - bool ok_to_paint = check_if_pixel_is_paintable(px, trace_t, bci.x, top_ty, orig_color, bci); - - if (initial_paint) { currently_painting_top = !ok_to_paint; } - - if (ok_to_paint && (!currently_painting_top)) { - currently_painting_top = true; - push_point_onto_queue(fill_queue, bci.max_queue_size, trace_t, bci.x, top_ty); - } - if ((!ok_to_paint) && currently_painting_top) { - currently_painting_top = false; - } - } - } - } - - if (can_paint_bottom) { - if (paint_directions & PAINT_DIRECTION_DOWN) { - unsigned char *trace_t = current_trace_t + bci.width; - if (!is_pixel_queued(trace_t)) { - bool ok_to_paint = check_if_pixel_is_paintable(px, trace_t, bci.x, bottom_ty, orig_color, bci); - - if (initial_paint) { currently_painting_bottom = !ok_to_paint; } - - if (ok_to_paint && (!currently_painting_bottom)) { - currently_painting_bottom = true; - push_point_onto_queue(fill_queue, bci.max_queue_size, trace_t, bci.x, bottom_ty); - } - if ((!ok_to_paint) && currently_painting_bottom) { - currently_painting_bottom = false; - } - } - } - } - - if (bci.is_left) { - if (paint_directions & PAINT_DIRECTION_LEFT) { - bci.x--; current_trace_t--; - ok = true; - } - } else { - if (paint_directions & PAINT_DIRECTION_RIGHT) { - bci.x++; current_trace_t++; - ok = true; - } - } - - initial_paint = false; - } - } else { - if (bci.bbox.min()[Geom::X] > bci.screen.min()[Geom::X]) { - aborted = true; break; - } else { - reached_screen_boundary = true; - } - } - } while (ok); - - if (aborted) { return SCANLINE_CHECK_ABORTED; } - if (reached_screen_boundary) { return SCANLINE_CHECK_BOUNDARY; } - return SCANLINE_CHECK_OK; -} - -/** - * Sort the rendered pixel buffer check queue vertically. - */ -static bool sort_fill_queue_vertical(Geom::Point a, Geom::Point b) { - return a[Geom::Y] > b[Geom::Y]; -} - -/** - * Sort the rendered pixel buffer check queue horizontally. - */ -static bool sort_fill_queue_horizontal(Geom::Point a, Geom::Point b) { - return a[Geom::X] > b[Geom::X]; -} - -/** - * Perform a flood fill operation. - * @param event_context The event context for this tool. - * @param event The details of this event. - * @param union_with_selection If true, union the new fill with the current selection. - * @param is_point_fill If false, use the Rubberband "touch selection" to get the initial points for the fill. - * @param is_touch_fill If true, use only the initial contact point in the Rubberband "touch selection" as the fill target color. - */ -static void sp_flood_do_flood_fill(ToolBase *event_context, GdkEvent *event, bool union_with_selection, bool is_point_fill, bool is_touch_fill) { - SPDesktop *desktop = event_context->desktop; - SPDocument *document = sp_desktop_document(desktop); - - document->ensureUpToDate(); - - Geom::OptRect bbox = document->getRoot()->visualBounds(); - - if (!bbox) { - desktop->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Area is not bounded, cannot fill.")); - return; - } - - double zoom_scale = desktop->current_zoom(); - - // Render 160% of the physical display to the render pixel buffer, so that available - // fill areas off the screen can be included in the fill. - double padding = 1.6; - - Geom::Rect screen = desktop->get_display_area(); - - unsigned int width = (int)ceil(screen.width() * zoom_scale * padding); - unsigned int height = (int)ceil(screen.height() * zoom_scale * padding); - - Geom::Point origin(screen.min()[Geom::X], - document->getHeight().value("px") - screen.height() - screen.min()[Geom::Y]); - - origin[Geom::X] += (screen.width() * ((1 - padding) / 2)); - origin[Geom::Y] += (screen.height() * ((1 - padding) / 2)); - - Geom::Scale scale(zoom_scale, zoom_scale); - Geom::Affine affine = scale * Geom::Translate(-origin * scale); - - int stride = cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, width); - guchar *px = g_new(guchar, stride * height); - guint32 bgcolor, dtc; - - // Draw image into data block px - { // this block limits the lifetime of Drawing and DrawingContext - /* Create DrawingItems and set transform */ - unsigned dkey = SPItem::display_key_new(1); - Inkscape::Drawing drawing; - Inkscape::DrawingItem *root = document->getRoot()->invoke_show( drawing, dkey, SP_ITEM_SHOW_DISPLAY); - root->setTransform(affine); - drawing.setRoot(root); - - Geom::IntRect final_bbox = Geom::IntRect::from_xywh(0, 0, width, height); - drawing.update(final_bbox); - - cairo_surface_t *s = cairo_image_surface_create_for_data( - px, CAIRO_FORMAT_ARGB32, width, height, stride); - Inkscape::DrawingContext ct(s, Geom::Point(0,0)); - // cairo_translate not necessary here - surface origin is at 0,0 - - SPNamedView *nv = sp_desktop_namedview(desktop); - bgcolor = nv->pagecolor; - // bgcolor is 0xrrggbbaa, we need 0xaarrggbb - dtc = (bgcolor >> 8) | (bgcolor << 24); - - ct.setSource(bgcolor); - ct.setOperator(CAIRO_OPERATOR_SOURCE); - ct.paint(); - ct.setOperator(CAIRO_OPERATOR_OVER); - - drawing.render(ct, final_bbox); - - //cairo_surface_write_to_png( s, "cairo.png" ); - - cairo_surface_flush(s); - cairo_surface_destroy(s); - - // Hide items - document->getRoot()->invoke_hide(dkey); - } - - // { - // // Dump data to png - // cairo_surface_t *s = cairo_image_surface_create_for_data( - // px, CAIRO_FORMAT_ARGB32, width, height, stride); - // cairo_surface_write_to_png( s, "cairo2.png" ); - // std::cout << " Wrote cairo2.png" << std::endl; - // } - - guchar *trace_px = g_new(guchar, width * height); - memset(trace_px, 0x00, width * height); - - std::deque fill_queue; - std::queue color_queue; - - std::vector fill_points; - - bool aborted = false; - int y_limit = height - 1; - - Inkscape::Preferences *prefs = Inkscape::Preferences::get(); - PaintBucketChannels method = (PaintBucketChannels) prefs->getInt("/tools/paintbucket/channels", 0); - int threshold = prefs->getIntLimited("/tools/paintbucket/threshold", 1, 0, 100); - - switch(method) { - case FLOOD_CHANNELS_ALPHA: - case FLOOD_CHANNELS_RGB: - case FLOOD_CHANNELS_R: - case FLOOD_CHANNELS_G: - case FLOOD_CHANNELS_B: - threshold = (255 * threshold) / 100; - break; - case FLOOD_CHANNELS_H: - case FLOOD_CHANNELS_S: - case FLOOD_CHANNELS_L: - break; - } - - bitmap_coords_info bci; - - bci.y_limit = y_limit; - bci.width = width; - bci.height = height; - bci.stride = stride; - bci.threshold = threshold; - bci.method = method; - bci.bbox = *bbox; - bci.screen = screen; - bci.dtc = dtc; - bci.radius = prefs->getIntLimited("/tools/paintbucket/autogap", 0, 0, 3); - bci.max_queue_size = (width * height) / 4; - bci.current_step = 0; - - if (is_point_fill) { - fill_points.push_back(Geom::Point(event->button.x, event->button.y)); - } else { - Inkscape::Rubberband *r = Inkscape::Rubberband::get(desktop); - fill_points = r->getPoints(); - } - - for (unsigned int i = 0; i < fill_points.size(); i++) { - Geom::Point pw = Geom::Point(fill_points[i][Geom::X] / zoom_scale, document->getHeight().value("px") + (fill_points[i][Geom::Y] / zoom_scale)) * affine; - - pw[Geom::X] = (int)MIN(width - 1, MAX(0, pw[Geom::X])); - pw[Geom::Y] = (int)MIN(height - 1, MAX(0, pw[Geom::Y])); - - if (is_touch_fill) { - if (i == 0) { - color_queue.push(pw); - } else { - unsigned char *trace_t = get_trace_pixel(trace_px, (int)pw[Geom::X], (int)pw[Geom::Y], width); - push_point_onto_queue(&fill_queue, bci.max_queue_size, trace_t, (int)pw[Geom::X], (int)pw[Geom::Y]); - } - } else { - color_queue.push(pw); - } - } - - bool reached_screen_boundary = false; - - bool first_run = true; - - unsigned long sort_size_threshold = 5; - - unsigned int min_y = height; - unsigned int max_y = 0; - unsigned int min_x = width; - unsigned int max_x = 0; - - while (!color_queue.empty() && !aborted) { - Geom::Point color_point = color_queue.front(); - color_queue.pop(); - - int cx = (int)color_point[Geom::X]; - int cy = (int)color_point[Geom::Y]; - - guint32 orig_color = get_pixel(px, cx, cy, stride); - bci.merged_orig_pixel = compose_onto(orig_color, dtc); - - unsigned char *trace_t = get_trace_pixel(trace_px, cx, cy, width); - if (!is_pixel_checked(trace_t) && !is_pixel_colored(trace_t)) { - if (check_if_pixel_is_paintable(px, trace_px, cx, cy, orig_color, bci)) { - shift_point_onto_queue(&fill_queue, bci.max_queue_size, trace_t, cx, cy); - - if (!first_run) { - for (unsigned int y = 0; y < height; y++) { - trace_t = get_trace_pixel(trace_px, 0, y, width); - for (unsigned int x = 0; x < width; x++) { - clear_pixel_paintability(trace_t); - trace_t++; - } - } - } - first_run = false; - } - } - - unsigned long old_fill_queue_size = fill_queue.size(); - - while (!fill_queue.empty() && !aborted) { - Geom::Point cp = fill_queue.front(); - - if (bci.radius == 0) { - unsigned long new_fill_queue_size = fill_queue.size(); - - /* - * To reduce the number of points in the fill queue, periodically - * resort all of the points in the queue so that scanline checks - * can complete more quickly. A point cannot be checked twice - * in a normal scanline checks, so forcing scanline checks to start - * from one corner of the rendered area as often as possible - * will reduce the number of points that need to be checked and queued. - */ - if (new_fill_queue_size > sort_size_threshold) { - if (new_fill_queue_size > old_fill_queue_size) { - std::sort(fill_queue.begin(), fill_queue.end(), sort_fill_queue_vertical); - - std::deque::iterator start_sort = fill_queue.begin(); - std::deque::iterator end_sort = fill_queue.begin(); - unsigned int sort_y = (unsigned int)cp[Geom::Y]; - unsigned int current_y = sort_y; - - for (std::deque::iterator i = fill_queue.begin(); i != fill_queue.end(); ++i) { - Geom::Point current = *i; - current_y = (unsigned int)current[Geom::Y]; - if (current_y != sort_y) { - if (start_sort != end_sort) { - std::sort(start_sort, end_sort, sort_fill_queue_horizontal); - } - sort_y = current_y; - start_sort = i; - } - end_sort = i; - } - if (start_sort != end_sort) { - std::sort(start_sort, end_sort, sort_fill_queue_horizontal); - } - - cp = fill_queue.front(); - } - } - - old_fill_queue_size = new_fill_queue_size; - } - - fill_queue.pop_front(); - - int x = (int)cp[Geom::X]; - int y = (int)cp[Geom::Y]; - - min_y = MIN((unsigned int)y, min_y); - max_y = MAX((unsigned int)y, max_y); - - unsigned char *trace_t = get_trace_pixel(trace_px, x, y, width); - if (!is_pixel_checked(trace_t)) { - mark_pixel_checked(trace_t); - - if (y == 0) { - if (bbox->min()[Geom::Y] > screen.min()[Geom::Y]) { - aborted = true; break; - } else { - reached_screen_boundary = true; - } - } - - if (y == y_limit) { - if (bbox->max()[Geom::Y] < screen.max()[Geom::Y]) { - aborted = true; break; - } else { - reached_screen_boundary = true; - } - } - - bci.is_left = true; - bci.x = x; - bci.y = y; - - ScanlineCheckResult result = perform_bitmap_scanline_check(&fill_queue, px, trace_px, orig_color, bci, &min_x, &max_x); - - switch (result) { - case SCANLINE_CHECK_ABORTED: - aborted = true; - break; - case SCANLINE_CHECK_BOUNDARY: - reached_screen_boundary = true; - break; - default: - break; - } - - if (bci.x < width) { - trace_t++; - if (!is_pixel_checked(trace_t) && !is_pixel_queued(trace_t)) { - mark_pixel_checked(trace_t); - bci.is_left = false; - bci.x = x + 1; - - result = perform_bitmap_scanline_check(&fill_queue, px, trace_px, orig_color, bci, &min_x, &max_x); - - switch (result) { - case SCANLINE_CHECK_ABORTED: - aborted = true; - break; - case SCANLINE_CHECK_BOUNDARY: - reached_screen_boundary = true; - break; - default: - break; - } - } - } - } - - bci.current_step++; - - if (bci.current_step > bci.max_queue_size) { - aborted = true; - } - } - } - - g_free(px); - - if (aborted) { - g_free(trace_px); - desktop->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Area is not bounded, cannot fill.")); - return; - } - - if (reached_screen_boundary) { - desktop->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Only the visible part of the bounded area was filled. If you want to fill all of the area, undo, zoom out, and fill again.")); - } - - unsigned int trace_padding = bci.radius + 1; - if (min_y > trace_padding) { min_y -= trace_padding; } - if (max_y < (y_limit - trace_padding)) { max_y += trace_padding; } - if (min_x > trace_padding) { min_x -= trace_padding; } - if (max_x < (width - 1 - trace_padding)) { max_x += trace_padding; } - - Geom::Point min_start = Geom::Point(min_x, min_y); - - affine = scale * Geom::Translate(-origin * scale - min_start); - Geom::Affine inverted_affine = Geom::Affine(affine).inverse(); - - do_trace(bci, trace_px, desktop, inverted_affine, min_x, max_x, min_y, max_y, union_with_selection); - - g_free(trace_px); - - DocumentUndo::done(document, SP_VERB_CONTEXT_PAINTBUCKET, _("Fill bounded area")); -} - -bool FloodTool::item_handler(SPItem* item, GdkEvent* event) { - gint ret = FALSE; - - switch (event->type) { - case GDK_BUTTON_PRESS: - if ((event->button.state & GDK_CONTROL_MASK) && event->button.button == 1 && !this->space_panning) { - Geom::Point const button_w(event->button.x, event->button.y); - - SPItem *item = sp_event_context_find_item (desktop, button_w, TRUE, TRUE); - - // Set style - desktop->applyCurrentOrToolStyle(item, "/tools/paintbucket", false); - - DocumentUndo::done(sp_desktop_document(desktop), SP_VERB_CONTEXT_PAINTBUCKET, _("Set style on object")); - - ret = TRUE; - } - break; - - default: - break; - } - -// if (((ToolBaseClass *) sp_flood_context_parent_class)->item_handler) { -// ret = ((ToolBaseClass *) sp_flood_context_parent_class)->item_handler(event_context, item, event); -// } - // CPPIFY: ret is overwritten... - ret = ToolBase::item_handler(item, event); - - return ret; -} - -bool FloodTool::root_handler(GdkEvent* event) { - static bool dragging; - - gint ret = FALSE; - - switch (event->type) { - case GDK_BUTTON_PRESS: - if (event->button.button == 1 && !this->space_panning) { - if (!(event->button.state & GDK_CONTROL_MASK)) { - Geom::Point const button_w(event->button.x, event->button.y); - - if (Inkscape::have_viable_layer(desktop, this->defaultMessageContext())) { - // save drag origin - this->xp = (gint) button_w[Geom::X]; - this->yp = (gint) button_w[Geom::Y]; - this->within_tolerance = true; - - dragging = true; - - Geom::Point const p(desktop->w2d(button_w)); - Inkscape::Rubberband::get(desktop)->setMode(RUBBERBAND_MODE_TOUCHPATH); - Inkscape::Rubberband::get(desktop)->start(desktop, p); - } - } - } - - case GDK_MOTION_NOTIFY: - if ( dragging && ( event->motion.state & GDK_BUTTON1_MASK ) && !this->space_panning) { - if ( this->within_tolerance - && ( abs( (gint) event->motion.x - this->xp ) < this->tolerance ) - && ( abs( (gint) event->motion.y - this->yp ) < this->tolerance ) ) { - break; // do not drag if we're within tolerance from origin - } - - this->within_tolerance = false; - - Geom::Point const motion_pt(event->motion.x, event->motion.y); - Geom::Point const p(desktop->w2d(motion_pt)); - - if (Inkscape::Rubberband::get(desktop)->is_started()) { - Inkscape::Rubberband::get(desktop)->move(p); - this->defaultMessageContext()->set(Inkscape::NORMAL_MESSAGE, _("Draw over areas to add to fill, hold Alt for touch fill")); - gobble_motion_events(GDK_BUTTON1_MASK); - } - } - break; - - case GDK_BUTTON_RELEASE: - if (event->button.button == 1 && !this->space_panning) { - Inkscape::Rubberband *r = Inkscape::Rubberband::get(desktop); - - if (r->is_started()) { - // set "busy" cursor - desktop->setWaitingCursor(); - - if (SP_IS_EVENT_CONTEXT(this)) { - // Since setWaitingCursor runs main loop iterations, we may have already left this tool! - // So check if the tool is valid before doing anything - dragging = false; - - bool is_point_fill = this->within_tolerance; - bool is_touch_fill = event->button.state & GDK_MOD1_MASK; - - sp_flood_do_flood_fill(this, event, event->button.state & GDK_SHIFT_MASK, is_point_fill, is_touch_fill); - - desktop->clearWaitingCursor(); - // restore cursor when done; note that it may already be different if e.g. user - // switched to another tool during interruptible tracing or drawing, in which case do nothing - - ret = TRUE; - } - - r->stop(); - - //if (SP_IS_EVENT_CONTEXT(this)) { - this->defaultMessageContext()->clear(); - //} - } - } - break; - case GDK_KEY_PRESS: - switch (get_group0_keyval (&event->key)) { - case GDK_KEY_Up: - case GDK_KEY_Down: - case GDK_KEY_KP_Up: - case GDK_KEY_KP_Down: - // prevent the zoom field from activation - if (!MOD__CTRL_ONLY(event)) - ret = TRUE; - break; - default: - break; - } - break; - - default: - break; - } - - if (!ret) { - ret = ToolBase::root_handler(event); - } - - return ret; -} - -void FloodTool::finishItem() { - this->message_context->clear(); - - if (this->item != NULL) { - this->item->updateRepr(); - - desktop->canvas->endForcedFullRedraws(); - - sp_desktop_selection(desktop)->set(this->item); - - DocumentUndo::done(sp_desktop_document(desktop), SP_VERB_CONTEXT_PAINTBUCKET, _("Fill bounded area")); - - this->item = NULL; - } -} - -void FloodTool::set_channels(gint channels) { - Inkscape::Preferences *prefs = Inkscape::Preferences::get(); - prefs->setInt("/tools/paintbucket/channels", channels); -} - -} -} -} - -/* - Local Variables: - mode:c++ - c-file-style:"stroustrup" - c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) - indent-tabs-mode:nil - fill-column:99 - End: -*/ -// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/flood-context.h b/src/flood-context.h deleted file mode 100644 index 93d2c12de..000000000 --- a/src/flood-context.h +++ /dev/null @@ -1,74 +0,0 @@ -#ifndef __SP_FLOOD_CONTEXT_H__ -#define __SP_FLOOD_CONTEXT_H__ - -/* - * Flood fill drawing context - * - * Authors: - * Lauris Kaplinski - * John Bintz - * - * Released under GNU GPL - */ - -#include -#include -#include -#include "event-context.h" - -#define SP_FLOOD_CONTEXT(obj) (dynamic_cast((Inkscape::UI::Tools::ToolBase*)obj)) -#define SP_IS_FLOOD_CONTEXT(obj) (dynamic_cast((const Inkscape::UI::Tools::ToolBase*)obj) != NULL) - - -#define FLOOD_COLOR_CHANNEL_R 1 -#define FLOOD_COLOR_CHANNEL_G 2 -#define FLOOD_COLOR_CHANNEL_B 4 -#define FLOOD_COLOR_CHANNEL_A 8 - -namespace Inkscape { -namespace UI { -namespace Tools { - -class FloodTool : public ToolBase { -public: - FloodTool(); - virtual ~FloodTool(); - - SPItem *item; - - sigc::connection sel_changed_connection; - - static const std::string prefsPath; - - virtual void setup(); - virtual bool root_handler(GdkEvent* event); - virtual bool item_handler(SPItem* item, GdkEvent* event); - - virtual const std::string& getPrefsPath(); - - static void set_channels(gint channels); - -private: - void selection_changed(Inkscape::Selection* selection); - void finishItem(); -}; - -GList* flood_channels_dropdown_items_list (void); -GList* flood_autogap_dropdown_items_list (void); - -enum PaintBucketChannels { - FLOOD_CHANNELS_RGB, - FLOOD_CHANNELS_R, - FLOOD_CHANNELS_G, - FLOOD_CHANNELS_B, - FLOOD_CHANNELS_H, - FLOOD_CHANNELS_S, - FLOOD_CHANNELS_L, - FLOOD_CHANNELS_ALPHA -}; - -} -} -} - -#endif diff --git a/src/gradient-chemistry.cpp b/src/gradient-chemistry.cpp index a12db7b59..89b7968fc 100644 --- a/src/gradient-chemistry.cpp +++ b/src/gradient-chemistry.cpp @@ -32,7 +32,7 @@ #include "desktop.h" #include "desktop-style.h" #include "desktop-handles.h" -#include "event-context.h" +#include "ui/tools/tool-base.h" #include "selection.h" #include "verbs.h" #include diff --git a/src/gradient-context.cpp b/src/gradient-context.cpp deleted file mode 100644 index 3a0261cc7..000000000 --- a/src/gradient-context.cpp +++ /dev/null @@ -1,977 +0,0 @@ -/* - * Gradient drawing and editing tool - * - * Authors: - * bulia byak - * Johan Engelen - * Abhishek Sharma - * - * Copyright (C) 2007 Johan Engelen - * Copyright (C) 2005 Authors - * - * Released under GNU GPL, read the file 'COPYING' for more information - */ - -#ifdef HAVE_CONFIG_H -# include "config.h" -#endif - - -#include - -#include "macros.h" -#include "document.h" -#include "selection.h" -#include "desktop.h" -#include "desktop-handles.h" -#include "message-context.h" -#include "message-stack.h" -#include "pixmaps/cursor-gradient.xpm" -#include "pixmaps/cursor-gradient-add.xpm" -#include "gradient-context.h" -#include "gradient-chemistry.h" -#include -#include "preferences.h" -#include "gradient-drag.h" -#include "gradient-chemistry.h" -#include "xml/repr.h" -#include "sp-item.h" -#include "display/sp-ctrlline.h" -#include "sp-linear-gradient.h" -#include "sp-radial-gradient.h" -#include "sp-stop.h" -#include "svg/css-ostringstream.h" -#include "svg/svg-color.h" -#include "snap.h" -#include "sp-namedview.h" -#include "rubberband.h" -#include "document-undo.h" -#include "verbs.h" -#include "selection-chemistry.h" - -using Inkscape::DocumentUndo; - -#include "tool-factory.h" - -namespace Inkscape { -namespace UI { -namespace Tools { - -static void sp_gradient_drag(GradientTool &rc, Geom::Point const pt, guint state, guint32 etime); - -namespace { - ToolBase* createGradientContext() { - return new GradientTool(); - } - - bool gradientContextRegistered = ToolFactory::instance().registerObject("/tools/gradient", createGradientContext); -} - -const std::string& GradientTool::getPrefsPath() { - return GradientTool::prefsPath; -} - -const std::string GradientTool::prefsPath = "/tools/gradient"; - - -GradientTool::GradientTool() : ToolBase() { - this->node_added = false; - this->subselcon = 0; - this->selcon = 0; - - this->cursor_addnode = false; - this->cursor_shape = cursor_gradient_xpm; - this->hot_x = 4; - this->hot_y = 4; - this->xp = 0; - this->yp = 0; - this->tolerance = 6; - this->within_tolerance = false; - this->item_to_select = NULL; -} - -GradientTool::~GradientTool() { - this->enableGrDrag(false); - - this->selcon->disconnect(); - delete this->selcon; - - this->subselcon->disconnect(); - delete this->subselcon; -} - -const gchar *gr_handle_descr [] = { - N_("Linear gradient start"), //POINT_LG_BEGIN - N_("Linear gradient end"), - N_("Linear gradient mid stop"), - N_("Radial gradient center"), - N_("Radial gradient radius"), - N_("Radial gradient radius"), - N_("Radial gradient focus"), // POINT_RG_FOCUS - N_("Radial gradient mid stop"), - N_("Radial gradient mid stop") -}; - -void GradientTool::selection_changed(Inkscape::Selection*) { - GradientTool *rc = (GradientTool *) this; - - GrDrag *drag = rc->_grdrag; - Inkscape::Selection *selection = sp_desktop_selection(SP_EVENT_CONTEXT(rc)->desktop); - if (selection == NULL) { - return; - } - guint n_obj = g_slist_length((GSList *) selection->itemList()); - - if (!drag->isNonEmpty() || selection->isEmpty()) - return; - guint n_tot = drag->numDraggers(); - guint n_sel = drag->numSelected(); - - //The use of ngettext in the following code is intentional even if the English singular form would never be used - if (n_sel == 1) { - if (drag->singleSelectedDraggerNumDraggables() == 1) { - gchar * message = g_strconcat( - //TRANSLATORS: %s will be substituted with the point name (see previous messages); This is part of a compound message - _("%s selected"), - //TRANSLATORS: Mind the space in front. This is part of a compound message - ngettext(" out of %d gradient handle"," out of %d gradient handles",n_tot), - ngettext(" on %d selected object"," on %d selected objects",n_obj),NULL); - rc->message_context->setF(Inkscape::NORMAL_MESSAGE, - message,_(gr_handle_descr[drag->singleSelectedDraggerSingleDraggableType()]), n_tot, n_obj); - } else { - gchar * message = g_strconcat( - //TRANSLATORS: This is a part of a compound message (out of two more indicating: grandint handle count & object count) - ngettext("One handle merging %d stop (drag with Shift to separate) selected", - "One handle merging %d stops (drag with Shift to separate) selected",drag->singleSelectedDraggerNumDraggables()), - ngettext(" out of %d gradient handle"," out of %d gradient handles",n_tot), - ngettext(" on %d selected object"," on %d selected objects",n_obj),NULL); - rc->message_context->setF(Inkscape::NORMAL_MESSAGE,message,drag->singleSelectedDraggerNumDraggables(), n_tot, n_obj); - } - } else if (n_sel > 1) { - //TRANSLATORS: The plural refers to number of selected gradient handles. This is part of a compound message (part two indicates selected object count) - gchar * message = g_strconcat(ngettext("%d gradient handle selected out of %d","%d gradient handles selected out of %d",n_sel), - //TRANSLATORS: Mind the space in front. (Refers to gradient handles selected). This is part of a compound message - ngettext(" on %d selected object"," on %d selected objects",n_obj),NULL); - rc->message_context->setF(Inkscape::NORMAL_MESSAGE,message, n_sel, n_tot, n_obj); - } else if (n_sel == 0) { - rc->message_context->setF(Inkscape::NORMAL_MESSAGE, - //TRANSLATORS: The plural refers to number of selected objects - ngettext("No gradient handles selected out of %d on %d selected object", - "No gradient handles selected out of %d on %d selected objects",n_obj), n_tot, n_obj); - } -} - -void GradientTool::setup() { - ToolBase::setup(); - - Inkscape::Preferences *prefs = Inkscape::Preferences::get(); - - if (prefs->getBool("/tools/gradient/selcue", true)) { - this->enableSelectionCue(); - } - - this->enableGrDrag(); - Inkscape::Selection *selection = sp_desktop_selection(this->desktop); - - this->selcon = new sigc::connection(selection->connectChanged( - sigc::mem_fun(this, &GradientTool::selection_changed) - )); - - this->subselcon = new sigc::connection(this->desktop->connectToolSubselectionChanged( - sigc::hide(sigc::bind( - sigc::mem_fun(this, &GradientTool::selection_changed), - (Inkscape::Selection*)NULL - )) - )); - - this->selection_changed(selection); -} - -void -sp_gradient_context_select_next (ToolBase *event_context) -{ - GrDrag *drag = event_context->_grdrag; - g_assert (drag); - - GrDragger *d = drag->select_next(); - - event_context->desktop->scroll_to_point(d->point, 1.0); -} - -void -sp_gradient_context_select_prev (ToolBase *event_context) -{ - GrDrag *drag = event_context->_grdrag; - g_assert (drag); - - GrDragger *d = drag->select_prev(); - - event_context->desktop->scroll_to_point(d->point, 1.0); -} - -static bool -sp_gradient_context_is_over_line (GradientTool *rc, SPItem *item, Geom::Point event_p) -{ - SPDesktop *desktop = SP_EVENT_CONTEXT (rc)->desktop; - - //Translate mouse point into proper coord system - rc->mousepoint_doc = desktop->w2d(event_p); - - SPCtrlLine* line = SP_CTRLLINE(item); - - Geom::LineSegment ls(line->s, line->e); - Geom::Point nearest = ls.pointAt(ls.nearestPoint(rc->mousepoint_doc)); - double dist_screen = Geom::L2 (rc->mousepoint_doc - nearest) * desktop->current_zoom(); - - double tolerance = (double) SP_EVENT_CONTEXT(rc)->tolerance; - - bool close = (dist_screen < tolerance); - - return close; -} - -static std::vector -sp_gradient_context_get_stop_intervals (GrDrag *drag, GSList **these_stops, GSList **next_stops) -{ - std::vector coords; - - // for all selected draggers - for (GList *i = drag->selected; i != NULL; i = i->next) { - GrDragger *dragger = (GrDragger *) i->data; - // remember the coord of the dragger to reselect it later - coords.push_back(dragger->point); - // for all draggables of dragger - for (GSList const* j = dragger->draggables; j != NULL; j = j->next) { - GrDraggable *d = (GrDraggable *) j->data; - - // find the gradient - SPGradient *gradient = getGradient(d->item, d->fill_or_stroke); - SPGradient *vector = sp_gradient_get_forked_vector_if_necessary (gradient, false); - - // these draggable types cannot have a next draggabe to insert a stop between them - if (d->point_type == POINT_LG_END || - d->point_type == POINT_RG_FOCUS || - d->point_type == POINT_RG_R1 || - d->point_type == POINT_RG_R2) { - continue; - } - - // from draggables to stops - SPStop *this_stop = sp_get_stop_i (vector, d->point_i); - SPStop *next_stop = this_stop->getNextStop(); - SPStop *last_stop = sp_last_stop (vector); - - Inkscape::PaintTarget fs = d->fill_or_stroke; - SPItem *item = d->item; - gint type = d->point_type; - gint p_i = d->point_i; - - // if there's a next stop, - if (next_stop) { - GrDragger *dnext = NULL; - // find its dragger - // (complex because it may have different types, and because in radial, - // more than one dragger may correspond to a stop, so we must distinguish) - if (type == POINT_LG_BEGIN || type == POINT_LG_MID) { - if (next_stop == last_stop) { - dnext = drag->getDraggerFor(item, POINT_LG_END, p_i+1, fs); - } else { - dnext = drag->getDraggerFor(item, POINT_LG_MID, p_i+1, fs); - } - } else { // radial - if (type == POINT_RG_CENTER || type == POINT_RG_MID1) { - if (next_stop == last_stop) { - dnext = drag->getDraggerFor(item, POINT_RG_R1, p_i+1, fs); - } else { - dnext = drag->getDraggerFor(item, POINT_RG_MID1, p_i+1, fs); - } - } - if ((type == POINT_RG_MID2) || - (type == POINT_RG_CENTER && dnext && !dnext->isSelected())) { - if (next_stop == last_stop) { - dnext = drag->getDraggerFor(item, POINT_RG_R2, p_i+1, fs); - } else { - dnext = drag->getDraggerFor(item, POINT_RG_MID2, p_i+1, fs); - } - } - } - - // if both adjacent draggers selected, - if (!g_slist_find(*these_stops, this_stop) && dnext && dnext->isSelected()) { - - // remember the coords of the future dragger to select it - coords.push_back(0.5*(dragger->point + dnext->point)); - - // do not insert a stop now, it will confuse the loop; - // just remember the stops - *these_stops = g_slist_prepend (*these_stops, this_stop); - *next_stops = g_slist_prepend (*next_stops, next_stop); - } - } - } - } - return coords; -} - -void -sp_gradient_context_add_stops_between_selected_stops (GradientTool *rc) -{ - SPDocument *doc = NULL; - GrDrag *drag = rc->_grdrag; - - GSList *these_stops = NULL; - GSList *next_stops = NULL; - - std::vector coords = sp_gradient_context_get_stop_intervals (drag, &these_stops, &next_stops); - - if (g_slist_length(these_stops) == 0 && drag->numSelected() == 1) { - // if a single stop is selected, add between that stop and the next one - GrDragger *dragger = (GrDragger *) drag->selected->data; - for (GSList const* j = dragger->draggables; j != NULL; j = j->next) { - GrDraggable *d = (GrDraggable *) j->data; - if (d->point_type == POINT_RG_FOCUS) { - /* - * There are 2 draggables at the center (start) of a radial gradient - * To avoid creating 2 seperate stops, ignore this draggable point type - */ - continue; - } - SPGradient *gradient = getGradient(d->item, d->fill_or_stroke); - SPGradient *vector = sp_gradient_get_forked_vector_if_necessary (gradient, false); - SPStop *this_stop = sp_get_stop_i (vector, d->point_i); - SPStop *next_stop = this_stop->getNextStop(); - if (this_stop && next_stop) { - these_stops = g_slist_prepend (these_stops, this_stop); - next_stops = g_slist_prepend (next_stops, next_stop); - } - } - } - - // now actually create the new stops - GSList *i = these_stops; - GSList *j = next_stops; - GSList *new_stops = NULL; - - for (; i != NULL && j != NULL; i = i->next, j = j->next) { - SPStop *this_stop = (SPStop *) i->data; - SPStop *next_stop = (SPStop *) j->data; - gfloat offset = 0.5*(this_stop->offset + next_stop->offset); - SPObject *parent = this_stop->parent; - if (SP_IS_GRADIENT (parent)) { - doc = parent->document; - SPStop *new_stop = sp_vector_add_stop (SP_GRADIENT (parent), this_stop, next_stop, offset); - new_stops = g_slist_prepend (new_stops, new_stop); - SP_GRADIENT(parent)->ensureVector(); - } - } - - if (g_slist_length(these_stops) > 0 && doc) { - DocumentUndo::done(doc, SP_VERB_CONTEXT_GRADIENT, _("Add gradient stop")); - drag->updateDraggers(); - // so that it does not automatically update draggers in idle loop, as this would deselect - drag->local_change = true; - - // select the newly created stops - for (GSList *s = new_stops; s != NULL; s = s->next) { - drag->selectByStop((SPStop *)s->data); - } - - } - - g_slist_free (these_stops); - g_slist_free (next_stops); - g_slist_free (new_stops); -} - -static double sqr(double x) {return x*x;} - -static void -sp_gradient_simplify(GradientTool *rc, double tolerance) -{ - SPDocument *doc = NULL; - GrDrag *drag = rc->_grdrag; - - GSList *these_stops = NULL; - GSList *next_stops = NULL; - - std::vector coords = sp_gradient_context_get_stop_intervals (drag, &these_stops, &next_stops); - - GSList *todel = NULL; - - GSList *i = these_stops; - GSList *j = next_stops; - for (; i != NULL && j != NULL; i = i->next, j = j->next) { - SPStop *stop0 = (SPStop *) i->data; - SPStop *stop1 = (SPStop *) j->data; - - gint i1 = g_slist_index(these_stops, stop1); - if (i1 != -1) { - GSList *next_next = g_slist_nth (next_stops, i1); - if (next_next) { - SPStop *stop2 = (SPStop *) next_next->data; - - if (g_slist_find(todel, stop0) || g_slist_find(todel, stop2)) - continue; - - guint32 const c0 = stop0->get_rgba32(); - guint32 const c2 = stop2->get_rgba32(); - guint32 const c1r = stop1->get_rgba32(); - guint32 c1 = average_color (c0, c2, - (stop1->offset - stop0->offset) / (stop2->offset - stop0->offset)); - - double diff = - sqr(SP_RGBA32_R_F(c1) - SP_RGBA32_R_F(c1r)) + - sqr(SP_RGBA32_G_F(c1) - SP_RGBA32_G_F(c1r)) + - sqr(SP_RGBA32_B_F(c1) - SP_RGBA32_B_F(c1r)) + - sqr(SP_RGBA32_A_F(c1) - SP_RGBA32_A_F(c1r)); - - if (diff < tolerance) - todel = g_slist_prepend (todel, stop1); - } - } - } - - for (i = todel; i != NULL; i = i->next) { - SPStop *stop = (SPStop*) i->data; - doc = stop->document; - Inkscape::XML::Node * parent = stop->getRepr()->parent(); - parent->removeChild( stop->getRepr() ); - } - - if (g_slist_length(todel) > 0) { - DocumentUndo::done(doc, SP_VERB_CONTEXT_GRADIENT, _("Simplify gradient")); - drag->local_change = true; - drag->updateDraggers(); - drag->selectByCoords(coords); - } - - g_slist_free (todel); - g_slist_free (these_stops); - g_slist_free (next_stops); -} - - -static void -sp_gradient_context_add_stop_near_point (GradientTool *rc, SPItem *item, Geom::Point mouse_p, guint32 /*etime*/) -{ - // item is the selected item. mouse_p the location in doc coordinates of where to add the stop - - ToolBase *ec = SP_EVENT_CONTEXT(rc); - SPDesktop *desktop = SP_EVENT_CONTEXT (rc)->desktop; - - double tolerance = (double) ec->tolerance; - - SPStop *newstop = ec->get_drag()->addStopNearPoint (item, mouse_p, tolerance/desktop->current_zoom()); - - DocumentUndo::done(sp_desktop_document (desktop), SP_VERB_CONTEXT_GRADIENT, - _("Add gradient stop")); - - ec->get_drag()->updateDraggers(); - ec->get_drag()->local_change = true; - ec->get_drag()->selectByStop(newstop); -} - -bool GradientTool::root_handler(GdkEvent* event) { - static bool dragging; - - Inkscape::Selection *selection = sp_desktop_selection (desktop); - Inkscape::Preferences *prefs = Inkscape::Preferences::get(); - - this->tolerance = prefs->getIntLimited("/options/dragtolerance/value", 0, 0, 100); - double const nudge = prefs->getDoubleLimited("/options/nudgedistance/value", 2, 0, 1000, "px"); // in px - - GrDrag *drag = this->_grdrag; - g_assert (drag); - - gint ret = FALSE; - - switch (event->type) { - case GDK_2BUTTON_PRESS: - if ( event->button.button == 1 ) { - bool over_line = false; - SPCtrlLine *line = NULL; - - if (drag->lines) { - for (GSList *l = drag->lines; (l != NULL) && (!over_line); l = l->next) { - line = (SPCtrlLine*) l->data; - over_line |= sp_gradient_context_is_over_line (this, (SPItem*) line, Geom::Point(event->motion.x, event->motion.y)); - } - } - - if (over_line) { - // we take the first item in selection, because with doubleclick, the first click - // always resets selection to the single object under cursor - sp_gradient_context_add_stop_near_point(this, SP_ITEM(selection->itemList()->data), this->mousepoint_doc, event->button.time); - } else { - for (GSList const* i = selection->itemList(); i != NULL; i = i->next) { - SPItem *item = SP_ITEM(i->data); - SPGradientType new_type = (SPGradientType) prefs->getInt("/tools/gradient/newgradient", SP_GRADIENT_TYPE_LINEAR); - Inkscape::PaintTarget fsmode = (prefs->getInt("/tools/gradient/newfillorstroke", 1) != 0) ? Inkscape::FOR_FILL : Inkscape::FOR_STROKE; - - SPGradient *vector = sp_gradient_vector_for_object(sp_desktop_document(desktop), desktop, item, fsmode); - - SPGradient *priv = sp_item_set_gradient(item, vector, new_type, fsmode); - sp_gradient_reset_to_userspace(priv, item); - } - - DocumentUndo::done(sp_desktop_document (desktop), SP_VERB_CONTEXT_GRADIENT, - _("Create default gradient")); - } - ret = TRUE; - } - break; - - case GDK_BUTTON_PRESS: - if ( event->button.button == 1 && !this->space_panning ) { - Geom::Point button_w(event->button.x, event->button.y); - - // save drag origin - this->xp = (gint) button_w[Geom::X]; - this->yp = (gint) button_w[Geom::Y]; - this->within_tolerance = true; - - dragging = true; - - Geom::Point button_dt = desktop->w2d(button_w); - if (event->button.state & GDK_SHIFT_MASK) { - Inkscape::Rubberband::get(desktop)->start(desktop, button_dt); - } else { - // remember clicked item, disregarding groups, honoring Alt; do nothing with Crtl to - // enable Ctrl+doubleclick of exactly the selected item(s) - if (!(event->button.state & GDK_CONTROL_MASK)) { - this->item_to_select = sp_event_context_find_item (desktop, button_w, event->button.state & GDK_MOD1_MASK, TRUE); - } - - if (!selection->isEmpty()) { - SnapManager &m = desktop->namedview->snap_manager; - m.setup(desktop); - m.freeSnapReturnByRef(button_dt, Inkscape::SNAPSOURCE_NODE_HANDLE); - m.unSetup(); - } - - this->origin = button_dt; - } - - ret = TRUE; - } - break; - - case GDK_MOTION_NOTIFY: - if (dragging && ( event->motion.state & GDK_BUTTON1_MASK ) && !this->space_panning) { - if ( this->within_tolerance - && ( abs( (gint) event->motion.x - this->xp ) < this->tolerance ) - && ( abs( (gint) event->motion.y - this->yp ) < this->tolerance ) ) { - break; // do not drag if we're within tolerance from origin - } - // Once the user has moved farther than tolerance from the original location - // (indicating they intend to draw, not click), then always process the - // motion notify coordinates as given (no snapping back to origin) - this->within_tolerance = false; - - Geom::Point const motion_w(event->motion.x, - event->motion.y); - Geom::Point const motion_dt = this->desktop->w2d(motion_w); - - if (Inkscape::Rubberband::get(desktop)->is_started()) { - Inkscape::Rubberband::get(desktop)->move(motion_dt); - this->defaultMessageContext()->set(Inkscape::NORMAL_MESSAGE, _("Draw around handles to select them")); - } else { - sp_gradient_drag(*this, motion_dt, event->motion.state, event->motion.time); - } - - gobble_motion_events(GDK_BUTTON1_MASK); - - ret = TRUE; - } else { - if (!drag->mouseOver() && !selection->isEmpty()) { - SnapManager &m = desktop->namedview->snap_manager; - m.setup(desktop); - - Geom::Point const motion_w(event->motion.x, event->motion.y); - Geom::Point const motion_dt = this->desktop->w2d(motion_w); - - m.preSnap(Inkscape::SnapCandidatePoint(motion_dt, Inkscape::SNAPSOURCE_OTHER_HANDLE)); - m.unSetup(); - } - - bool over_line = false; - - if (drag->lines) { - for (GSList *l = drag->lines; l != NULL; l = l->next) { - over_line |= sp_gradient_context_is_over_line (this, (SPItem*) l->data, Geom::Point(event->motion.x, event->motion.y)); - } - } - - if (this->cursor_addnode && !over_line) { - this->cursor_shape = cursor_gradient_xpm; - this->sp_event_context_update_cursor(); - this->cursor_addnode = false; - } else if (!this->cursor_addnode && over_line) { - this->cursor_shape = cursor_gradient_add_xpm; - this->sp_event_context_update_cursor(); - this->cursor_addnode = true; - } - } - break; - - case GDK_BUTTON_RELEASE: - this->xp = this->yp = 0; - - if ( event->button.button == 1 && !this->space_panning ) { - bool over_line = false; - SPCtrlLine *line = NULL; - - if (drag->lines) { - for (GSList *l = drag->lines; (l != NULL) && (!over_line); l = l->next) { - line = (SPCtrlLine*) l->data; - over_line = sp_gradient_context_is_over_line (this, (SPItem*) line, Geom::Point(event->motion.x, event->motion.y)); - if (over_line) - break; - } - } - - if ( (event->button.state & GDK_CONTROL_MASK) && (event->button.state & GDK_MOD1_MASK ) ) { - if (over_line && line) { - sp_gradient_context_add_stop_near_point(this, line->item, this->mousepoint_doc, 0); - ret = TRUE; - } - } else { - dragging = false; - - // unless clicked with Ctrl (to enable Ctrl+doubleclick). - if (event->button.state & GDK_CONTROL_MASK) { - ret = TRUE; - break; - } - - if (!this->within_tolerance) { - // we've been dragging, either do nothing (grdrag handles that), - // or rubberband-select if we have rubberband - Inkscape::Rubberband *r = Inkscape::Rubberband::get(desktop); - - if (r->is_started() && !this->within_tolerance) { - // this was a rubberband drag - if (r->getMode() == RUBBERBAND_MODE_RECT) { - Geom::OptRect const b = r->getRectangle(); - drag->selectRect(*b); - } - } - } else if (this->item_to_select) { - if (over_line && line) { - // Clicked on an existing gradient line, dont change selection. This stops - // possible change in selection during a double click with overlapping objects - } else { - // no dragging, select clicked item if any - if (event->button.state & GDK_SHIFT_MASK) { - selection->toggle(this->item_to_select); - } else { - drag->deselectAll(); - selection->set(this->item_to_select); - } - } - } else { - // click in an empty space; do the same as Esc - if (drag->selected) { - drag->deselectAll(); - } else { - selection->clear(); - } - } - - this->item_to_select = NULL; - ret = TRUE; - } - - Inkscape::Rubberband::get(desktop)->stop(); - } - break; - - case GDK_KEY_PRESS: - switch (get_group0_keyval (&event->key)) { - case GDK_KEY_Alt_L: - case GDK_KEY_Alt_R: - case GDK_KEY_Control_L: - case GDK_KEY_Control_R: - case GDK_KEY_Shift_L: - case GDK_KEY_Shift_R: - case GDK_KEY_Meta_L: // Meta is when you press Shift+Alt (at least on my machine) - case GDK_KEY_Meta_R: - sp_event_show_modifier_tip (this->defaultMessageContext(), event, - _("Ctrl: snap gradient angle"), - _("Shift: draw gradient around the starting point"), - NULL); - break; - - case GDK_KEY_x: - case GDK_KEY_X: - if (MOD__ALT_ONLY(event)) { - desktop->setToolboxFocusTo ("altx-grad"); - ret = TRUE; - } - break; - - case GDK_KEY_A: - case GDK_KEY_a: - if (MOD__CTRL_ONLY(event) && drag->isNonEmpty()) { - drag->selectAll(); - ret = TRUE; - } - break; - - case GDK_KEY_L: - case GDK_KEY_l: - if (MOD__CTRL_ONLY(event) && drag->isNonEmpty() && drag->hasSelection()) { - sp_gradient_simplify(this, 1e-4); - ret = TRUE; - } - break; - - case GDK_KEY_Escape: - if (drag->selected) { - drag->deselectAll(); - } else { - Inkscape::SelectionHelper::selectNone(desktop); - } - ret = TRUE; - //TODO: make dragging escapable by Esc - break; - - case GDK_KEY_Left: // move handle left - case GDK_KEY_KP_Left: - case GDK_KEY_KP_4: - if (!MOD__CTRL(event)) { // not ctrl - gint mul = 1 + gobble_key_events( - get_group0_keyval(&event->key), 0); // with any mask - if (MOD__ALT(event)) { // alt - if (MOD__SHIFT(event)) { - drag->selected_move_screen(mul*-10, 0); // shift - } else { - drag->selected_move_screen(mul*-1, 0); // no shift - } - } else { // no alt - if (MOD__SHIFT(event)) { - drag->selected_move(mul*-10*nudge, 0); // shift - } else { - drag->selected_move(mul*-nudge, 0); // no shift - } - } - ret = TRUE; - } - break; - - case GDK_KEY_Up: // move handle up - case GDK_KEY_KP_Up: - case GDK_KEY_KP_8: - if (!MOD__CTRL(event)) { // not ctrl - gint mul = 1 + gobble_key_events( - get_group0_keyval(&event->key), 0); // with any mask - if (MOD__ALT(event)) { // alt - if (MOD__SHIFT(event)) { - drag->selected_move_screen(0, mul*10); // shift - } else { - drag->selected_move_screen(0, mul*1); // no shift - } - } else { // no alt - if (MOD__SHIFT(event)) { - drag->selected_move(0, mul*10*nudge); // shift - } else { - drag->selected_move(0, mul*nudge); // no shift - } - } - - ret = TRUE; - } - break; - - case GDK_KEY_Right: // move handle right - case GDK_KEY_KP_Right: - case GDK_KEY_KP_6: - if (!MOD__CTRL(event)) { // not ctrl - gint mul = 1 + gobble_key_events( - get_group0_keyval(&event->key), 0); // with any mask - - if (MOD__ALT(event)) { // alt - if (MOD__SHIFT(event)) { - drag->selected_move_screen(mul*10, 0); // shift - } else { - drag->selected_move_screen(mul*1, 0); // no shift - } - } else { // no alt - if (MOD__SHIFT(event)) { - drag->selected_move(mul*10*nudge, 0); // shift - } else { - drag->selected_move(mul*nudge, 0); // no shift - } - } - - ret = TRUE; - } - break; - - case GDK_KEY_Down: // move handle down - case GDK_KEY_KP_Down: - case GDK_KEY_KP_2: - if (!MOD__CTRL(event)) { // not ctrl - gint mul = 1 + gobble_key_events( - get_group0_keyval(&event->key), 0); // with any mask - - if (MOD__ALT(event)) { // alt - if (MOD__SHIFT(event)) { - drag->selected_move_screen(0, mul*-10); // shift - } else { - drag->selected_move_screen(0, mul*-1); // no shift - } - } else { // no alt - if (MOD__SHIFT(event)) { - drag->selected_move(0, mul*-10*nudge); // shift - } else { - drag->selected_move(0, mul*-nudge); // no shift - } - } - - ret = TRUE; - } - break; - - case GDK_KEY_r: - case GDK_KEY_R: - if (MOD__SHIFT_ONLY(event)) { - sp_gradient_reverse_selected_gradients(desktop); - ret = TRUE; - } - break; - - case GDK_KEY_Insert: - case GDK_KEY_KP_Insert: - // with any modifiers: - sp_gradient_context_add_stops_between_selected_stops (this); - ret = TRUE; - break; - - case GDK_KEY_Delete: - case GDK_KEY_KP_Delete: - case GDK_KEY_BackSpace: - ret = this->deleteSelectedDrag(MOD__CTRL_ONLY(event)); - break; - - default: - break; - } - break; - - case GDK_KEY_RELEASE: - switch (get_group0_keyval (&event->key)) { - case GDK_KEY_Alt_L: - case GDK_KEY_Alt_R: - case GDK_KEY_Control_L: - case GDK_KEY_Control_R: - case GDK_KEY_Shift_L: - case GDK_KEY_Shift_R: - case GDK_KEY_Meta_L: // Meta is when you press Shift+Alt - case GDK_KEY_Meta_R: - this->defaultMessageContext()->clear(); - break; - - default: - break; - } - break; - - default: - break; - } - - if (!ret) { - ret = ToolBase::root_handler(event); - } - - return ret; -} - -static void sp_gradient_drag(GradientTool &rc, Geom::Point const pt, guint /*state*/, guint32 etime) -{ - SPDesktop *desktop = SP_EVENT_CONTEXT(&rc)->desktop; - Inkscape::Selection *selection = sp_desktop_selection(desktop); - SPDocument *document = sp_desktop_document(desktop); - ToolBase *ec = SP_EVENT_CONTEXT(&rc); - - if (!selection->isEmpty()) { - Inkscape::Preferences *prefs = Inkscape::Preferences::get(); - int type = prefs->getInt("/tools/gradient/newgradient", 1); - Inkscape::PaintTarget fill_or_stroke = (prefs->getInt("/tools/gradient/newfillorstroke", 1) != 0) ? Inkscape::FOR_FILL : Inkscape::FOR_STROKE; - - SPGradient *vector; - if (ec->item_to_select) { - // pick color from the object where drag started - vector = sp_gradient_vector_for_object(document, desktop, ec->item_to_select, fill_or_stroke); - } else { - // Starting from empty space: - // Sort items so that the topmost comes last - GSList *items = g_slist_copy ((GSList *) selection->itemList()); - items = g_slist_sort(items, (GCompareFunc) sp_item_repr_compare_position); - // take topmost - vector = sp_gradient_vector_for_object(document, desktop, SP_ITEM(g_slist_last(items)->data), fill_or_stroke); - g_slist_free (items); - } - - // HACK: reset fill-opacity - that 0.75 is annoying; BUT remove this when we have an opacity slider for all tabs - SPCSSAttr *css = sp_repr_css_attr_new(); - sp_repr_css_set_property(css, "fill-opacity", "1.0"); - - for (GSList const *i = selection->itemList(); i != NULL; i = i->next) { - - //FIXME: see above - sp_repr_css_change_recursive(SP_OBJECT(i->data)->getRepr(), css, "style"); - - sp_item_set_gradient(SP_ITEM(i->data), vector, (SPGradientType) type, fill_or_stroke); - - if (type == SP_GRADIENT_TYPE_LINEAR) { - sp_item_gradient_set_coords (SP_ITEM(i->data), POINT_LG_BEGIN, 0, rc.origin, fill_or_stroke, true, false); - sp_item_gradient_set_coords (SP_ITEM(i->data), POINT_LG_END, 0, pt, fill_or_stroke, true, false); - } else if (type == SP_GRADIENT_TYPE_RADIAL) { - sp_item_gradient_set_coords (SP_ITEM(i->data), POINT_RG_CENTER, 0, rc.origin, fill_or_stroke, true, false); - sp_item_gradient_set_coords (SP_ITEM(i->data), POINT_RG_R1, 0, pt, fill_or_stroke, true, false); - } - SP_OBJECT(i->data)->requestModified(SP_OBJECT_MODIFIED_FLAG); - } - if (ec->_grdrag) { - ec->_grdrag->updateDraggers(); - // prevent regenerating draggers by selection modified signal, which sometimes - // comes too late and thus destroys the knot which we will now grab: - ec->_grdrag->local_change = true; - // give the grab out-of-bounds values of xp/yp because we're already dragging - // and therefore are already out of tolerance - ec->_grdrag->grabKnot (SP_ITEM(selection->itemList()->data), - type == SP_GRADIENT_TYPE_LINEAR? POINT_LG_END : POINT_RG_R1, - -1, // ignore number (though it is always 1) - fill_or_stroke, 99999, 99999, etime); - } - // We did an undoable action, but SPDocumentUndo::done will be called by the knot when released - - // status text; we do not track coords because this branch is run once, not all the time - // during drag - int n_objects = g_slist_length((GSList *) selection->itemList()); - rc.message_context->setF(Inkscape::NORMAL_MESSAGE, - ngettext("Gradient for %d object; with Ctrl to snap angle", - "Gradient for %d objects; with Ctrl to snap angle", n_objects), - n_objects); - } else { - sp_desktop_message_stack(desktop)->flash(Inkscape::WARNING_MESSAGE, _("Select objects on which to create gradient.")); - } -} - -} -} -} - - -/* - Local Variables: - mode:c++ - c-file-style:"stroustrup" - c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) - indent-tabs-mode:nil - fill-column:99 - End: -*/ -// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/gradient-context.h b/src/gradient-context.h deleted file mode 100644 index 5530791df..000000000 --- a/src/gradient-context.h +++ /dev/null @@ -1,76 +0,0 @@ -#ifndef __SP_GRADIENT_CONTEXT_H__ -#define __SP_GRADIENT_CONTEXT_H__ - -/* - * Gradient drawing and editing tool - * - * Authors: - * bulia byak - * Johan Engelen - * Jon A. Cruz -#include -#include "event-context.h" - -#define SP_GRADIENT_CONTEXT(obj) (dynamic_cast((Inkscape::UI::Tools::ToolBase*)obj)) -#define SP_IS_GRADIENT_CONTEXT(obj) (dynamic_cast((const Inkscape::UI::Tools::ToolBase*)obj) != NULL) - -namespace Inkscape { -namespace UI { -namespace Tools { - -class GradientTool : public ToolBase { -public: - GradientTool(); - virtual ~GradientTool(); - - Geom::Point origin; - - bool cursor_addnode; - - bool node_added; - - Geom::Point mousepoint_doc; // stores mousepoint when over_line in doc coords - - sigc::connection *selcon; - sigc::connection *subselcon; - - static const std::string prefsPath; - - virtual void setup(); - virtual bool root_handler(GdkEvent* event); - - virtual const std::string& getPrefsPath(); - -private: - void selection_changed(Inkscape::Selection*); -}; - -void sp_gradient_context_select_next (ToolBase *event_context); -void sp_gradient_context_select_prev (ToolBase *event_context); -void sp_gradient_context_add_stops_between_selected_stops (GradientTool *rc); - -} -} -} - -#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:fileencoding=utf-8:textwidth=99 : diff --git a/src/helper/window.cpp b/src/helper/window.cpp index 17501c260..98fbef170 100644 --- a/src/helper/window.cpp +++ b/src/helper/window.cpp @@ -22,7 +22,7 @@ #include "inkscape.h" #include "shortcuts.h" #include "desktop.h" -#include "event-context.h" +#include "ui/tools/tool-base.h" #include "window.h" #include diff --git a/src/inkscape.cpp b/src/inkscape.cpp index 8973c7b25..8c4ba5695 100644 --- a/src/inkscape.cpp +++ b/src/inkscape.cpp @@ -51,7 +51,7 @@ #include "desktop-handles.h" #include "device-manager.h" #include "document.h" -#include "event-context.h" +#include "ui/tools/tool-base.h" #include "extension/db.h" #include "extension/init.h" #include "extension/output.h" diff --git a/src/interface.cpp b/src/interface.cpp index f411989ce..5d4022e7b 100644 --- a/src/interface.cpp +++ b/src/interface.cpp @@ -67,7 +67,7 @@ #include "svg/svg-color.h" #include "desktop-style.h" #include "style.h" -#include "event-context.h" +#include "ui/tools/tool-base.h" #include "gradient-drag.h" #include "widgets/ege-paint-def.h" #include "document-undo.h" @@ -1300,7 +1300,7 @@ sp_ui_drag_data_received(GtkWidget *widget, } } -#include "gradient-context.h" +#include "ui/tools/gradient-tool.h" void sp_ui_drag_motion( GtkWidget */*widget*/, GdkDragContext */*drag_context*/, diff --git a/src/knot.cpp b/src/knot.cpp index 0fb0600c4..2179bc3d5 100644 --- a/src/knot.cpp +++ b/src/knot.cpp @@ -27,7 +27,7 @@ #include "preferences.h" #include "message-stack.h" #include "message-context.h" -#include "event-context.h" +#include "ui/tools/tool-base.h" using Inkscape::DocumentUndo; diff --git a/src/knotholder.cpp b/src/knotholder.cpp index 7129348c2..71bccca16 100644 --- a/src/knotholder.cpp +++ b/src/knotholder.cpp @@ -20,13 +20,13 @@ #include "knot.h" #include "knotholder.h" #include "knot-holder-entity.h" -#include "rect-context.h" +#include "ui/tools/rect-tool.h" #include "sp-rect.h" -#include "arc-context.h" +#include "ui/tools/arc-tool.h" #include "sp-ellipse.h" -#include "star-context.h" +#include "ui/tools/tweak-tool.h" #include "sp-star.h" -#include "spiral-context.h" +#include "ui/tools/spiral-tool.h" #include "sp-spiral.h" #include "sp-offset.h" #include "box3d.h" diff --git a/src/live_effects/effect.cpp b/src/live_effects/effect.cpp index 21ef02359..70ce1e89d 100644 --- a/src/live_effects/effect.cpp +++ b/src/live_effects/effect.cpp @@ -58,7 +58,7 @@ #include "document-private.h" #include "xml/document.h" #include -#include "pen-context.h" +#include "ui/tools/pen-tool.h" #include "tools-switch.h" #include "message-stack.h" #include "desktop.h" diff --git a/src/live_effects/lpe-line_segment.cpp b/src/live_effects/lpe-line_segment.cpp index 793b00bcc..78d286143 100644 --- a/src/live_effects/lpe-line_segment.cpp +++ b/src/live_effects/lpe-line_segment.cpp @@ -12,7 +12,7 @@ */ #include "live_effects/lpe-line_segment.h" -#include "lpe-tool-context.h" +#include "ui/tools/lpe-tool.h" #include <2geom/pathvector.h> #include <2geom/geom.h> diff --git a/src/live_effects/parameter/path.cpp b/src/live_effects/parameter/path.cpp index 6e951f6ea..77f7eabcc 100644 --- a/src/live_effects/parameter/path.cpp +++ b/src/live_effects/parameter/path.cpp @@ -40,7 +40,7 @@ #include "sp-text.h" #include "display/curve.h" -#include "ui/tool/node-tool.h" +#include "ui/tools/node-tool.h" #include "ui/tool/multi-path-manipulator.h" #include "ui/tool/shape-record.h" diff --git a/src/lpe-tool-context.cpp b/src/lpe-tool-context.cpp deleted file mode 100644 index 3d004f821..000000000 --- a/src/lpe-tool-context.cpp +++ /dev/null @@ -1,503 +0,0 @@ -/* - * LPEToolContext: a context for a generic tool composed of subtools that are given by LPEs - * - * Authors: - * Maximilian Albert - * Lauris Kaplinski - * Abhishek Sharma - * - * Copyright (C) 1998 The Free Software Foundation - * Copyright (C) 1999-2005 authors - * Copyright (C) 2001-2002 Ximian, Inc. - * Copyright (C) 2008 Maximilian Albert - * - * Released under GNU GPL, read the file 'COPYING' for more information - */ - -#ifdef HAVE_CONFIG_H -#include "config.h" -#endif - -#include <2geom/sbasis-geometric.h> -#include - -#include -#include "macros.h" -#include "pixmaps/cursor-crosshairs.xpm" -#include -#include "desktop.h" -#include "message-context.h" -#include "preferences.h" -#include "shape-editor.h" -#include "selection.h" -#include "desktop-handles.h" -#include "document.h" -#include "display/curve.h" -#include "display/canvas-bpath.h" -#include "display/canvas-text.h" -#include "message-stack.h" -#include "sp-path.h" -#include "util/units.h" - -#include "lpe-tool-context.h" - -using Inkscape::Util::unit_table; -using Inkscape::UI::Tools::PenTool; - -const int num_subtools = 8; - -SubtoolEntry lpesubtools[] = { - // this must be here to account for the "all inactive" action - {Inkscape::LivePathEffect::INVALID_LPE, "draw-geometry-inactive"}, - {Inkscape::LivePathEffect::LINE_SEGMENT, "draw-geometry-line-segment"}, - {Inkscape::LivePathEffect::CIRCLE_3PTS, "draw-geometry-circle-from-three-points"}, - {Inkscape::LivePathEffect::CIRCLE_WITH_RADIUS, "draw-geometry-circle-from-radius"}, - {Inkscape::LivePathEffect::PARALLEL, "draw-geometry-line-parallel"}, - {Inkscape::LivePathEffect::PERP_BISECTOR, "draw-geometry-line-perpendicular"}, - {Inkscape::LivePathEffect::ANGLE_BISECTOR, "draw-geometry-angle-bisector"}, - {Inkscape::LivePathEffect::MIRROR_SYMMETRY, "draw-geometry-mirror"} -}; - - -#include "tool-factory.h" - -namespace Inkscape { -namespace UI { -namespace Tools { - -void sp_lpetool_context_selection_changed(Inkscape::Selection *selection, gpointer data); - -namespace { - ToolBase* createLPEToolContext() { - return new LpeTool(); - } - - bool lpetoolContextRegistered = ToolFactory::instance().registerObject("/tools/lpetool", createLPEToolContext); -} - -const std::string& LpeTool::getPrefsPath() { - return LpeTool::prefsPath; -} - -const std::string LpeTool::prefsPath = "/tools/lpetool"; - -LpeTool::LpeTool() : PenTool() { - this->mode = Inkscape::LivePathEffect::BEND_PATH; - this->shape_editor = 0; - - this->cursor_shape = cursor_crosshairs_xpm; - this->hot_x = 7; - this->hot_y = 7; - - this->canvas_bbox = NULL; - this->measuring_items = new std::map; -} - -LpeTool::~LpeTool() { - delete this->shape_editor; - this->shape_editor = NULL; - - if (this->canvas_bbox) { - sp_canvas_item_destroy(SP_CANVAS_ITEM(this->canvas_bbox)); - this->canvas_bbox = NULL; - } - - lpetool_delete_measuring_items(this); - delete this->measuring_items; - this->measuring_items = NULL; - - this->sel_changed_connection.disconnect(); -} - -void LpeTool::setup() { - PenTool::setup(); - - Inkscape::Selection *selection = sp_desktop_selection (this->desktop); - SPItem *item = selection->singleItem(); - - this->sel_changed_connection.disconnect(); - this->sel_changed_connection = - selection->connectChanged(sigc::bind(sigc::ptr_fun(&sp_lpetool_context_selection_changed), (gpointer)this)); - - this->shape_editor = new ShapeEditor(this->desktop); - - lpetool_context_switch_mode(this, Inkscape::LivePathEffect::INVALID_LPE); - lpetool_context_reset_limiting_bbox(this); - lpetool_create_measuring_items(this); - -// TODO temp force: - this->enableSelectionCue(); - - Inkscape::Preferences *prefs = Inkscape::Preferences::get(); - - if (item) { - this->shape_editor->set_item(item, SH_NODEPATH); - this->shape_editor->set_item(item, SH_KNOTHOLDER); - } - - if (prefs->getBool("/tools/lpetool/selcue")) { - this->enableSelectionCue(); - } -} - -/** - * Callback that processes the "changed" signal on the selection; - * destroys old and creates new nodepath and reassigns listeners to the new selected item's repr. - */ -void sp_lpetool_context_selection_changed(Inkscape::Selection *selection, gpointer data) -{ - LpeTool *lc = SP_LPETOOL_CONTEXT(data); - - lc->shape_editor->unset_item(SH_KNOTHOLDER); - SPItem *item = selection->singleItem(); - lc->shape_editor->set_item(item, SH_KNOTHOLDER); -} - -void LpeTool::set(const Inkscape::Preferences::Entry& val) { - if (val.getEntryName() == "mode") { - Inkscape::Preferences::get()->setString("/tools/geometric/mode", "drag"); - SP_PEN_CONTEXT(this)->mode = PenTool::MODE_DRAG; - } -} - -bool LpeTool::item_handler(SPItem* item, GdkEvent* event) { - gint ret = FALSE; - - switch (event->type) { - case GDK_BUTTON_PRESS: - { - // select the clicked item but do nothing else - Inkscape::Selection * const selection = sp_desktop_selection(this->desktop); - selection->clear(); - selection->add(item); - ret = TRUE; - break; - } - case GDK_BUTTON_RELEASE: - // TODO: do we need to catch this or can we pass it on to the parent handler? - ret = TRUE; - break; - default: - break; - } - - if (!ret) { - ret = PenTool::item_handler(item, event); - } - - return ret; -} - -bool LpeTool::root_handler(GdkEvent* event) { - Inkscape::Selection *selection = sp_desktop_selection (desktop); - - bool ret = false; - - if (sp_pen_context_has_waiting_LPE(this)) { - // quit when we are waiting for a LPE to be applied - //ret = ((ToolBaseClass *) sp_lpetool_context_parent_class)->root_handler(event_context, event); - return PenTool::root_handler(event); - } - - switch (event->type) { - case GDK_BUTTON_PRESS: - if (event->button.button == 1 && !this->space_panning) { - if (this->mode == Inkscape::LivePathEffect::INVALID_LPE) { - // don't do anything for now if we are inactive (except clearing the selection - // since this was a click into empty space) - selection->clear(); - desktop->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Choose a construction tool from the toolbar.")); - ret = true; - break; - } - - // save drag origin - this->xp = (gint) event->button.x; - this->yp = (gint) event->button.y; - this->within_tolerance = true; - - using namespace Inkscape::LivePathEffect; - - Inkscape::Preferences *prefs = Inkscape::Preferences::get(); - int mode = prefs->getInt("/tools/lpetool/mode"); - EffectType type = lpesubtools[mode].type; - - //bool over_stroke = lc->shape_editor->is_over_stroke(Geom::Point(event->button.x, event->button.y), true); - - sp_pen_context_wait_for_LPE_mouse_clicks(this, type, Inkscape::LivePathEffect::Effect::acceptsNumClicks(type)); - - // we pass the mouse click on to pen tool as the first click which it should collect - //ret = ((ToolBaseClass *) sp_lpetool_context_parent_class)->root_handler(event_context, event); - ret = PenTool::root_handler(event); - } - break; - - - case GDK_BUTTON_RELEASE: - { - /** - break; - **/ - } - - case GDK_KEY_PRESS: - /** - switch (get_group0_keyval (&event->key)) { - } - break; - **/ - - case GDK_KEY_RELEASE: - /** - switch (get_group0_keyval(&event->key)) { - case GDK_Control_L: - case GDK_Control_R: - dc->_message_context->clear(); - break; - default: - break; - } - **/ - - default: - break; - } - - if (!ret) { - ret = PenTool::root_handler(event); - } - - return ret; -} - -/* - * Finds the index in the list of geometric subtools corresponding to the given LPE type. - * Returns -1 if no subtool is found. - */ -int -lpetool_mode_to_index(Inkscape::LivePathEffect::EffectType const type) { - for (int i = 0; i < num_subtools; ++i) { - if (lpesubtools[i].type == type) { - return i; - } - } - return -1; -} - -/* - * Checks whether an item has a construction applied as LPE and if so returns the index in - * lpesubtools of this construction - */ -int lpetool_item_has_construction(LpeTool */*lc*/, SPItem *item) -{ - if (!SP_IS_LPE_ITEM(item)) { - return -1; - } - - Inkscape::LivePathEffect::Effect* lpe = SP_LPE_ITEM(item)->getCurrentLPE(); - if (!lpe) { - return -1; - } - return lpetool_mode_to_index(lpe->effectType()); -} - -/* - * Attempts to perform the construction of the given type (i.e., to apply the corresponding LPE) to - * a single selected item. Returns whether we succeeded. - */ -bool -lpetool_try_construction(LpeTool *lc, Inkscape::LivePathEffect::EffectType const type) -{ - Inkscape::Selection *selection = sp_desktop_selection(lc->desktop); - SPItem *item = selection->singleItem(); - - // TODO: should we check whether type represents a valid geometric construction? - if (item && SP_IS_LPE_ITEM(item) && Inkscape::LivePathEffect::Effect::acceptsNumClicks(type) == 0) { - Inkscape::LivePathEffect::Effect::createAndApply(type, sp_desktop_document(lc->desktop), item); - return true; - } - return false; -} - -void -lpetool_context_switch_mode(LpeTool *lc, Inkscape::LivePathEffect::EffectType const type) -{ - int index = lpetool_mode_to_index(type); - if (index != -1) { - lc->mode = type; - lc->desktop->setToolboxSelectOneValue ("lpetool_mode_action", index); - } else { - g_warning ("Invalid mode selected: %d", type); - return; - } -} - -void -lpetool_get_limiting_bbox_corners(SPDocument *document, Geom::Point &A, Geom::Point &B) { - Geom::Coord w = document->getWidth().value("px"); - Geom::Coord h = document->getHeight().value("px"); - Inkscape::Preferences *prefs = Inkscape::Preferences::get(); - - double ulx = prefs->getDouble("/tools/lpetool/bbox_upperleftx", 0); - double uly = prefs->getDouble("/tools/lpetool/bbox_upperlefty", 0); - double lrx = prefs->getDouble("/tools/lpetool/bbox_lowerrightx", w); - double lry = prefs->getDouble("/tools/lpetool/bbox_lowerrighty", h); - - A = Geom::Point(ulx, uly); - B = Geom::Point(lrx, lry); -} - -/* - * Reads the limiting bounding box from preferences and draws it on the screen - */ -// TODO: Note that currently the bbox is not user-settable; we simply use the page borders -void -lpetool_context_reset_limiting_bbox(LpeTool *lc) -{ - if (lc->canvas_bbox) { - sp_canvas_item_destroy(lc->canvas_bbox); - lc->canvas_bbox = NULL; - } - - Inkscape::Preferences *prefs = Inkscape::Preferences::get(); - if (!prefs->getBool("/tools/lpetool/show_bbox", true)) - return; - - SPDocument *document = sp_desktop_document(lc->desktop); - - Geom::Point A, B; - lpetool_get_limiting_bbox_corners(document, A, B); - Geom::Affine doc2dt(lc->desktop->doc2dt()); - A *= doc2dt; - B *= doc2dt; - - Geom::Rect rect(A, B); - SPCurve *curve = SPCurve::new_from_rect(rect); - - lc->canvas_bbox = sp_canvas_bpath_new (sp_desktop_controls(lc->desktop), curve); - sp_canvas_bpath_set_stroke(SP_CANVAS_BPATH(lc->canvas_bbox), 0x0000ffff, 0.8, SP_STROKE_LINEJOIN_MITER, SP_STROKE_LINECAP_BUTT, 5, 5); -} - -static void -set_pos_and_anchor(SPCanvasText *canvas_text, const Geom::Piecewise > &pwd2, - const double t, const double length, bool /*use_curvature*/ = false) -{ - using namespace Geom; - - Piecewise > pwd2_reparam = arc_length_parametrization(pwd2, 2 , 0.1); - double t_reparam = pwd2_reparam.cuts.back() * t; - Point pos = pwd2_reparam.valueAt(t_reparam); - Point dir = unit_vector(derivative(pwd2_reparam).valueAt(t_reparam)); - Point n = -rot90(dir); - double angle = Geom::angle_between(dir, Point(1,0)); - - sp_canvastext_set_coords(canvas_text, pos + n * length); - sp_canvastext_set_anchor_manually(canvas_text, std::sin(angle), -std::cos(angle)); -} - -void -lpetool_create_measuring_items(LpeTool *lc, Inkscape::Selection *selection) -{ - if (!selection) { - selection = sp_desktop_selection(lc->desktop); - } - Inkscape::Preferences *prefs = Inkscape::Preferences::get(); - bool show = prefs->getBool("/tools/lpetool/show_measuring_info", true); - - SPPath *path; - SPCurve *curve; - SPCanvasText *canvas_text; - SPCanvasGroup *tmpgrp = sp_desktop_tempgroup(lc->desktop); - gchar *arc_length; - double lengthval; - - for (GSList const *i = selection->itemList(); i != NULL; i = i->next) { - if (SP_IS_PATH(i->data)) { - path = SP_PATH(i->data); - curve = path->getCurve(); - Geom::Piecewise > pwd2 = paths_to_pw(curve->get_pathvector()); - canvas_text = (SPCanvasText *) sp_canvastext_new(tmpgrp, lc->desktop, Geom::Point(0,0), ""); - if (!show) - sp_canvas_item_hide(SP_CANVAS_ITEM(canvas_text)); - - Inkscape::Util::Unit const * unit = NULL; - if (prefs->getString("/tools/lpetool/unit").compare("")) { - unit = unit_table.getUnit(prefs->getString("/tools/lpetool/unit")); - } else { - unit = unit_table.getUnit("px"); - } - - lengthval = Geom::length(pwd2); - lengthval = Inkscape::Util::Quantity::convert(lengthval, "px", unit); - arc_length = g_strdup_printf("%.2f %s", lengthval, unit->abbr.c_str()); - sp_canvastext_set_text (canvas_text, arc_length); - set_pos_and_anchor(canvas_text, pwd2, 0.5, 10); - // TODO: must we free arc_length? - (*lc->measuring_items)[path] = SP_CANVAS_ITEM(canvas_text); - } - } -} - -void -lpetool_delete_measuring_items(LpeTool *lc) -{ - std::map::iterator i; - for (i = lc->measuring_items->begin(); i != lc->measuring_items->end(); ++i) { - sp_canvas_item_destroy(i->second); - } - lc->measuring_items->clear(); -} - -void -lpetool_update_measuring_items(LpeTool *lc) -{ - Inkscape::Preferences *prefs = Inkscape::Preferences::get(); - for ( std::map::iterator i = lc->measuring_items->begin(); - i != lc->measuring_items->end(); - ++i ) - { - SPPath *path = i->first; - SPCurve *curve = path->getCurve(); - Geom::Piecewise > pwd2 = Geom::paths_to_pw(curve->get_pathvector()); - Inkscape::Util::Unit const * unit = NULL; - if (prefs->getString("/tools/lpetool/unit").compare("")) { - unit = unit_table.getUnit(prefs->getString("/tools/lpetool/unit")); - } else { - unit = unit_table.getUnit("px"); - } - double lengthval = Geom::length(pwd2); - lengthval = Inkscape::Util::Quantity::convert(lengthval, "px", unit); - gchar *arc_length = g_strdup_printf("%.2f %s", lengthval, unit->abbr.c_str()); - sp_canvastext_set_text (SP_CANVASTEXT(i->second), arc_length); - set_pos_and_anchor(SP_CANVASTEXT(i->second), pwd2, 0.5, 10); - // TODO: must we free arc_length? - } -} - -void -lpetool_show_measuring_info(LpeTool *lc, bool show) -{ - std::map::iterator i; - for (i = lc->measuring_items->begin(); i != lc->measuring_items->end(); ++i) { - if (show) { - sp_canvas_item_show(i->second); - } else { - sp_canvas_item_hide(i->second); - } - } -} - -} -} -} - -/* - Local Variables: - mode:c++ - c-file-style:"stroustrup" - c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) - indent-tabs-mode:nil - fill-column:99 - End: -*/ -// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/lpe-tool-context.h b/src/lpe-tool-context.h deleted file mode 100644 index 8be416e9b..000000000 --- a/src/lpe-tool-context.h +++ /dev/null @@ -1,99 +0,0 @@ -#ifndef SP_LPETOOL_CONTEXT_H_SEEN -#define SP_LPETOOL_CONTEXT_H_SEEN - -/* - * LPEToolContext: a context for a generic tool composed of subtools that are given by LPEs - * - * Authors: - * Maximilian Albert - * - * Copyright (C) 1998 The Free Software Foundation - * Copyright (C) 1999-2002 authors - * Copyright (C) 2001-2002 Ximian, Inc. - * Copyright (C) 2008 Maximilian Albert - * - * Released under GNU GPL, read the file 'COPYING' for more information - */ - -#include "pen-context.h" - -#define SP_LPETOOL_CONTEXT(obj) (dynamic_cast((Inkscape::UI::Tools::ToolBase*)obj)) -#define SP_IS_LPETOOL_CONTEXT(obj) (dynamic_cast((const Inkscape::UI::Tools::ToolBase*)obj) != NULL) - -/* This is the list of subtools from which the toolbar of the LPETool is built automatically */ -extern const int num_subtools; - -struct SubtoolEntry { - Inkscape::LivePathEffect::EffectType type; - gchar const *icon_name; -}; - -extern SubtoolEntry lpesubtools[]; - -enum LPEToolState { - LPETOOL_STATE_PEN, - LPETOOL_STATE_NODE -}; - -namespace Inkscape { -class Selection; -} - -class ShapeEditor; - -namespace Inkscape { -namespace UI { -namespace Tools { - -class LpeTool : public PenTool { -public: - LpeTool(); - virtual ~LpeTool(); - - ShapeEditor* shape_editor; - SPCanvasItem *canvas_bbox; - Inkscape::LivePathEffect::EffectType mode; - - std::map *measuring_items; - - sigc::connection sel_changed_connection; - sigc::connection sel_modified_connection; - - static const std::string prefsPath; - - virtual const std::string& getPrefsPath(); - -protected: - virtual void setup(); - virtual void set(const Inkscape::Preferences::Entry& val); - virtual bool root_handler(GdkEvent* event); - virtual bool item_handler(SPItem* item, GdkEvent* event); -}; - -int lpetool_mode_to_index(Inkscape::LivePathEffect::EffectType const type); -int lpetool_item_has_construction(LpeTool *lc, SPItem *item); -bool lpetool_try_construction(LpeTool *lc, Inkscape::LivePathEffect::EffectType const type); -void lpetool_context_switch_mode(LpeTool *lc, Inkscape::LivePathEffect::EffectType const type); -void lpetool_get_limiting_bbox_corners(SPDocument *document, Geom::Point &A, Geom::Point &B); -void lpetool_context_reset_limiting_bbox(LpeTool *lc); -void lpetool_create_measuring_items(LpeTool *lc, Inkscape::Selection *selection = NULL); -void lpetool_delete_measuring_items(LpeTool *lc); -void lpetool_update_measuring_items(LpeTool *lc); -void lpetool_show_measuring_info(LpeTool *lc, bool show = true); - -} -} -} - -#endif // SP_LPETOOL_CONTEXT_H_SEEN - -/* - Local Variables: - mode:c++ - c-file-style:"stroustrup" - c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) - indent-tabs-mode:nil - fill-column:99 - End: -*/ -// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/measure-context.cpp b/src/measure-context.cpp deleted file mode 100644 index 11161dc8f..000000000 --- a/src/measure-context.cpp +++ /dev/null @@ -1,777 +0,0 @@ -/* - * Our nice measuring tool - * - * Authors: - * Felipe Correa da Silva Sanches - * Jon A. Cruz - * - * Copyright (C) 2011 Authors - * - * Released under GNU GPL, read the file 'COPYING' for more information - */ - - -#include -#include -#include "util/units.h" -#include "macros.h" -#include "display/curve.h" -#include "sp-shape.h" -#include "sp-text.h" -#include "sp-flowtext.h" -#include "text-editing.h" -#include "display/sp-ctrlline.h" -#include "display/sodipodi-ctrl.h" -#include "display/sp-canvas-item.h" -#include "display/sp-canvas-util.h" -#include "desktop.h" -#include "document.h" -#include "pixmaps/cursor-measure.xpm" -#include "preferences.h" -#include "inkscape.h" -#include "desktop-handles.h" -#include "measure-context.h" -#include "draw-context.h" -#include "display/canvas-text.h" -#include "path-chemistry.h" -#include "2geom/line.h" -#include <2geom/path-intersection.h> -#include <2geom/pathvector.h> -#include <2geom/crossing.h> -#include <2geom/angle.h> -#include "snap.h" -#include "sp-namedview.h" -#include "enums.h" -#include "ui/control-manager.h" - -using Inkscape::ControlManager; -using Inkscape::CTLINE_SECONDARY; -using Inkscape::Util::unit_table; - -#include "tool-factory.h" - -namespace Inkscape { -namespace UI { -namespace Tools { - -std::vector measure_tmp_items; - -namespace { - ToolBase* createMeasureContext() { - return new MeasureTool(); - } - - bool measureContextRegistered = ToolFactory::instance().registerObject("/tools/measure", createMeasureContext); -} - -const std::string& MeasureTool::getPrefsPath() { - return MeasureTool::prefsPath; -} - -const std::string MeasureTool::prefsPath = "/tools/measure"; - -namespace -{ - -gint const DIMENSION_OFFSET = 35; - -/** - * Simple class to use for removing label overlap. - */ -class LabelPlacement { -public: - - double lengthVal; - double offset; - Geom::Point start; - Geom::Point end; -}; - -bool SortLabelPlacement(LabelPlacement const &first, LabelPlacement const &second) -{ - if (first.end[Geom::Y] == second.end[Geom::Y]) { - return first.end[Geom::X] < second.end[Geom::X]; - } else { - return first.end[Geom::Y] < second.end[Geom::Y]; - } -} - -void repositionOverlappingLabels(std::vector &placements, SPDesktop *desktop, Geom::Point const &normal, double fontsize) -{ - std::sort(placements.begin(), placements.end(), SortLabelPlacement); - - double border = 3; - Geom::Rect box; - { - Geom::Point tmp(fontsize * 8 + (border * 2), fontsize + (border * 2)); - tmp = desktop->w2d(tmp); - box = Geom::Rect(-tmp[Geom::X] / 2, -tmp[Geom::Y] / 2, tmp[Geom::X] / 2, tmp[Geom::Y] / 2); - } - - // Using index since vector may be re-ordered as we go. - // Starting at one, since the first item can't overlap itself - for (size_t i = 1; i < placements.size(); i++) { - LabelPlacement &place = placements[i]; - - bool changed = false; - do { - Geom::Rect current(box + place.end); - - changed = false; - bool overlaps = false; - for (size_t j = i; (j > 0) && !overlaps; --j) { - LabelPlacement &otherPlace = placements[j - 1]; - Geom::Rect target(box + otherPlace.end); - if (current.intersects(target)) { - overlaps = true; - } - } - if (overlaps) { - place.offset += (fontsize + border); - place.end = place.start - desktop->w2d(normal * place.offset); - changed = true; - } - } while (changed); - - std::sort(placements.begin(), placements.begin() + i + 1, SortLabelPlacement); - } -} - -/** - * Calculates where to place the anchor for the display text and arc. - * - * @param desktop the desktop that is being used. - * @param angle the angle to be displaying. - * @param baseAngle the angle of the initial baseline. - * @param startPoint the point that is the vertex of the selected angle. - * @param endPoint the point that is the end the user is manipulating for measurement. - * @param fontsize the size to display the text label at. - */ -Geom::Point calcAngleDisplayAnchor(SPDesktop *desktop, double angle, double baseAngle, - Geom::Point const &startPoint, Geom::Point const &endPoint, - double fontsize) -{ - // Time for the trick work of figuring out where things should go, and how. - double lengthVal = (endPoint - startPoint).length(); - double effective = baseAngle + (angle / 2); - Geom::Point where(lengthVal, 0); - where *= Geom::Affine(Geom::Rotate(effective)) * Geom::Affine(Geom::Translate(startPoint)); - - // When the angle is tight, the label would end up under the cursor and/or lines. Bump it - double scaledFontsize = std::abs(fontsize * desktop->w2d(Geom::Point(0, 1.0))[Geom::Y]); - if (std::abs((where - endPoint).length()) < scaledFontsize) { - where[Geom::Y] += scaledFontsize * 2; - } - - // We now have the ideal position, but need to see if it will fit/work. - - Geom::Rect visibleArea = desktop->get_display_area(); - // Bring it in to "title safe" for the anchor point - Geom::Point textBox = desktop->w2d(Geom::Point(fontsize * 3, fontsize / 2)); - textBox[Geom::Y] = std::abs(textBox[Geom::Y]); - - visibleArea = Geom::Rect(visibleArea.min()[Geom::X] + textBox[Geom::X], - visibleArea.min()[Geom::Y] + textBox[Geom::Y], - visibleArea.max()[Geom::X] - textBox[Geom::X], - visibleArea.max()[Geom::Y] - textBox[Geom::Y]); - - where[Geom::X] = std::min(where[Geom::X], visibleArea.max()[Geom::X]); - where[Geom::X] = std::max(where[Geom::X], visibleArea.min()[Geom::X]); - where[Geom::Y] = std::min(where[Geom::Y], visibleArea.max()[Geom::Y]); - where[Geom::Y] = std::max(where[Geom::Y], visibleArea.min()[Geom::Y]); - - return where; -} - -/** - * Given an angle, the arc center and edge point, draw an arc segment centered around that edge point. - * - * @param desktop the desktop that is being used. - * @param center the center point for the arc. - * @param end the point that ends at the edge of the arc segment. - * @param anchor the anchor point for displaying the text label. - * @param angle the angle of the arc segment to draw. - */ -void createAngleDisplayCurve(SPDesktop *desktop, Geom::Point const ¢er, Geom::Point const &end, Geom::Point const &anchor, double angle) -{ - // Given that we have a point on the arc's edge and the angle of the arc, we need to get the two endpoints. - - double textLen = std::abs((anchor - center).length()); - double sideLen = std::abs((end - center).length()); - if (sideLen > 0.0) { - double factor = std::min(1.0, textLen / sideLen); - - // arc start - Geom::Point p1 = end * (Geom::Affine(Geom::Translate(-center)) - * Geom::Affine(Geom::Scale(factor)) - * Geom::Affine(Geom::Translate(center))); - - // arc end - Geom::Point p4 = p1 * (Geom::Affine(Geom::Translate(-center)) - * Geom::Affine(Geom::Rotate(-angle)) - * Geom::Affine(Geom::Translate(center))); - - // from Riskus - double xc = center[Geom::X]; - double yc = center[Geom::Y]; - double ax = p1[Geom::X] - xc; - double ay = p1[Geom::Y] - yc; - double bx = p4[Geom::X] - xc; - double by = p4[Geom::Y] - yc; - double q1 = (ax * ax) + (ay * ay); - double q2 = q1 + (ax * bx) + (ay * by); - - double k2 = (4.0 / 3.0) * (std::sqrt(2 * q1 * q2) - q2) / ((ax * by) - (ay * bx)); - - Geom::Point p2(xc + ax - (k2 * ay), - yc + ay + (k2 * ax)); - Geom::Point p3(xc + bx + (k2 * by), - yc + by - (k2 * bx)); - SPCtrlCurve *curve = ControlManager::getManager().createControlCurve(sp_desktop_tempgroup(desktop), p1, p2, p3, p4, CTLINE_SECONDARY); - - measure_tmp_items.push_back(desktop->add_temporary_canvasitem(SP_CANVAS_ITEM(curve), 0, true)); - } -} - -} // namespace - - -MeasureTool::MeasureTool() : ToolBase() { - this->grabbed = 0; - - this->cursor_shape = cursor_measure_xpm; - this->hot_x = 4; - this->hot_y = 4; -} - -MeasureTool::~MeasureTool() { -} - -void MeasureTool::finish() { - this->enableGrDrag(false); - - if (this->grabbed) { - sp_canvas_item_ungrab(this->grabbed, GDK_CURRENT_TIME); - this->grabbed = NULL; - } -} - -//void MeasureTool::setup() { -// ToolBase* ec = this; -// -//// if (SP_EVENT_CONTEXT_CLASS(sp_measure_context_parent_class)->setup) { -//// SP_EVENT_CONTEXT_CLASS(sp_measure_context_parent_class)->setup(ec); -//// } -// ToolBase::setup(); -//} - -//gint MeasureTool::item_handler(SPItem* item, GdkEvent* event) { -// gint ret = FALSE; -// -//// if (SP_EVENT_CONTEXT_CLASS(sp_measure_context_parent_class)->item_handler) { -//// ret = SP_EVENT_CONTEXT_CLASS(sp_measure_context_parent_class)->item_handler(event_context, item, event); -//// } -// ret = ToolBase::item_handler(item, event); -// -// return ret; -//} - -static bool GeomPointSortPredicate(const Geom::Point& p1, const Geom::Point& p2) -{ - if (p1[Geom::Y] == p2[Geom::Y]) { - return p1[Geom::X] < p2[Geom::X]; - } else { - return p1[Geom::Y] < p2[Geom::Y]; - } -} - -static void calculate_intersections(SPDesktop * /*desktop*/, SPItem* item, Geom::PathVector const &lineseg, SPCurve *curve, std::vector &intersections) -{ - curve->transform(item->i2doc_affine()); - - // Find all intersections of the control-line with this shape - Geom::CrossingSet cs = Geom::crossings(lineseg, curve->get_pathvector()); - - // Reconstruct and store the points of intersection - for (Geom::Crossings::const_iterator m = cs[0].begin(); m != cs[0].end(); ++m) { -#if 0 -//TODO: consider only visible intersections - Geom::Point intersection = lineseg[0].pointAt((*m).ta); - double eps = 0.0001; - SPDocument* doc = sp_desktop_document(desktop); - if (((*m).ta > eps && - item == doc->getItemAtPoint(desktop->dkey, lineseg[0].pointAt((*m).ta - eps), false, NULL)) || - ((*m).ta + eps < 1 && - item == doc->getItemAtPoint(desktop->dkey, lineseg[0].pointAt((*m).ta + eps), false, NULL)) ) { - intersections.push_back(intersection); - } -#else - intersections.push_back(lineseg[0].pointAt((*m).ta)); -#endif - } -} - -bool MeasureTool::root_handler(GdkEvent* event) { - Inkscape::Preferences *prefs = Inkscape::Preferences::get(); - tolerance = prefs->getIntLimited("/options/dragtolerance/value", 0, 0, 100); - - gint ret = FALSE; - - switch (event->type) { - case GDK_BUTTON_PRESS: { - Geom::Point const button_w(event->button.x, event->button.y); - explicitBase = boost::none; - lastEnd = boost::none; - start_point = desktop->w2d(button_w); - - if (event->button.button == 1 && !this->space_panning) { - // save drag origin - xp = static_cast(event->button.x); - yp = static_cast(event->button.y); - within_tolerance = true; - - ret = TRUE; - } - - SnapManager &m = desktop->namedview->snap_manager; - m.setup(desktop); - m.freeSnapReturnByRef(start_point, Inkscape::SNAPSOURCE_OTHER_HANDLE); - m.unSetup(); - - sp_canvas_item_grab(SP_CANVAS_ITEM(desktop->acetate), - GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK | GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK, - NULL, event->button.time); - this->grabbed = SP_CANVAS_ITEM(desktop->acetate); - break; - } - case GDK_KEY_PRESS: { - if ((event->key.keyval == GDK_KEY_Shift_L) || (event->key.keyval == GDK_KEY_Shift_R)) { - if (lastEnd) { - explicitBase = lastEnd; - } - } - break; - } - case GDK_MOTION_NOTIFY: { - if (!((event->motion.state & GDK_BUTTON1_MASK) && !this->space_panning)) { - if (!(event->motion.state & GDK_SHIFT_MASK)) { - Geom::Point const motion_w(event->motion.x, event->motion.y); - Geom::Point const motion_dt(desktop->w2d(motion_w)); - - SnapManager &m = desktop->namedview->snap_manager; - m.setup(desktop); - - Inkscape::SnapCandidatePoint scp(motion_dt, Inkscape::SNAPSOURCE_OTHER_HANDLE); - scp.addOrigin(start_point); - - m.preSnap(scp); - m.unSetup(); - } - } else { - ret = TRUE; - - if ( within_tolerance - && ( abs( static_cast(event->motion.x) - xp ) < tolerance ) - && ( abs( static_cast(event->motion.y) - yp ) < tolerance ) ) { - break; // do not drag if we're within tolerance from origin - } - // Once the user has moved farther than tolerance from the original location - // (indicating they intend to move the object, not click), then always process the - // motion notify coordinates as given (no snapping back to origin) - within_tolerance = false; - - //clear previous temporary canvas items, we'll draw new ones - for (size_t idx = 0; idx < measure_tmp_items.size(); ++idx) { - desktop->remove_temporary_canvasitem(measure_tmp_items[idx]); - } - - measure_tmp_items.clear(); - - Geom::Point const motion_w(event->motion.x, event->motion.y); - Geom::Point const motion_dt(desktop->w2d(motion_w)); - Geom::Point end_point = motion_dt; - - if (event->motion.state & GDK_CONTROL_MASK) { - spdc_endpoint_snap_rotation(this, end_point, start_point, event->motion.state); - } else { - if (!(event->motion.state & GDK_SHIFT_MASK)) { - SnapManager &m = desktop->namedview->snap_manager; - m.setup(desktop); - Inkscape::SnapCandidatePoint scp(end_point, Inkscape::SNAPSOURCE_OTHER_HANDLE); - scp.addOrigin(start_point); - Inkscape::SnappedPoint sp = m.freeSnap(scp); - end_point = sp.getPoint(); - m.unSetup(); - } - } - - Geom::PathVector lineseg; - Geom::Path p; - p.start(desktop->dt2doc(start_point)); - p.appendNew(desktop->dt2doc(end_point)); - lineseg.push_back(p); - - double deltax = end_point[Geom::X] - start_point[Geom::X]; - double deltay = end_point[Geom::Y] - start_point[Geom::Y]; - double angle = atan2(deltay, deltax); - double baseAngle = 0; - - if (explicitBase) { - double deltax2 = explicitBase.get()[Geom::X] - start_point[Geom::X]; - double deltay2 = explicitBase.get()[Geom::Y] - start_point[Geom::Y]; - - baseAngle = atan2(deltay2, deltax2); - angle -= baseAngle; - - if (angle < -M_PI) { - angle += 2 * M_PI; - } else if (angle > M_PI) { - angle -= 2 * M_PI; - } - } - -//TODO: calculate NPOINTS -//800 seems to be a good value for 800x600 resolution -#define NPOINTS 800 - - std::vector points; - - for (double i = 0; i < NPOINTS; i++) { - points.push_back(desktop->d2w(start_point + (i / NPOINTS) * (end_point - start_point))); - } - -// TODO: Felipe, why don't you simply iterate over all items, and test whether their bounding boxes intersect -// with the measurement line, instead of interpolating? E.g. bbox_of_measurement_line.intersects(*bbox_of_item). -// That's also how the object-snapper works, see _findCandidates() in object-snapper.cpp. - - //select elements crossed by line segment: - GSList *items = sp_desktop_document(desktop)->getItemsAtPoints(desktop->dkey, points); - std::vector intersections; - Inkscape::Preferences *prefs = Inkscape::Preferences::get(); - bool ignore_1st_and_last = prefs->getBool("/tools/measure/ignore_1st_and_last", true); - - if (!ignore_1st_and_last) { - intersections.push_back(desktop->dt2doc(start_point)); - } - - std::vector placements; - - // TODO switch to a different variable name. The single letter 'l' is easy to misread. - for (GSList *l = items; l != NULL; l = l->next) { - SPItem *item = static_cast(l->data); - - if (SP_IS_SHAPE(item)) { - calculate_intersections(desktop, item, lineseg, SP_SHAPE(item)->getCurve(), intersections); - } else { - if (SP_IS_TEXT(item) || SP_IS_FLOWTEXT(item)) { - Inkscape::Text::Layout::iterator iter = te_get_layout(item)->begin(); - do { - Inkscape::Text::Layout::iterator iter_next = iter; - iter_next.nextGlyph(); // iter_next is one glyph ahead from iter - if (iter == iter_next) { - break; - } - - // get path from iter to iter_next: - SPCurve *curve = te_get_layout(item)->convertToCurves(iter, iter_next); - iter = iter_next; // shift to next glyph - if (!curve) { - continue; // error converting this glyph - } - if (curve->is_empty()) { // whitespace glyph? - curve->unref(); - continue; - } - - curve->transform(item->i2doc_affine()); - - calculate_intersections(desktop, item, lineseg, curve, intersections); - - if (iter == te_get_layout(item)->end()) { - break; - } - } while (true); - } - } - } - - if (!ignore_1st_and_last) { - intersections.push_back(desktop->dt2doc(end_point)); - } - - //sort intersections - if (intersections.size() > 2) { - std::sort(intersections.begin(), intersections.end(), GeomPointSortPredicate); - } - - Glib::ustring unit_name = prefs->getString("/tools/measure/unit"); - if (!unit_name.compare("")) { - unit_name = "px"; - } - - double fontsize = prefs->getInt("/tools/measure/fontsize"); - - // Normal will be used for lines and text - Geom::Point windowNormal = Geom::unit_vector(Geom::rot90(desktop->d2w(end_point - start_point))); - Geom::Point normal = desktop->w2d(windowNormal); - - for (size_t idx = 1; idx < intersections.size(); ++idx) { - LabelPlacement placement; - placement.lengthVal = (intersections[idx] - intersections[idx - 1]).length(); - placement.lengthVal = Inkscape::Util::Quantity::convert(placement.lengthVal, "px", unit_name); - placement.offset = DIMENSION_OFFSET; - placement.start = desktop->doc2dt( (intersections[idx - 1] + intersections[idx]) / 2 ); - placement.end = placement.start - (normal * placement.offset); - - placements.push_back(placement); - } - - // Adjust positions - repositionOverlappingLabels(placements, desktop, windowNormal, fontsize); - - for (std::vector::iterator it = placements.begin(); it != placements.end(); ++it) - { - LabelPlacement &place = *it; - - // TODO cleanup memory, Glib::ustring, etc.: - gchar *measure_str = g_strdup_printf("%.2f %s", place.lengthVal, unit_name.c_str()); - SPCanvasText *canvas_tooltip = sp_canvastext_new(sp_desktop_tempgroup(desktop), - desktop, - place.end, - measure_str); - sp_canvastext_set_fontsize(canvas_tooltip, fontsize); - canvas_tooltip->rgba = 0xffffffff; - canvas_tooltip->rgba_background = 0x0000007f; - canvas_tooltip->outline = false; - canvas_tooltip->background = true; - canvas_tooltip->anchor_position = TEXT_ANCHOR_CENTER; - - measure_tmp_items.push_back(desktop->add_temporary_canvasitem(canvas_tooltip, 0)); - g_free(measure_str); - } - - Geom::Point angleDisplayPt = calcAngleDisplayAnchor(desktop, angle, baseAngle, - start_point, end_point, - fontsize); - - { - // TODO cleanup memory, Glib::ustring, etc.: - gchar *angle_str = g_strdup_printf("%.2f °", angle * 180/M_PI); - - SPCanvasText *canvas_tooltip = sp_canvastext_new(sp_desktop_tempgroup(desktop), - desktop, - angleDisplayPt, - angle_str); - sp_canvastext_set_fontsize(canvas_tooltip, fontsize); - canvas_tooltip->rgba = 0xffffffff; - canvas_tooltip->rgba_background = 0x337f337f; - canvas_tooltip->outline = false; - canvas_tooltip->background = true; - canvas_tooltip->anchor_position = TEXT_ANCHOR_CENTER; - - measure_tmp_items.push_back(desktop->add_temporary_canvasitem(canvas_tooltip, 0)); - g_free(angle_str); - } - - { - double totallengthval = (end_point - start_point).length(); - totallengthval = Inkscape::Util::Quantity::convert(totallengthval, "px", unit_name); - - // TODO cleanup memory, Glib::ustring, etc.: - gchar *totallength_str = g_strdup_printf("%.2f %s", totallengthval, unit_name.c_str()); - SPCanvasText *canvas_tooltip = sp_canvastext_new(sp_desktop_tempgroup(desktop), - desktop, - end_point + desktop->w2d(Geom::Point(3*fontsize, -fontsize)), - totallength_str); - sp_canvastext_set_fontsize(canvas_tooltip, fontsize); - canvas_tooltip->rgba = 0xffffffff; - canvas_tooltip->rgba_background = 0x3333337f; - canvas_tooltip->outline = false; - canvas_tooltip->background = true; - canvas_tooltip->anchor_position = TEXT_ANCHOR_LEFT; - - measure_tmp_items.push_back(desktop->add_temporary_canvasitem(canvas_tooltip, 0)); - g_free(totallength_str); - } - - if (intersections.size() > 2) { - double totallengthval = (intersections[intersections.size()-1] - intersections[0]).length(); - totallengthval = Inkscape::Util::Quantity::convert(totallengthval, "px", unit_name); - - // TODO cleanup memory, Glib::ustring, etc.: - gchar *total_str = g_strdup_printf("%.2f %s", totallengthval, unit_name.c_str()); - SPCanvasText *canvas_tooltip = sp_canvastext_new(sp_desktop_tempgroup(desktop), - desktop, - desktop->doc2dt((intersections[0] + intersections[intersections.size()-1])/2) + normal * 60, - total_str); - sp_canvastext_set_fontsize(canvas_tooltip, fontsize); - canvas_tooltip->rgba = 0xffffffff; - canvas_tooltip->rgba_background = 0x33337f7f; - canvas_tooltip->outline = false; - canvas_tooltip->background = true; - canvas_tooltip->anchor_position = TEXT_ANCHOR_CENTER; - - measure_tmp_items.push_back(desktop->add_temporary_canvasitem(canvas_tooltip, 0)); - g_free(total_str); - } - - // Now that text has been added, we can add lines and controls so that they go underneath - - for (size_t idx = 0; idx < intersections.size(); ++idx) { - // Display the intersection indicator (i.e. the cross) - SPCanvasItem * canvasitem = sp_canvas_item_new(sp_desktop_tempgroup(desktop), - SP_TYPE_CTRL, - "anchor", SP_ANCHOR_CENTER, - "size", 8.0, - "stroked", TRUE, - "stroke_color", 0xff0000ff, - "mode", SP_KNOT_MODE_XOR, - "shape", SP_KNOT_SHAPE_CROSS, - NULL ); - - SP_CTRL(canvasitem)->moveto(desktop->doc2dt(intersections[idx])); - measure_tmp_items.push_back(desktop->add_temporary_canvasitem(canvasitem, 0)); - } - - // Since adding goes to the bottom, do all lines last. - - // draw main control line - { - SPCtrlLine *control_line = ControlManager::getManager().createControlLine(sp_desktop_tempgroup(desktop), - start_point, - end_point); - measure_tmp_items.push_back(desktop->add_temporary_canvasitem(control_line, 0)); - - if ((end_point[Geom::X] != start_point[Geom::X]) && (end_point[Geom::Y] != start_point[Geom::Y])) { - double length = std::abs((end_point - start_point).length()); - Geom::Point anchorEnd = start_point; - anchorEnd[Geom::X] += length; - if (explicitBase) { - anchorEnd *= (Geom::Affine(Geom::Translate(-start_point)) - * Geom::Affine(Geom::Rotate(baseAngle)) - * Geom::Affine(Geom::Translate(start_point))); - } - - SPCtrlLine *control_line = ControlManager::getManager().createControlLine(sp_desktop_tempgroup(desktop), - start_point, - anchorEnd, - CTLINE_SECONDARY); - measure_tmp_items.push_back(desktop->add_temporary_canvasitem(control_line, 0)); - - createAngleDisplayCurve(desktop, start_point, end_point, angleDisplayPt, angle); - } - } - - if (intersections.size() > 2) { - ControlManager &mgr = ControlManager::getManager(); - SPCtrlLine *control_line = 0; - control_line = mgr.createControlLine(sp_desktop_tempgroup(desktop), - desktop->doc2dt(intersections[0]) + normal * 60, - desktop->doc2dt(intersections[intersections.size() - 1]) + normal * 60); - measure_tmp_items.push_back(desktop->add_temporary_canvasitem(control_line, 0)); - - control_line = mgr.createControlLine(sp_desktop_tempgroup(desktop), - desktop->doc2dt(intersections[0]), - desktop->doc2dt(intersections[0]) + normal * 65); - measure_tmp_items.push_back(desktop->add_temporary_canvasitem(control_line, 0)); - - control_line = mgr.createControlLine(sp_desktop_tempgroup(desktop), - desktop->doc2dt(intersections[intersections.size() - 1]), - desktop->doc2dt(intersections[intersections.size() - 1]) + normal * 65); - measure_tmp_items.push_back(desktop->add_temporary_canvasitem(control_line, 0)); - } - - // call-out lines - for (std::vector::iterator it = placements.begin(); it != placements.end(); ++it) - { - LabelPlacement &place = *it; - - ControlManager &mgr = ControlManager::getManager(); - SPCtrlLine *control_line = mgr.createControlLine(sp_desktop_tempgroup(desktop), - place.start, - place.end, - CTLINE_SECONDARY); - measure_tmp_items.push_back(desktop->add_temporary_canvasitem(control_line, 0)); - } - - { - for (size_t idx = 1; idx < intersections.size(); ++idx) { - Geom::Point measure_text_pos = (intersections[idx - 1] + intersections[idx]) / 2; - - ControlManager &mgr = ControlManager::getManager(); - SPCtrlLine *control_line = mgr.createControlLine(sp_desktop_tempgroup(desktop), - desktop->doc2dt(measure_text_pos), - desktop->doc2dt(measure_text_pos) - (normal * DIMENSION_OFFSET), - CTLINE_SECONDARY); - measure_tmp_items.push_back(desktop->add_temporary_canvasitem(control_line, 0)); - } - } - - // Initial point - { - SPCanvasItem * canvasitem = sp_canvas_item_new(sp_desktop_tempgroup(desktop), - SP_TYPE_CTRL, - "anchor", SP_ANCHOR_CENTER, - "size", 8.0, - "stroked", TRUE, - "stroke_color", 0xff0000ff, - "mode", SP_KNOT_MODE_XOR, - "shape", SP_KNOT_SHAPE_CROSS, - NULL ); - - SP_CTRL(canvasitem)->moveto(start_point); - measure_tmp_items.push_back(desktop->add_temporary_canvasitem(canvasitem, 0)); - } - - lastEnd = end_point; // track in case we get a anchoring key-press later - - gobble_motion_events(GDK_BUTTON1_MASK); - } - break; - } - case GDK_BUTTON_RELEASE: { - sp_event_context_discard_delayed_snap_event(this); - explicitBase = boost::none; - lastEnd = boost::none; - - //clear all temporary canvas items related to the measurement tool. - for (size_t idx = 0; idx < measure_tmp_items.size(); ++idx) { - desktop->remove_temporary_canvasitem(measure_tmp_items[idx]); - } - - measure_tmp_items.clear(); - - if (this->grabbed) { - sp_canvas_item_ungrab(this->grabbed, event->button.time); - this->grabbed = NULL; - } - - xp = 0; - yp = 0; - break; - } - default: - break; - } - - if (!ret) { - ret = ToolBase::root_handler(event); - } - - return ret; -} - -} -} -} - -/* - Local Variables: - mode:c++ - c-file-style:"stroustrup" - c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) - indent-tabs-mode:nil - fill-column:99 - End: -*/ -// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 : diff --git a/src/measure-context.h b/src/measure-context.h deleted file mode 100644 index 97f896e0b..000000000 --- a/src/measure-context.h +++ /dev/null @@ -1,50 +0,0 @@ -#ifndef SEEN_SP_MEASURING_CONTEXT_H -#define SEEN_SP_MEASURING_CONTEXT_H - -/* - * Our fine measuring tool - * - * Authors: - * Felipe Correa da Silva Sanches - * - * Copyright (C) 2011 Authors - * - * Released under GNU GPL, read the file 'COPYING' for more information - */ - -#include "event-context.h" -#include <2geom/point.h> -#include - -#define SP_MEASURE_CONTEXT(obj) (dynamic_cast((Inkscape::UI::Tools::ToolBase*)obj)) -#define SP_IS_MEASURE_CONTEXT(obj) (dynamic_cast((const Inkscape::UI::Tools::ToolBase*)obj) != NULL) - -namespace Inkscape { -namespace UI { -namespace Tools { - -class MeasureTool : public ToolBase { -public: - MeasureTool(); - virtual ~MeasureTool(); - - static const std::string prefsPath; - - virtual void finish(); - virtual bool root_handler(GdkEvent* event); - - virtual const std::string& getPrefsPath(); - -private: - SPCanvasItem* grabbed; - - Geom::Point start_point; - boost::optional explicitBase; - boost::optional lastEnd; -}; - -} -} -} - -#endif // SEEN_SP_MEASURING_CONTEXT_H diff --git a/src/mesh-context.cpp b/src/mesh-context.cpp deleted file mode 100644 index 6b5542e3f..000000000 --- a/src/mesh-context.cpp +++ /dev/null @@ -1,1017 +0,0 @@ -/* - * Mesh drawing and editing tool - * - * Authors: - * bulia byak - * Johan Engelen - * Abhishek Sharma - * Tavmjong Bah - * - * Copyright (C) 2012 Tavmjong Bah - * Copyright (C) 2007 Johan Engelen - * Copyright (C) 2005 Authors - * - * Released under GNU GPL, read the file 'COPYING' for more information - */ - -#ifdef HAVE_CONFIG_H -# include "config.h" -#endif - -//#define DEBUG_MESH - - -// Libraries -#include -#include - -// General -#include "desktop.h" -#include "desktop-handles.h" -#include "document.h" -#include "document-undo.h" -#include "macros.h" -#include "message-context.h" -#include "message-stack.h" -#include "preferences.h" -#include "rubberband.h" -#include "selection.h" -#include "snap.h" -#include "sp-namedview.h" -#include "verbs.h" - -// Gradient specific -#include "gradient-drag.h" -#include "gradient-chemistry.h" -#include "pixmaps/cursor-gradient.xpm" -#include "pixmaps/cursor-gradient-add.xpm" - -// Mesh specific -#include "mesh-context.h" -#include "sp-mesh-gradient.h" -#include "display/sp-ctrlcurve.h" - -using Inkscape::DocumentUndo; - -#include "tool-factory.h" - -namespace Inkscape { -namespace UI { -namespace Tools { - -static void sp_mesh_drag(MeshTool &rc, Geom::Point const pt, guint state, guint32 etime); - -namespace { - ToolBase* createMeshContext() { - return new MeshTool(); - } - - bool meshContextRegistered = ToolFactory::instance().registerObject("/tools/mesh", createMeshContext); -} - -const std::string& MeshTool::getPrefsPath() { - return MeshTool::prefsPath; -} - -const std::string MeshTool::prefsPath = "/tools/mesh"; - -MeshTool::MeshTool() : ToolBase() { - this->selcon = 0; - this->node_added = false; - this->subselcon = 0; - - this->cursor_addnode = false; - this->cursor_shape = cursor_gradient_xpm; - this->hot_x = 4; - this->hot_y = 4; - this->xp = 0; - this->yp = 0; - this->tolerance = 6; - this->within_tolerance = false; - this->item_to_select = NULL; -} - -MeshTool::~MeshTool() { - this->enableGrDrag(false); - - this->selcon->disconnect(); - delete this->selcon; - - this->subselcon->disconnect(); - delete this->subselcon; -} - -const gchar *ms_handle_descr [] = { - N_("Mesh gradient corner"), - N_("Mesh gradient handle"), - N_("Mesh gradient tensor") -}; - -void MeshTool::selection_changed(Inkscape::Selection* /*sel*/) { - GrDrag *drag = this->_grdrag; - Inkscape::Selection *selection = sp_desktop_selection(this->desktop); - - if (selection == NULL) { - return; - } - - guint n_obj = g_slist_length((GSList *) selection->itemList()); - - if (!drag->isNonEmpty() || selection->isEmpty()) { - return; - } - - guint n_tot = drag->numDraggers(); - guint n_sel = drag->numSelected(); - - //The use of ngettext in the following code is intentional even if the English singular form would never be used - if (n_sel == 1) { - if (drag->singleSelectedDraggerNumDraggables() == 1) { - gchar * message = g_strconcat( - //TRANSLATORS: %s will be substituted with the point name (see previous messages); This is part of a compound message - _("%s selected"), - //TRANSLATORS: Mind the space in front. This is part of a compound message - ngettext(" out of %d mesh handle"," out of %d mesh handles",n_tot), - ngettext(" on %d selected object"," on %d selected objects",n_obj),NULL); - this->message_context->setF(Inkscape::NORMAL_MESSAGE, - message,_(ms_handle_descr[drag->singleSelectedDraggerSingleDraggableType()]), n_tot, n_obj); - } else { - gchar * message = - g_strconcat( - //TRANSLATORS: This is a part of a compound message (out of two more indicating: grandint handle count & object count) - ngettext("One handle merging %d stop (drag with Shift to separate) selected", - "One handle merging %d stops (drag with Shift to separate) selected", - drag->singleSelectedDraggerNumDraggables()), - ngettext(" out of %d mesh handle"," out of %d mesh handles",n_tot), - ngettext(" on %d selected object"," on %d selected objects",n_obj),NULL); - this->message_context->setF(Inkscape::NORMAL_MESSAGE,message,drag->singleSelectedDraggerNumDraggables(), n_tot, n_obj); - } - } else if (n_sel > 1) { - //TRANSLATORS: The plural refers to number of selected mesh handles. This is part of a compound message (part two indicates selected object count) - gchar * message = - g_strconcat(ngettext("%d mesh handle selected out of %d","%d mesh handles selected out of %d",n_sel), - //TRANSLATORS: Mind the space in front. (Refers to gradient handles selected). This is part of a compound message - ngettext(" on %d selected object"," on %d selected objects",n_obj),NULL); - this->message_context->setF(Inkscape::NORMAL_MESSAGE,message, n_sel, n_tot, n_obj); - } else if (n_sel == 0) { - this->message_context->setF(Inkscape::NORMAL_MESSAGE, - //TRANSLATORS: The plural refers to number of selected objects - ngettext("No mesh handles selected out of %d on %d selected object", - "No mesh handles selected out of %d on %d selected objects",n_obj), n_tot, n_obj); - } - - // FIXME - // We need to update mesh gradient handles. - // Get gradient this drag belongs too.. - // std::cout << "mesh_selection_changed: selection: objects: " << n_obj << std::endl; - // GSList *itemList = (GSList *) selection->itemList(); - // while( itemList ) { - - // SPItem *item = SP_ITEM( itemList->data ); - // // std::cout << " item: " << SP_OBJECT(item)->getId() << std::endl; - - // SPStyle *style = item->style; - // if (style && (style->fill.isPaintserver())) { - - // SPPaintServer *server = item->style->getFillPaintServer(); - // if ( SP_IS_MESHGRADIENT(server) ) { - - // SPMeshGradient *mg = SP_MESHGRADIENT(server); - - // guint rows = 0;//mg->array.patches.size(); - // for ( guint i = 0; i < rows; ++i ) { - // guint columns = 0;//mg->array.patches[0].size(); - // for ( guint j = 0; j < columns; ++j ) { - // } - // } - // } - // } - // itemList = itemList->next; - // } - - // GList* dragger_ptr = drag->draggers; // Points to GrDragger class (group of GrDraggable) - // guint count = 0; - // while( dragger_ptr ) { - - // std::cout << "mesh_selection_changed: dragger: " << ++count << std::endl; - // GSList* draggable_ptr = ((GrDragger *) dragger_ptr->data)->draggables; - - // while( draggable_ptr ) { - - // std::cout << "mesh_selection_changed: draggable: " << draggable_ptr << std::endl; - // GrDraggable *draggable = (GrDraggable *) draggable_ptr->data; - - // gint point_type = draggable->point_type; - // gint point_i = draggable->point_i; - // bool fill_or_stroke = draggable->fill_or_stroke; - - // if( point_type == POINT_MG_CORNER ) { - - // //std::cout << "mesh_selection_changed: POINT_MG_CORNER: " << point_i << std::endl; - // // Now we must create or destroy corresponding handles. - - // if( g_list_find( drag->selected, dragger_ptr->data ) ) { - // //std::cout << "gradient_selection_changed: Selected: " << point_i << std::endl; - // // Which meshes does this point belong to? - - // } else { - // //std::cout << "mesh_selection_changed: Not Selected: " << point_i << std::endl; - // } - // } - - // draggable_ptr = draggable_ptr->next; - - // } - - // dragger_ptr = dragger_ptr->next; - // } -} - -void MeshTool::setup() { - ToolBase::setup(); - - Inkscape::Preferences *prefs = Inkscape::Preferences::get(); - if (prefs->getBool("/tools/mesh/selcue", true)) { - this->enableSelectionCue(); - } - - this->enableGrDrag(); - Inkscape::Selection *selection = sp_desktop_selection(this->desktop); - - this->selcon = new sigc::connection(selection->connectChanged( - sigc::mem_fun(this, &MeshTool::selection_changed) - )); - - this->subselcon = new sigc::connection(this->desktop->connectToolSubselectionChanged( - sigc::hide(sigc::bind( - sigc::mem_fun(*this, &MeshTool::selection_changed), - (Inkscape::Selection*)NULL) - ) - )); - - this->selection_changed(selection); -} - -void -sp_mesh_context_select_next (ToolBase *event_context) -{ - GrDrag *drag = event_context->_grdrag; - g_assert (drag); - - GrDragger *d = drag->select_next(); - - event_context->desktop->scroll_to_point(d->point, 1.0); -} - -void -sp_mesh_context_select_prev (ToolBase *event_context) -{ - GrDrag *drag = event_context->_grdrag; - g_assert (drag); - - GrDragger *d = drag->select_prev(); - - event_context->desktop->scroll_to_point(d->point, 1.0); -} - -/** -Returns true if mouse cursor over mesh edge. -*/ -static bool -sp_mesh_context_is_over_line (MeshTool *rc, SPItem *item, Geom::Point event_p) -{ - SPDesktop *desktop = SP_EVENT_CONTEXT (rc)->desktop; - - //Translate mouse point into proper coord system - rc->mousepoint_doc = desktop->w2d(event_p); - - SPCtrlCurve *curve = SP_CTRLCURVE(item); - Geom::BezierCurveN<3> b( curve->p0, curve->p1, curve->p2, curve->p3 ); - Geom::Coord coord = b.nearestPoint( rc->mousepoint_doc ); // Coord == double - Geom::Point nearest = b( coord ); - - double dist_screen = Geom::L2 (rc->mousepoint_doc - nearest) * desktop->current_zoom(); - - double tolerance = (double) SP_EVENT_CONTEXT(rc)->tolerance; - - bool close = (dist_screen < tolerance); - - return close; -} - - -/** -Split row/column near the mouse point. -*/ -static void sp_mesh_context_split_near_point(MeshTool *rc, SPItem *item, Geom::Point mouse_p, guint32 /*etime*/) -{ - -#ifdef DEBUG_MESH - std::cout << "sp_mesh_context_split_near_point: entrance: " << mouse_p << std::endl; -#endif - - // item is the selected item. mouse_p the location in doc coordinates of where to add the stop - - ToolBase *ec = SP_EVENT_CONTEXT(rc); - SPDesktop *desktop = SP_EVENT_CONTEXT (rc)->desktop; - - double tolerance = (double) ec->tolerance; - - ec->get_drag()->addStopNearPoint (item, mouse_p, tolerance/desktop->current_zoom()); - - DocumentUndo::done(sp_desktop_document (desktop), SP_VERB_CONTEXT_MESH, - _("Split mesh row/column")); - - ec->get_drag()->updateDraggers(); -} - -/** -Wrapper for various mesh operations that require a list of selected corner nodes. - */ -static void -sp_mesh_context_corner_operation (MeshTool *rc, MeshCornerOperation operation ) -{ - -#ifdef DEBUG_MESH - std::cout << "sp_mesh_corner_operation: entrance: " << operation << std::endl; -#endif - - SPDocument *doc = NULL; - GrDrag *drag = rc->_grdrag; - - std::map > points; - std::map items; - - // Get list of selected draggers for each mesh. - // For all selected draggers - for (GList *i = drag->selected; i != NULL; i = i->next) { - GrDragger *dragger = (GrDragger *) i->data; - // For all draggables of dragger - for (GSList const* j = dragger->draggables; j != NULL; j = j->next) { - GrDraggable *d = (GrDraggable *) j->data; - - // Only mesh corners - if( d->point_type != POINT_MG_CORNER ) continue; - - // Find the gradient - SPMeshGradient *gradient = SP_MESHGRADIENT( getGradient (d->item, d->fill_or_stroke) ); - - // Collect points together for same gradient - points[gradient].push_back( d->point_i ); - items[gradient] = d->item; - } - } - - // Loop over meshes. - for( std::map >::const_iterator iter = points.begin(); iter != points.end(); ++iter) { - SPMeshGradient *mg = SP_MESHGRADIENT( iter->first ); - if( iter->second.size() > 0 ) { - guint noperation = 0; - switch (operation) { - - case MG_CORNER_SIDE_TOGGLE: - // std::cout << "SIDE_TOGGLE" << std::endl; - noperation += mg->array.side_toggle( iter->second ); - break; - - case MG_CORNER_SIDE_ARC: - // std::cout << "SIDE_ARC" << std::endl; - noperation += mg->array.side_arc( iter->second ); - break; - - case MG_CORNER_TENSOR_TOGGLE: - // std::cout << "TENSOR_TOGGLE" << std::endl; - noperation += mg->array.tensor_toggle( iter->second ); - break; - - case MG_CORNER_COLOR_SMOOTH: - // std::cout << "COLOR_SMOOTH" << std::endl; - noperation += mg->array.color_smooth( iter->second ); - break; - - case MG_CORNER_COLOR_PICK: - // std::cout << "COLOR_PICK" << std::endl; - noperation += mg->array.color_pick( iter->second, items[iter->first] ); - break; - - default: - std::cout << "sp_mesh_corner_operation: unknown operation" << std::endl; - } - - if( noperation > 0 ) { - mg->array.write( mg ); - mg->requestModified(SP_OBJECT_MODIFIED_FLAG); - doc = mg->document; - - switch (operation) { - - case MG_CORNER_SIDE_TOGGLE: - DocumentUndo::done(doc, SP_VERB_CONTEXT_MESH, _("Toggled mesh path type.")); - break; - - case MG_CORNER_SIDE_ARC: - DocumentUndo::done(doc, SP_VERB_CONTEXT_MESH, _("Approximated arc for mesh side.")); - break; - - case MG_CORNER_TENSOR_TOGGLE: - DocumentUndo::done(doc, SP_VERB_CONTEXT_MESH, _("Toggled mesh tensors.")); - break; - - case MG_CORNER_COLOR_SMOOTH: - DocumentUndo::done(doc, SP_VERB_CONTEXT_MESH, _("Smoothed mesh corner color.")); - break; - - case MG_CORNER_COLOR_PICK: - DocumentUndo::done(doc, SP_VERB_CONTEXT_MESH, _("Picked mesh corner color.")); - break; - - default: - std::cout << "sp_mesh_corner_operation: unknown operation" << std::endl; - } - } - } - } - drag->updateDraggers(); - -} - - -/** -Handles all keyboard and mouse input for meshs. -*/ -bool MeshTool::root_handler(GdkEvent* event) { - static bool dragging; - - Inkscape::Selection *selection = sp_desktop_selection (desktop); - Inkscape::Preferences *prefs = Inkscape::Preferences::get(); - - this->tolerance = prefs->getIntLimited("/options/dragtolerance/value", 0, 0, 100); - double const nudge = prefs->getDoubleLimited("/options/nudgedistance/value", 2, 0, 1000, "px"); // in px - - GrDrag *drag = this->_grdrag; - g_assert (drag); - - gint ret = FALSE; - - switch (event->type) { - case GDK_2BUTTON_PRESS: - -#ifdef DEBUG_MESH - std::cout << "sp_mesh_context_root_handler: GDK_2BUTTON_PRESS" << std::endl; -#endif - - // Double click: - // If over a mesh line, divide mesh row/column - // If not over a line, create new gradients for selected objects. - - if ( event->button.button == 1 ) { - // Are we over a mesh line? - bool over_line = false; - SPCtrlCurve *line = NULL; - - if (drag->lines) { - for (GSList *l = drag->lines; (l != NULL) && (!over_line); l = l->next) { - line = (SPCtrlCurve*) l->data; - over_line |= sp_mesh_context_is_over_line (this, (SPItem*) line, Geom::Point(event->motion.x, event->motion.y)); - } - } - - if (over_line) { - // We take the first item in selection, because with doubleclick, the first click - // always resets selection to the single object under cursor - sp_mesh_context_split_near_point(this, SP_ITEM(selection->itemList()->data), this->mousepoint_doc, event->button.time); - } else { - // Create a new gradient with default coordinates. - for (GSList const* i = selection->itemList(); i != NULL; i = i->next) { - SPItem *item = SP_ITEM(i->data); - SPGradientType new_type = SP_GRADIENT_TYPE_MESH; - Inkscape::PaintTarget fsmode = (prefs->getInt("/tools/gradient/newfillorstroke", 1) != 0) ? Inkscape::FOR_FILL : Inkscape::FOR_STROKE; - -#ifdef DEBUG_MESH - std::cout << "sp_mesh_context_root_handler: creating new mesh on: " << (fsmode == Inkscape::FOR_FILL ? "Fill" : "Stroke") << std::endl; -#endif - SPGradient *vector = sp_gradient_vector_for_object(sp_desktop_document(desktop), desktop, item, fsmode); - - SPGradient *priv = sp_item_set_gradient(item, vector, new_type, fsmode); - sp_gradient_reset_to_userspace(priv, item); - } - - DocumentUndo::done(sp_desktop_document (desktop), SP_VERB_CONTEXT_MESH, - _("Create default mesh")); - } - - ret = TRUE; - } - break; - - case GDK_BUTTON_PRESS: - -#ifdef DEBUG_MESH - std::cout << "sp_mesh_context_root_handler: GDK_BUTTON_PRESS" << std::endl; -#endif - // Button down - // If Shift key down: do rubber band selection - // Else set origin for drag. A drag creates a new gradient if one does not exist - if ( event->button.button == 1 && !this->space_panning ) { - Geom::Point button_w(event->button.x, event->button.y); - - // save drag origin - this->xp = (gint) button_w[Geom::X]; - this->yp = (gint) button_w[Geom::Y]; - this->within_tolerance = true; - - dragging = true; - - Geom::Point button_dt = desktop->w2d(button_w); - if (event->button.state & GDK_SHIFT_MASK) { - Inkscape::Rubberband::get(desktop)->start(desktop, button_dt); - } else { - // remember clicked item, disregarding groups, honoring Alt; do nothing with Crtl to - // enable Ctrl+doubleclick of exactly the selected item(s) - if (!(event->button.state & GDK_CONTROL_MASK)) { - this->item_to_select = sp_event_context_find_item (desktop, button_w, event->button.state & GDK_MOD1_MASK, TRUE); - } - - if (!selection->isEmpty()) { - SnapManager &m = desktop->namedview->snap_manager; - m.setup(desktop); - m.freeSnapReturnByRef(button_dt, Inkscape::SNAPSOURCE_NODE_HANDLE); - m.unSetup(); - } - - this->origin = button_dt; - } - - ret = TRUE; - } - break; - - case GDK_MOTION_NOTIFY: - // Mouse move - if ( dragging && ( event->motion.state & GDK_BUTTON1_MASK ) && !this->space_panning ) { - -#ifdef DEBUG_MESH - std::cout << "sp_mesh_context_root_handler: GDK_MOTION_NOTIFY: Dragging" << std::endl; -#endif - if ( this->within_tolerance - && ( abs( (gint) event->motion.x - this->xp ) < this->tolerance ) - && ( abs( (gint) event->motion.y - this->yp ) < this->tolerance ) ) { - break; // do not drag if we're within tolerance from origin - } - // Once the user has moved farther than tolerance from the original location - // (indicating they intend to draw, not click), then always process the - // motion notify coordinates as given (no snapping back to origin) - this->within_tolerance = false; - - Geom::Point const motion_w(event->motion.x, - event->motion.y); - Geom::Point const motion_dt = this->desktop->w2d(motion_w); - - if (Inkscape::Rubberband::get(desktop)->is_started()) { - Inkscape::Rubberband::get(desktop)->move(motion_dt); - this->defaultMessageContext()->set(Inkscape::NORMAL_MESSAGE, _("Draw around handles to select them")); - } else { - // Create new gradient with coordinates determined by drag. - sp_mesh_drag(*this, motion_dt, event->motion.state, event->motion.time); - } - - gobble_motion_events(GDK_BUTTON1_MASK); - - ret = TRUE; - } else { - // Not dragging - - // Do snapping - if (!drag->mouseOver() && !selection->isEmpty()) { - SnapManager &m = desktop->namedview->snap_manager; - m.setup(desktop); - - Geom::Point const motion_w(event->motion.x, event->motion.y); - Geom::Point const motion_dt = this->desktop->w2d(motion_w); - - m.preSnap(Inkscape::SnapCandidatePoint(motion_dt, Inkscape::SNAPSOURCE_OTHER_HANDLE)); - m.unSetup(); - } - - // Highlight corner node corresponding to side or tensor node - if( drag->mouseOver() ) { - // MESH FIXME: Light up corresponding corner node corresponding to node we are over. - // See "pathflash" in ui/tools/node-tool.cpp for ideas. - // Use desktop->add_temporary_canvasitem( SPCanvasItem, milliseconds ); - } - - // Change cursor shape if over line - bool over_line = false; - - if (drag->lines) { - for (GSList *l = drag->lines; l != NULL; l = l->next) { - over_line |= sp_mesh_context_is_over_line (this, (SPItem*) l->data, Geom::Point(event->motion.x, event->motion.y)); - } - } - - if (this->cursor_addnode && !over_line) { - this->cursor_shape = cursor_gradient_xpm; - this->sp_event_context_update_cursor(); - this->cursor_addnode = false; - } else if (!this->cursor_addnode && over_line) { - this->cursor_shape = cursor_gradient_add_xpm; - this->sp_event_context_update_cursor(); - this->cursor_addnode = true; - } - } - break; - - case GDK_BUTTON_RELEASE: - -#ifdef DEBUG_MESH - std::cout << "sp_mesh_context_root_handler: GDK_BUTTON_RELEASE" << std::endl; -#endif - - this->xp = this->yp = 0; - - if ( event->button.button == 1 && !this->space_panning ) { - // Check if over line - bool over_line = false; - SPCtrlLine *line = NULL; - - if (drag->lines) { - for (GSList *l = drag->lines; (l != NULL) && (!over_line); l = l->next) { - line = (SPCtrlLine*) l->data; - over_line = sp_mesh_context_is_over_line (this, (SPItem*) line, Geom::Point(event->motion.x, event->motion.y)); - - if (over_line) { - break; - } - } - } - - if ( (event->button.state & GDK_CONTROL_MASK) && (event->button.state & GDK_MOD1_MASK ) ) { - if (over_line && line) { - sp_mesh_context_split_near_point(this, line->item, this->mousepoint_doc, 0); - ret = TRUE; - } - } else { - dragging = false; - - // unless clicked with Ctrl (to enable Ctrl+doubleclick). - if (event->button.state & GDK_CONTROL_MASK) { - ret = TRUE; - break; - } - - if (!this->within_tolerance) { - // we've been dragging, either do nothing (grdrag handles that), - // or rubberband-select if we have rubberband - Inkscape::Rubberband *r = Inkscape::Rubberband::get(desktop); - - if (r->is_started() && !this->within_tolerance) { - // this was a rubberband drag - if (r->getMode() == RUBBERBAND_MODE_RECT) { - Geom::OptRect const b = r->getRectangle(); - drag->selectRect(*b); - } - } - } else if (this->item_to_select) { - if (over_line && line) { - // Clicked on an existing mesh line, don't change selection. This stops - // possible change in selection during a double click with overlapping objects - } else { - // no dragging, select clicked item if any - if (event->button.state & GDK_SHIFT_MASK) { - selection->toggle(this->item_to_select); - } else { - selection->set(this->item_to_select); - } - } - } else { - // click in an empty space; do the same as Esc - if (drag->selected) { - drag->deselectAll(); - } else { - selection->clear(); - } - } - - this->item_to_select = NULL; - ret = TRUE; - } - - Inkscape::Rubberband::get(desktop)->stop(); - } - break; - - case GDK_KEY_PRESS: - -#ifdef DEBUG_MESH - std::cout << "sp_mesh_context_root_handler: GDK_KEY_PRESS" << std::endl; -#endif - - // FIXME: tip - switch (get_group0_keyval (&event->key)) { - case GDK_KEY_Alt_L: - case GDK_KEY_Alt_R: - case GDK_KEY_Control_L: - case GDK_KEY_Control_R: - case GDK_KEY_Shift_L: - case GDK_KEY_Shift_R: - case GDK_KEY_Meta_L: // Meta is when you press Shift+Alt (at least on my machine) - case GDK_KEY_Meta_R: - sp_event_show_modifier_tip (this->defaultMessageContext(), event, - _("FIXMECtrl: snap mesh angle"), - _("FIXMEShift: draw mesh around the starting point"), - NULL); - break; - - case GDK_KEY_A: - case GDK_KEY_a: - if (MOD__CTRL_ONLY(event) && drag->isNonEmpty()) { - drag->selectAll(); - ret = TRUE; - } - break; - - case GDK_KEY_Escape: - if (drag->selected) { - drag->deselectAll(); - } else { - selection->clear(); - } - - ret = TRUE; - //TODO: make dragging escapable by Esc - break; - - case GDK_KEY_Left: // move handle left - case GDK_KEY_KP_Left: - case GDK_KEY_KP_4: - if (!MOD__CTRL(event)) { // not ctrl - gint mul = 1 + gobble_key_events(get_group0_keyval(&event->key), 0); // with any mask - - if (MOD__ALT(event)) { // alt - if (MOD__SHIFT(event)) { - drag->selected_move_screen(mul*-10, 0); // shift - } else { - drag->selected_move_screen(mul*-1, 0); // no shift - } - } else { // no alt - if (MOD__SHIFT(event)) { - drag->selected_move(mul*-10*nudge, 0); // shift - } else { - drag->selected_move(mul*-nudge, 0); // no shift - } - } - - ret = TRUE; - } - break; - - case GDK_KEY_Up: // move handle up - case GDK_KEY_KP_Up: - case GDK_KEY_KP_8: - if (!MOD__CTRL(event)) { // not ctrl - gint mul = 1 + gobble_key_events(get_group0_keyval(&event->key), 0); // with any mask - - if (MOD__ALT(event)) { // alt - if (MOD__SHIFT(event)) { - drag->selected_move_screen(0, mul*10); // shift - } else { - drag->selected_move_screen(0, mul*1); // no shift - } - } else { // no alt - if (MOD__SHIFT(event)) { - drag->selected_move(0, mul*10*nudge); // shift - } else { - drag->selected_move(0, mul*nudge); // no shift - } - } - - ret = TRUE; - } - break; - - case GDK_KEY_Right: // move handle right - case GDK_KEY_KP_Right: - case GDK_KEY_KP_6: - if (!MOD__CTRL(event)) { // not ctrl - gint mul = 1 + gobble_key_events(get_group0_keyval(&event->key), 0); // with any mask - - if (MOD__ALT(event)) { // alt - if (MOD__SHIFT(event)) { - drag->selected_move_screen(mul*10, 0); // shift - } else { - drag->selected_move_screen(mul*1, 0); // no shift - } - } else { // no alt - if (MOD__SHIFT(event)) { - drag->selected_move(mul*10*nudge, 0); // shift - } else { - drag->selected_move(mul*nudge, 0); // no shift - } - } - - ret = TRUE; - } - break; - - case GDK_KEY_Down: // move handle down - case GDK_KEY_KP_Down: - case GDK_KEY_KP_2: - if (!MOD__CTRL(event)) { // not ctrl - gint mul = 1 + gobble_key_events(get_group0_keyval(&event->key), 0); // with any mask - - if (MOD__ALT(event)) { // alt - if (MOD__SHIFT(event)) { - drag->selected_move_screen(0, mul*-10); // shift - } else { - drag->selected_move_screen(0, mul*-1); // no shift - } - } else { // no alt - if (MOD__SHIFT(event)) { - drag->selected_move(0, mul*-10*nudge); // shift - } else { - drag->selected_move(0, mul*-nudge); // no shift - } - } - - ret = TRUE; - } - break; - - case GDK_KEY_Insert: - case GDK_KEY_KP_Insert: - // with any modifiers: - //sp_gradient_context_add_stops_between_selected_stops (rc); - std::cout << "Inserting stops between selected stops not implemented yet" << std::endl; - ret = TRUE; - break; - - case GDK_KEY_Delete: - case GDK_KEY_KP_Delete: - case GDK_KEY_BackSpace: - if ( drag->selected ) { - std::cout << "Deleting mesh stops not implemented yet" << std::endl; - ret = TRUE; - } - break; - - // Mesh Operations -------------------------------------------- - - case GDK_KEY_b: // Toggle mesh side between lineto and curveto. - case GDK_KEY_B: - if (MOD__ALT(event) && drag->isNonEmpty() && drag->hasSelection()) { - sp_mesh_context_corner_operation ( this, MG_CORNER_SIDE_TOGGLE ); - ret = TRUE; - } - break; - - case GDK_KEY_c: // Convert mesh side from generic Bezier to Bezier approximating arc, - case GDK_KEY_C: // preserving handle direction. - if (MOD__ALT(event) && drag->isNonEmpty() && drag->hasSelection()) { - sp_mesh_context_corner_operation ( this, MG_CORNER_SIDE_ARC ); - ret = TRUE; - } - break; - - case GDK_KEY_g: // Toggle mesh tensor points on/off - case GDK_KEY_G: - if (MOD__ALT(event) && drag->isNonEmpty() && drag->hasSelection()) { - sp_mesh_context_corner_operation ( this, MG_CORNER_TENSOR_TOGGLE ); - ret = TRUE; - } - break; - - case GDK_KEY_j: // Smooth corner color - case GDK_KEY_J: - if (MOD__ALT(event) && drag->isNonEmpty() && drag->hasSelection()) { - sp_mesh_context_corner_operation ( this, MG_CORNER_COLOR_SMOOTH ); - ret = TRUE; - } - break; - - case GDK_KEY_k: // Pick corner color - case GDK_KEY_K: - if (MOD__ALT(event) && drag->isNonEmpty() && drag->hasSelection()) { - sp_mesh_context_corner_operation ( this, MG_CORNER_COLOR_PICK ); - ret = TRUE; - } - break; - - default: - break; - } - - break; - - case GDK_KEY_RELEASE: - -#ifdef DEBUG_MESH - std::cout << "sp_mesh_context_root_handler: GDK_KEY_RELEASE" << std::endl; -#endif - switch (get_group0_keyval (&event->key)) { - case GDK_KEY_Alt_L: - case GDK_KEY_Alt_R: - case GDK_KEY_Control_L: - case GDK_KEY_Control_R: - case GDK_KEY_Shift_L: - case GDK_KEY_Shift_R: - case GDK_KEY_Meta_L: // Meta is when you press Shift+Alt - case GDK_KEY_Meta_R: - this->defaultMessageContext()->clear(); - break; - default: - break; - } - break; - default: - break; - } - - if (!ret) { - ret = ToolBase::root_handler(event); - } - - return ret; -} - -static void sp_mesh_drag(MeshTool &rc, Geom::Point const /*pt*/, guint /*state*/, guint32 /*etime*/) { - SPDesktop *desktop = SP_EVENT_CONTEXT(&rc)->desktop; - Inkscape::Selection *selection = sp_desktop_selection(desktop); - SPDocument *document = sp_desktop_document(desktop); - ToolBase *ec = SP_EVENT_CONTEXT(&rc); - - if (!selection->isEmpty()) { - - Inkscape::Preferences *prefs = Inkscape::Preferences::get(); - int type = SP_GRADIENT_TYPE_MESH; - Inkscape::PaintTarget fill_or_stroke = (prefs->getInt("/tools/gradient/newfillorstroke", 1) != 0) ? Inkscape::FOR_FILL : Inkscape::FOR_STROKE; - - SPGradient *vector; - if (ec->item_to_select) { - // pick color from the object where drag started - vector = sp_gradient_vector_for_object(document, desktop, ec->item_to_select, fill_or_stroke); - } else { - // Starting from empty space: - // Sort items so that the topmost comes last - GSList *items = g_slist_copy ((GSList *) selection->itemList()); - items = g_slist_sort(items, (GCompareFunc) sp_item_repr_compare_position); - // take topmost - vector = sp_gradient_vector_for_object(document, desktop, SP_ITEM(g_slist_last(items)->data), fill_or_stroke); - g_slist_free (items); - } - - // HACK: reset fill-opacity - that 0.75 is annoying; BUT remove this when we have an opacity slider for all tabs - SPCSSAttr *css = sp_repr_css_attr_new(); - sp_repr_css_set_property(css, "fill-opacity", "1.0"); - - for (GSList const *i = selection->itemList(); i != NULL; i = i->next) { - - //FIXME: see above - sp_repr_css_change_recursive(SP_OBJECT(i->data)->getRepr(), css, "style"); - - sp_item_set_gradient(SP_ITEM(i->data), vector, (SPGradientType) type, fill_or_stroke); - - // We don't need to do anything. Mesh is already sized appropriately. - - SP_OBJECT(i->data)->requestModified(SP_OBJECT_MODIFIED_FLAG); - } - // if (ec->_grdrag) { - // ec->_grdrag->updateDraggers(); - // // prevent regenerating draggers by selection modified signal, which sometimes - // // comes too late and thus destroys the knot which we will now grab: - // ec->_grdrag->local_change = true; - // // give the grab out-of-bounds values of xp/yp because we're already dragging - // // and therefore are already out of tolerance - // ec->_grdrag->grabKnot (SP_ITEM(selection->itemList()->data), - // type == SP_GRADIENT_TYPE_LINEAR? POINT_LG_END : POINT_RG_R1, - // -1, // ignore number (though it is always 1) - // fill_or_stroke, 99999, 99999, etime); - // } - // We did an undoable action, but SPDocumentUndo::done will be called by the knot when released - - // status text; we do not track coords because this branch is run once, not all the time - // during drag - int n_objects = g_slist_length((GSList *) selection->itemList()); - rc.message_context->setF(Inkscape::NORMAL_MESSAGE, - ngettext("Gradient for %d object; with Ctrl to snap angle", - "Gradient for %d objects; with Ctrl to snap angle", n_objects), - n_objects); - } else { - sp_desktop_message_stack(desktop)->flash(Inkscape::WARNING_MESSAGE, _("Select objects on which to create gradient.")); - } - -} - -} -} -} - -/* - Local Variables: - mode:c++ - c-file-style:"stroustrup" - c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) - indent-tabs-mode:nil - fill-column:99 - End: -*/ -// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/mesh-context.h b/src/mesh-context.h deleted file mode 100644 index a8c35e4d8..000000000 --- a/src/mesh-context.h +++ /dev/null @@ -1,77 +0,0 @@ -#ifndef SEEN_SP_MESH_CONTEXT_H -#define SEEN_SP_MESH_CONTEXT_H - -/* - * Mesh drawing and editing tool - * - * Authors: - * bulia byak - * Johan Engelen - * Jon A. Cruz - * - * Copyright (C) 2012 Tavmjong Bah - * Copyright (C) 2007 Johan Engelen - * Copyright (C) 2005,2010 Authors - * - * Released under GNU GPL - */ - -#include -#include -#include "event-context.h" - -#define SP_MESH_CONTEXT(obj) (dynamic_cast((Inkscape::UI::Tools::ToolBase*)obj)) -#define SP_IS_MESH_CONTEXT(obj) (dynamic_cast((const Inkscape::UI::Tools::ToolBase*)obj) != NULL) - -namespace Inkscape { -namespace UI { -namespace Tools { - -class MeshTool : public ToolBase { -public: - MeshTool(); - virtual ~MeshTool(); - - Geom::Point origin; - - bool cursor_addnode; - - bool node_added; - - Geom::Point mousepoint_doc; // stores mousepoint when over_line in doc coords - - sigc::connection *selcon; - sigc::connection *subselcon; - - static const std::string prefsPath; - - virtual void setup(); - virtual bool root_handler(GdkEvent* event); - - virtual const std::string& getPrefsPath(); - -private: - void selection_changed(Inkscape::Selection* sel); -}; - -void sp_mesh_context_select_next(ToolBase *event_context); -void sp_mesh_context_select_prev(ToolBase *event_context); - -} -} -} - -#endif // SEEN_SP_MESH_CONTEXT_H - - -/* - Local Variables: - mode:c++ - c-file-style:"stroustrup" - c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) - indent-tabs-mode:nil - fill-column:99 - End: -*/ -// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/pen-context.cpp b/src/pen-context.cpp deleted file mode 100644 index 726034fac..000000000 --- a/src/pen-context.cpp +++ /dev/null @@ -1,1404 +0,0 @@ -/** \file - * Pen event context implementation. - */ - -/* - * Authors: - * Lauris Kaplinski - * bulia byak - * Jon A. Cruz - * - * Copyright (C) 2000 Lauris Kaplinski - * Copyright (C) 2000-2001 Ximian, Inc. - * Copyright (C) 2002 Lauris Kaplinski - * Copyright (C) 2004 Monash University - * - * Released under GNU GPL, read the file 'COPYING' for more information - */ - -#include -#include -#include - -#include "pen-context.h" -#include "sp-namedview.h" -#include "desktop.h" -#include "desktop-handles.h" -#include "selection.h" -#include "selection-chemistry.h" -#include "draw-anchor.h" -#include "message-stack.h" -#include "message-context.h" -#include "preferences.h" -#include "sp-path.h" -#include "display/sp-canvas.h" -#include "display/curve.h" -#include "pixmaps/cursor-pen.xpm" -#include "display/canvas-bpath.h" -#include "display/sp-ctrlline.h" -#include "display/sodipodi-ctrl.h" -#include -#include "macros.h" -#include "context-fns.h" -#include "tools-switch.h" -#include "ui/control-manager.h" -#include "tool-factory.h" - -using Inkscape::ControlManager; - -namespace Inkscape { -namespace UI { -namespace Tools { - -static void spdc_pen_set_initial_point(PenTool *pc, Geom::Point const p); -static void spdc_pen_set_subsequent_point(PenTool *const pc, Geom::Point const p, bool statusbar, guint status = 0); -static void spdc_pen_set_ctrl(PenTool *pc, Geom::Point const p, guint state); -static void spdc_pen_finish_segment(PenTool *pc, Geom::Point p, guint state); - -static void spdc_pen_finish(PenTool *pc, gboolean closed); - -static gint pen_handle_button_press(PenTool *const pc, GdkEventButton const &bevent); -static gint pen_handle_motion_notify(PenTool *const pc, GdkEventMotion const &mevent); -static gint pen_handle_button_release(PenTool *const pc, GdkEventButton const &revent); -static gint pen_handle_2button_press(PenTool *const pc, GdkEventButton const &bevent); -static gint pen_handle_key_press(PenTool *const pc, GdkEvent *event); -static void spdc_reset_colors(PenTool *pc); - -static void pen_disable_events(PenTool *const pc); -static void pen_enable_events(PenTool *const pc); - -static Geom::Point pen_drag_origin_w(0, 0); -static bool pen_within_tolerance = false; - -static int pen_next_paraxial_direction(const PenTool *const pc, Geom::Point const &pt, Geom::Point const &origin, guint state); -static void pen_set_to_nearest_horiz_vert(const PenTool *const pc, Geom::Point &pt, guint const state, bool snap); - -static int pen_last_paraxial_dir = 0; // last used direction in horizontal/vertical mode; 0 = horizontal, 1 = vertical - -namespace { - ToolBase* createPenContext() { - return new PenTool(); - } - - bool penContextRegistered = ToolFactory::instance().registerObject("/tools/freehand/pen", createPenContext); -} - -const std::string& PenTool::getPrefsPath() { - return PenTool::prefsPath; -} - -const std::string PenTool::prefsPath = "/tools/freehand/pen"; - -PenTool::PenTool() : SPDrawContext() { - this->polylines_only = false; - this->polylines_paraxial = false; - this->expecting_clicks_for_LPE = 0; - - this->cursor_shape = cursor_pen_xpm; - this->hot_x = 4; - this->hot_y = 4; - - this->npoints = 0; - this->mode = MODE_CLICK; - this->state = POINT; - - this->c0 = NULL; - this->c1 = NULL; - this->cl0 = NULL; - this->cl1 = NULL; - - this->events_disabled = 0; - - this->num_clicks = 0; - this->waiting_LPE = NULL; - this->waiting_item = NULL; -} - -PenTool::~PenTool() { - if (this->c0) { - sp_canvas_item_destroy(this->c0); - this->c0 = NULL; - } - if (this->c1) { - sp_canvas_item_destroy(this->c1); - this->c1 = NULL; - } - if (this->cl0) { - sp_canvas_item_destroy(this->cl0); - this->cl0 = NULL; - } - if (this->cl1) { - sp_canvas_item_destroy(this->cl1); - this->cl1 = NULL; - } - - if (this->expecting_clicks_for_LPE > 0) { - // we received too few clicks to sanely set the parameter path so we remove the LPE from the item - this->waiting_item->removeCurrentPathEffect(false); - } -} - -void sp_pen_context_set_polyline_mode(PenTool *const pc) { - Inkscape::Preferences *prefs = Inkscape::Preferences::get(); - guint mode = prefs->getInt("/tools/freehand/pen/freehand-mode", 0); - pc->polylines_only = (mode == 2 || mode == 3); - pc->polylines_paraxial = (mode == 3); -} - -/** - * Callback to initialize PenTool object. - */ -void PenTool::setup() { - SPDrawContext::setup(); - - ControlManager &mgr = ControlManager::getManager(); - - // Pen indicators - this->c0 = mgr.createControl(sp_desktop_controls(SP_EVENT_CONTEXT_DESKTOP(this)), Inkscape::CTRL_TYPE_ADJ_HANDLE); - mgr.track(this->c0); - - this->c1 = mgr.createControl(sp_desktop_controls(SP_EVENT_CONTEXT_DESKTOP(this)), Inkscape::CTRL_TYPE_ADJ_HANDLE); - mgr.track(this->c1); - - this->cl0 = mgr.createControlLine(sp_desktop_controls(SP_EVENT_CONTEXT_DESKTOP(this))); - this->cl1 = mgr.createControlLine(sp_desktop_controls(SP_EVENT_CONTEXT_DESKTOP(this))); - - sp_canvas_item_hide(this->c0); - sp_canvas_item_hide(this->c1); - sp_canvas_item_hide(this->cl0); - sp_canvas_item_hide(this->cl1); - - sp_event_context_read(this, "mode"); - - this->anchor_statusbar = false; - - sp_pen_context_set_polyline_mode(this); - - Inkscape::Preferences *prefs = Inkscape::Preferences::get(); - if (prefs->getBool("/tools/freehand/pen/selcue")) { - this->enableSelectionCue(); - } -} - -static void pen_cancel (PenTool *const pc) -{ - pc->num_clicks = 0; - pc->state = PenTool::STOP; - spdc_reset_colors(pc); - sp_canvas_item_hide(pc->c0); - sp_canvas_item_hide(pc->c1); - sp_canvas_item_hide(pc->cl0); - sp_canvas_item_hide(pc->cl1); - pc->message_context->clear(); - pc->message_context->flash(Inkscape::NORMAL_MESSAGE, _("Drawing cancelled")); - - pc->desktop->canvas->endForcedFullRedraws(); -} - -/** - * Finalization callback. - */ -void PenTool::finish() { - sp_event_context_discard_delayed_snap_event(this); - - if (this->npoints != 0) { - pen_cancel(this); - } - - SPDrawContext::finish(); -} - -/** - * Callback that sets key to value in pen context. - */ -void PenTool::set(const Inkscape::Preferences::Entry& val) { - Glib::ustring name = val.getEntryName(); - - if (name == "mode") { - if ( val.getString() == "drag" ) { - this->mode = MODE_DRAG; - } else { - this->mode = MODE_CLICK; - } - } -} - -/** - * Snaps new node relative to the previous node. - */ -static void spdc_endpoint_snap(PenTool const *const pc, Geom::Point &p, guint const state) -{ - if ((state & GDK_CONTROL_MASK) && !pc->polylines_paraxial) { //CTRL enables angular snapping - if (pc->npoints > 0) { - spdc_endpoint_snap_rotation(pc, p, pc->p[0], state); - } - } else { - // We cannot use shift here to disable snapping because the shift-key is already used - // to toggle the paraxial direction; if the user wants to disable snapping (s)he will - // have to use the %-key, the menu, or the snap toolbar - if ((pc->npoints > 0) && pc->polylines_paraxial) { - // snap constrained - pen_set_to_nearest_horiz_vert(pc, p, state, true); - } else { - // snap freely - boost::optional origin = pc->npoints > 0 ? pc->p[0] : boost::optional(); - spdc_endpoint_snap_free(pc, p, origin, state); // pass the origin, to allow for perpendicular / tangential snapping - } - } -} - -/** - * Snaps new node's handle relative to the new node. - */ -static void spdc_endpoint_snap_handle(PenTool const *const pc, Geom::Point &p, guint const state) -{ - g_return_if_fail(( pc->npoints == 2 || - pc->npoints == 5 )); - - if ((state & GDK_CONTROL_MASK)) { //CTRL enables angular snapping - spdc_endpoint_snap_rotation(pc, p, pc->p[pc->npoints - 2], state); - } else { - if (!(state & GDK_SHIFT_MASK)) { //SHIFT disables all snapping, except the angular snapping above - boost::optional origin = pc->p[pc->npoints - 2]; - spdc_endpoint_snap_free(pc, p, origin, state); - } - } -} - -bool PenTool::item_handler(SPItem* item, GdkEvent* event) { - gint ret = FALSE; - - switch (event->type) { - case GDK_BUTTON_PRESS: - ret = pen_handle_button_press(this, event->button); - break; - case GDK_BUTTON_RELEASE: - ret = pen_handle_button_release(this, event->button); - break; - default: - break; - } - - if (!ret) { - ret = SPDrawContext::item_handler(item, event); - } - - return ret; -} - -/** - * Callback to handle all pen events. - */ -bool PenTool::root_handler(GdkEvent* event) { - gint ret = FALSE; - - switch (event->type) { - case GDK_BUTTON_PRESS: - ret = pen_handle_button_press(this, event->button); - break; - - case GDK_MOTION_NOTIFY: - ret = pen_handle_motion_notify(this, event->motion); - break; - - case GDK_BUTTON_RELEASE: - ret = pen_handle_button_release(this, event->button); - break; - - case GDK_2BUTTON_PRESS: - ret = pen_handle_2button_press(this, event->button); - break; - - case GDK_KEY_PRESS: - ret = pen_handle_key_press(this, event); - break; - - default: - break; - } - - if (!ret) { - ret = SPDrawContext::root_handler(event); - } - - return ret; -} - -/** - * Handle mouse button press event. - */ -static gint pen_handle_button_press(PenTool *const pc, GdkEventButton const &bevent) -{ - if (pc->events_disabled) { - // skip event processing if events are disabled - return FALSE; - } - - SPDrawContext * const dc = SP_DRAW_CONTEXT(pc); - SPDesktop * const desktop = SP_EVENT_CONTEXT_DESKTOP(dc); - Geom::Point const event_w(bevent.x, bevent.y); - Geom::Point event_dt(desktop->w2d(event_w)); - ToolBase *event_context = SP_EVENT_CONTEXT(pc); - - gint ret = FALSE; - if (bevent.button == 1 && !event_context->space_panning - // make sure this is not the last click for a waiting LPE (otherwise we want to finish the path) - && pc->expecting_clicks_for_LPE != 1) { - - if (Inkscape::have_viable_layer(desktop, dc->message_context) == false) { - return TRUE; - } - - if (!pc->grab ) { - // Grab mouse, so release will not pass unnoticed - pc->grab = SP_CANVAS_ITEM(desktop->acetate); - sp_canvas_item_grab(pc->grab, ( GDK_KEY_PRESS_MASK | GDK_BUTTON_PRESS_MASK | - GDK_BUTTON_RELEASE_MASK | - GDK_POINTER_MOTION_MASK ), - NULL, bevent.time); - } - - pen_drag_origin_w = event_w; - pen_within_tolerance = true; - - // Test whether we hit any anchor. - SPDrawAnchor * const anchor = spdc_test_inside(pc, event_w); - - switch (pc->mode) { - case PenTool::MODE_CLICK: - // In click mode we add point on release - switch (pc->state) { - case PenTool::POINT: - case PenTool::CONTROL: - case PenTool::CLOSE: - break; - case PenTool::STOP: - // This is allowed, if we just canceled curve - pc->state = PenTool::POINT; - break; - default: - break; - } - break; - case PenTool::MODE_DRAG: - switch (pc->state) { - case PenTool::STOP: - // This is allowed, if we just canceled curve - case PenTool::POINT: - if (pc->npoints == 0) { - - Geom::Point p; - if ((bevent.state & GDK_CONTROL_MASK) && (pc->polylines_only || pc->polylines_paraxial)) { - p = event_dt; - if (!(bevent.state & GDK_SHIFT_MASK)) { - SnapManager &m = desktop->namedview->snap_manager; - m.setup(desktop); - m.freeSnapReturnByRef(p, Inkscape::SNAPSOURCE_NODE_HANDLE); - m.unSetup(); - } - spdc_create_single_dot(event_context, p, "/tools/freehand/pen", bevent.state); - ret = TRUE; - break; - } - - // TODO: Perhaps it would be nicer to rearrange the following case - // distinction so that the case of a waiting LPE is treated separately - - // Set start anchor - pc->sa = anchor; - if (anchor && !sp_pen_context_has_waiting_LPE(pc)) { - // Adjust point to anchor if needed; if we have a waiting LPE, we need - // a fresh path to be created so don't continue an existing one - p = anchor->dp; - desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("Continuing selected path")); - } else { - // This is the first click of a new curve; deselect item so that - // this curve is not combined with it (unless it is drawn from its - // anchor, which is handled by the sibling branch above) - Inkscape::Selection * const selection = sp_desktop_selection(desktop); - if (!(bevent.state & GDK_SHIFT_MASK) || sp_pen_context_has_waiting_LPE(pc)) { - // if we have a waiting LPE, we need a fresh path to be created - // so don't append to an existing one - selection->clear(); - desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("Creating new path")); - } else if (selection->singleItem() && SP_IS_PATH(selection->singleItem())) { - desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("Appending to selected path")); - } - - // Create green anchor - p = event_dt; - spdc_endpoint_snap(pc, p, bevent.state); - pc->green_anchor = sp_draw_anchor_new(pc, pc->green_curve, TRUE, p); - } - spdc_pen_set_initial_point(pc, p); - } else { - - // Set end anchor - pc->ea = anchor; - Geom::Point p; - if (anchor) { - p = anchor->dp; - // we hit an anchor, will finish the curve (either with or without closing) - // in release handler - pc->state = PenTool::CLOSE; - - if (pc->green_anchor && pc->green_anchor->active) { - // we clicked on the current curve start, so close it even if - // we drag a handle away from it - dc->green_closed = TRUE; - } - ret = TRUE; - break; - - } else { - p = event_dt; - spdc_endpoint_snap(pc, p, bevent.state); // Snap node only if not hitting anchor. - spdc_pen_set_subsequent_point(pc, p, true); - } - } - - pc->state = pc->polylines_only ? PenTool::POINT : PenTool::CONTROL; - ret = TRUE; - break; - case PenTool::CONTROL: - g_warning("Button down in CONTROL state"); - break; - case PenTool::CLOSE: - g_warning("Button down in CLOSE state"); - break; - default: - break; - } - break; - default: - break; - } - } else if (pc->expecting_clicks_for_LPE == 1 && pc->npoints != 0) { - // when the last click for a waiting LPE occurs we want to finish the path - spdc_pen_finish_segment(pc, event_dt, bevent.state); - if (pc->green_closed) { - // finishing at the start anchor, close curve - spdc_pen_finish(pc, TRUE); - } else { - // finishing at some other anchor, finish curve but not close - spdc_pen_finish(pc, FALSE); - } - - ret = TRUE; - } else if (bevent.button == 3 && pc->npoints != 0) { - // right click - finish path - spdc_pen_finish(pc, FALSE); - ret = TRUE; - } - - if (pc->expecting_clicks_for_LPE > 0) { - --pc->expecting_clicks_for_LPE; - } - - return ret; -} - -/** - * Handle motion_notify event. - */ -static gint pen_handle_motion_notify(PenTool *const pc, GdkEventMotion const &mevent) -{ - gint ret = FALSE; - - ToolBase *event_context = SP_EVENT_CONTEXT(pc); - SPDesktop * const dt = SP_EVENT_CONTEXT_DESKTOP(event_context); - - if (event_context->space_panning || mevent.state & GDK_BUTTON2_MASK || mevent.state & GDK_BUTTON3_MASK) { - // allow scrolling - return FALSE; - } - - if (pc->events_disabled) { - // skip motion events if pen events are disabled - return FALSE; - } - - Geom::Point const event_w(mevent.x, - mevent.y); - if (pen_within_tolerance) { - Inkscape::Preferences *prefs = Inkscape::Preferences::get(); - gint const tolerance = prefs->getIntLimited("/options/dragtolerance/value", 0, 0, 100); - if ( Geom::LInfty( event_w - pen_drag_origin_w ) < tolerance ) { - return FALSE; // Do not drag if we're within tolerance from origin. - } - } - // Once the user has moved farther than tolerance from the original location - // (indicating they intend to move the object, not click), then always process the - // motion notify coordinates as given (no snapping back to origin) - pen_within_tolerance = false; - - // Find desktop coordinates - Geom::Point p = dt->w2d(event_w); - - // Test, whether we hit any anchor - SPDrawAnchor *anchor = spdc_test_inside(pc, event_w); - - switch (pc->mode) { - case PenTool::MODE_CLICK: - switch (pc->state) { - case PenTool::POINT: - if ( pc->npoints != 0 ) { - // Only set point, if we are already appending - spdc_endpoint_snap(pc, p, mevent.state); - spdc_pen_set_subsequent_point(pc, p, true); - ret = TRUE; - } else if (!sp_event_context_knot_mouseover(pc)) { - SnapManager &m = dt->namedview->snap_manager; - m.setup(dt); - m.preSnap(Inkscape::SnapCandidatePoint(p, Inkscape::SNAPSOURCE_NODE_HANDLE)); - m.unSetup(); - } - break; - case PenTool::CONTROL: - case PenTool::CLOSE: - // Placing controls is last operation in CLOSE state - spdc_endpoint_snap(pc, p, mevent.state); - spdc_pen_set_ctrl(pc, p, mevent.state); - ret = TRUE; - break; - case PenTool::STOP: - // This is perfectly valid - break; - default: - break; - } - break; - case PenTool::MODE_DRAG: - switch (pc->state) { - case PenTool::POINT: - if ( pc->npoints > 0 ) { - // Only set point, if we are already appending - - if (!anchor) { // Snap node only if not hitting anchor - spdc_endpoint_snap(pc, p, mevent.state); - spdc_pen_set_subsequent_point(pc, p, true, mevent.state); - } else { - spdc_pen_set_subsequent_point(pc, anchor->dp, false, mevent.state); - } - - if (anchor && !pc->anchor_statusbar) { - pc->message_context->set(Inkscape::NORMAL_MESSAGE, _("Click or click and drag to close and finish the path.")); - pc->anchor_statusbar = true; - } else if (!anchor && pc->anchor_statusbar) { - pc->message_context->clear(); - pc->anchor_statusbar = false; - } - - ret = TRUE; - } else { - if (anchor && !pc->anchor_statusbar) { - pc->message_context->set(Inkscape::NORMAL_MESSAGE, _("Click or click and drag to continue the path from this point.")); - pc->anchor_statusbar = true; - } else if (!anchor && pc->anchor_statusbar) { - pc->message_context->clear(); - pc->anchor_statusbar = false; - } - if (!sp_event_context_knot_mouseover(pc)) { - SnapManager &m = dt->namedview->snap_manager; - m.setup(dt); - m.preSnap(Inkscape::SnapCandidatePoint(p, Inkscape::SNAPSOURCE_NODE_HANDLE)); - m.unSetup(); - } - } - break; - case PenTool::CONTROL: - case PenTool::CLOSE: - // Placing controls is last operation in CLOSE state - - // snap the handle - spdc_endpoint_snap_handle(pc, p, mevent.state); - - if (!pc->polylines_only) { - spdc_pen_set_ctrl(pc, p, mevent.state); - } else { - spdc_pen_set_ctrl(pc, pc->p[1], mevent.state); - } - gobble_motion_events(GDK_BUTTON1_MASK); - ret = TRUE; - break; - case PenTool::STOP: - // This is perfectly valid - break; - default: - if (!sp_event_context_knot_mouseover(pc)) { - SnapManager &m = dt->namedview->snap_manager; - m.setup(dt); - m.preSnap(Inkscape::SnapCandidatePoint(p, Inkscape::SNAPSOURCE_NODE_HANDLE)); - m.unSetup(); - } - break; - } - break; - default: - break; - } - return ret; -} - -/** - * Handle mouse button release event. - */ -static gint pen_handle_button_release(PenTool *const pc, GdkEventButton const &revent) -{ - if (pc->events_disabled) { - // skip event processing if events are disabled - return FALSE; - } - - gint ret = FALSE; - ToolBase *event_context = SP_EVENT_CONTEXT(pc); - if ( revent.button == 1 && !event_context->space_panning) { - - SPDrawContext *dc = SP_DRAW_CONTEXT (pc); - - Geom::Point const event_w(revent.x, - revent.y); - // Find desktop coordinates - Geom::Point p = pc->desktop->w2d(event_w); - - // Test whether we hit any anchor. - SPDrawAnchor *anchor = spdc_test_inside(pc, event_w); - - switch (pc->mode) { - case PenTool::MODE_CLICK: - switch (pc->state) { - case PenTool::POINT: - if ( pc->npoints == 0 ) { - // Start new thread only with button release - if (anchor) { - p = anchor->dp; - } - pc->sa = anchor; - spdc_pen_set_initial_point(pc, p); - } else { - // Set end anchor here - pc->ea = anchor; - if (anchor) { - p = anchor->dp; - } - } - pc->state = PenTool::CONTROL; - ret = TRUE; - break; - case PenTool::CONTROL: - // End current segment - spdc_endpoint_snap(pc, p, revent.state); - spdc_pen_finish_segment(pc, p, revent.state); - pc->state = PenTool::POINT; - ret = TRUE; - break; - case PenTool::CLOSE: - // End current segment - if (!anchor) { // Snap node only if not hitting anchor - spdc_endpoint_snap(pc, p, revent.state); - } - spdc_pen_finish_segment(pc, p, revent.state); - spdc_pen_finish(pc, TRUE); - pc->state = PenTool::POINT; - ret = TRUE; - break; - case PenTool::STOP: - // This is allowed, if we just canceled curve - pc->state = PenTool::POINT; - ret = TRUE; - break; - default: - break; - } - break; - case PenTool::MODE_DRAG: - switch (pc->state) { - case PenTool::POINT: - case PenTool::CONTROL: - spdc_endpoint_snap(pc, p, revent.state); - spdc_pen_finish_segment(pc, p, revent.state); - break; - case PenTool::CLOSE: - spdc_endpoint_snap(pc, p, revent.state); - spdc_pen_finish_segment(pc, p, revent.state); - if (pc->green_closed) { - // finishing at the start anchor, close curve - spdc_pen_finish(pc, TRUE); - } else { - // finishing at some other anchor, finish curve but not close - spdc_pen_finish(pc, FALSE); - } - break; - case PenTool::STOP: - // This is allowed, if we just cancelled curve - break; - default: - break; - } - pc->state = PenTool::POINT; - ret = TRUE; - break; - default: - break; - } - - if (pc->grab) { - // Release grab now - sp_canvas_item_ungrab(pc->grab, revent.time); - pc->grab = NULL; - } - - ret = TRUE; - - dc->green_closed = FALSE; - } - - // TODO: can we be sure that the path was created correctly? - // TODO: should we offer an option to collect the clicks in a list? - if (pc->expecting_clicks_for_LPE == 0 && sp_pen_context_has_waiting_LPE(pc)) { - sp_pen_context_set_polyline_mode(pc); - - ToolBase *ec = SP_EVENT_CONTEXT(pc); - Inkscape::Selection *selection = sp_desktop_selection (ec->desktop); - - if (pc->waiting_LPE) { - // we have an already created LPE waiting for a path - pc->waiting_LPE->acceptParamPath(SP_PATH(selection->singleItem())); - selection->add(SP_OBJECT(pc->waiting_item)); - pc->waiting_LPE = NULL; - } else { - // the case that we need to create a new LPE and apply it to the just-drawn path is - // handled in spdc_check_for_and_apply_waiting_LPE() in draw-context.cpp - } - } - - return ret; -} - -static gint pen_handle_2button_press(PenTool *const pc, GdkEventButton const &bevent) -{ - gint ret = FALSE; - // only end on LMB double click. Otherwise horizontal scrolling causes ending of the path - if (pc->npoints != 0 && bevent.button == 1) { - spdc_pen_finish(pc, FALSE); - ret = TRUE; - } - return ret; -} - -static void pen_redraw_all (PenTool *const pc) -{ - // green - if (pc->green_bpaths) { - // remove old piecewise green canvasitems - while (pc->green_bpaths) { - sp_canvas_item_destroy(SP_CANVAS_ITEM(pc->green_bpaths->data)); - pc->green_bpaths = g_slist_remove(pc->green_bpaths, pc->green_bpaths->data); - } - // one canvas bpath for all of green_curve - SPCanvasItem *cshape = sp_canvas_bpath_new(sp_desktop_sketch(pc->desktop), pc->green_curve); - sp_canvas_bpath_set_stroke(SP_CANVAS_BPATH(cshape), pc->green_color, 1.0, SP_STROKE_LINEJOIN_MITER, SP_STROKE_LINECAP_BUTT); - sp_canvas_bpath_set_fill(SP_CANVAS_BPATH(cshape), 0, SP_WIND_RULE_NONZERO); - - pc->green_bpaths = g_slist_prepend(pc->green_bpaths, cshape); - } - - if (pc->green_anchor) - SP_CTRL(pc->green_anchor->ctrl)->moveto(pc->green_anchor->dp); - - pc->red_curve->reset(); - pc->red_curve->moveto(pc->p[0]); - pc->red_curve->curveto(pc->p[1], pc->p[2], pc->p[3]); - sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(pc->red_bpath), pc->red_curve); - - // handles - if (pc->p[0] != pc->p[1]) { - SP_CTRL(pc->c1)->moveto(pc->p[1]); - pc->cl1->setCoords(pc->p[0], pc->p[1]); - sp_canvas_item_show(pc->c1); - sp_canvas_item_show(pc->cl1); - } else { - sp_canvas_item_hide(pc->c1); - sp_canvas_item_hide(pc->cl1); - } - - Geom::Curve const * last_seg = pc->green_curve->last_segment(); - if (last_seg) { - Geom::CubicBezier const * cubic = dynamic_cast( last_seg ); - if ( cubic && - (*cubic)[2] != pc->p[0] ) - { - Geom::Point p2 = (*cubic)[2]; - SP_CTRL(pc->c0)->moveto(p2); - pc->cl0->setCoords(p2, pc->p[0]); - sp_canvas_item_show(pc->c0); - sp_canvas_item_show(pc->cl0); - } else { - sp_canvas_item_hide(pc->c0); - sp_canvas_item_hide(pc->cl0); - } - } -} - -static void pen_lastpoint_move (PenTool *const pc, gdouble x, gdouble y) -{ - if (pc->npoints != 5) - return; - - // green - if (!pc->green_curve->is_empty()) { - pc->green_curve->last_point_additive_move( Geom::Point(x,y) ); - } else { - // start anchor too - if (pc->green_anchor) { - pc->green_anchor->dp += Geom::Point(x, y); - } - } - - // red - pc->p[0] += Geom::Point(x, y); - pc->p[1] += Geom::Point(x, y); - pen_redraw_all(pc); -} - -static void pen_lastpoint_move_screen (PenTool *const pc, gdouble x, gdouble y) -{ - pen_lastpoint_move (pc, x / pc->desktop->current_zoom(), y / pc->desktop->current_zoom()); -} - -static void pen_lastpoint_tocurve (PenTool *const pc) -{ - if (pc->npoints != 5) - return; - - Geom::CubicBezier const * cubic = dynamic_cast( pc->green_curve->last_segment() ); - if ( cubic ) { - pc->p[1] = pc->p[0] + (Geom::Point)( (*cubic)[3] - (*cubic)[2] ); - } else { - pc->p[1] = pc->p[0] + (1./3)*(pc->p[3] - pc->p[0]); - } - - pen_redraw_all(pc); -} - -static void pen_lastpoint_toline (PenTool *const pc) -{ - if (pc->npoints != 5) - return; - - pc->p[1] = pc->p[0]; - - pen_redraw_all(pc); -} - - -static gint pen_handle_key_press(PenTool *const pc, GdkEvent *event) -{ - - gint ret = FALSE; - Inkscape::Preferences *prefs = Inkscape::Preferences::get(); - gdouble const nudge = prefs->getDoubleLimited("/options/nudgedistance/value", 2, 0, 1000, "px"); // in px - - switch (get_group0_keyval (&event->key)) { - - case GDK_KEY_Left: // move last point left - case GDK_KEY_KP_Left: - if (!MOD__CTRL(event)) { // not ctrl - if (MOD__ALT(event)) { // alt - if (MOD__SHIFT(event)) pen_lastpoint_move_screen(pc, -10, 0); // shift - else pen_lastpoint_move_screen(pc, -1, 0); // no shift - } - else { // no alt - if (MOD__SHIFT(event)) pen_lastpoint_move(pc, -10*nudge, 0); // shift - else pen_lastpoint_move(pc, -nudge, 0); // no shift - } - ret = TRUE; - } - break; - case GDK_KEY_Up: // move last point up - case GDK_KEY_KP_Up: - if (!MOD__CTRL(event)) { // not ctrl - if (MOD__ALT(event)) { // alt - if (MOD__SHIFT(event)) pen_lastpoint_move_screen(pc, 0, 10); // shift - else pen_lastpoint_move_screen(pc, 0, 1); // no shift - } - else { // no alt - if (MOD__SHIFT(event)) pen_lastpoint_move(pc, 0, 10*nudge); // shift - else pen_lastpoint_move(pc, 0, nudge); // no shift - } - ret = TRUE; - } - break; - case GDK_KEY_Right: // move last point right - case GDK_KEY_KP_Right: - if (!MOD__CTRL(event)) { // not ctrl - if (MOD__ALT(event)) { // alt - if (MOD__SHIFT(event)) pen_lastpoint_move_screen(pc, 10, 0); // shift - else pen_lastpoint_move_screen(pc, 1, 0); // no shift - } - else { // no alt - if (MOD__SHIFT(event)) pen_lastpoint_move(pc, 10*nudge, 0); // shift - else pen_lastpoint_move(pc, nudge, 0); // no shift - } - ret = TRUE; - } - break; - case GDK_KEY_Down: // move last point down - case GDK_KEY_KP_Down: - if (!MOD__CTRL(event)) { // not ctrl - if (MOD__ALT(event)) { // alt - if (MOD__SHIFT(event)) pen_lastpoint_move_screen(pc, 0, -10); // shift - else pen_lastpoint_move_screen(pc, 0, -1); // no shift - } - else { // no alt - if (MOD__SHIFT(event)) pen_lastpoint_move(pc, 0, -10*nudge); // shift - else pen_lastpoint_move(pc, 0, -nudge); // no shift - } - ret = TRUE; - } - break; - -/*TODO: this is not yet enabled?? looks like some traces of the Geometry tool - case GDK_KEY_P: - case GDK_KEY_p: - if (MOD__SHIFT_ONLY(event)) { - sp_pen_context_wait_for_LPE_mouse_clicks(pc, Inkscape::LivePathEffect::PARALLEL, 2); - ret = TRUE; - } - break; - - case GDK_KEY_C: - case GDK_KEY_c: - if (MOD__SHIFT_ONLY(event)) { - sp_pen_context_wait_for_LPE_mouse_clicks(pc, Inkscape::LivePathEffect::CIRCLE_3PTS, 3); - ret = TRUE; - } - break; - - case GDK_KEY_B: - case GDK_KEY_b: - if (MOD__SHIFT_ONLY(event)) { - sp_pen_context_wait_for_LPE_mouse_clicks(pc, Inkscape::LivePathEffect::PERP_BISECTOR, 2); - ret = TRUE; - } - break; - - case GDK_KEY_A: - case GDK_KEY_a: - if (MOD__SHIFT_ONLY(event)) { - sp_pen_context_wait_for_LPE_mouse_clicks(pc, Inkscape::LivePathEffect::ANGLE_BISECTOR, 3); - ret = TRUE; - } - break; -*/ - - case GDK_KEY_U: - case GDK_KEY_u: - if (MOD__SHIFT_ONLY(event)) { - pen_lastpoint_tocurve(pc); - ret = TRUE; - } - break; - case GDK_KEY_L: - case GDK_KEY_l: - if (MOD__SHIFT_ONLY(event)) { - pen_lastpoint_toline(pc); - ret = TRUE; - } - break; - - case GDK_KEY_Return: - case GDK_KEY_KP_Enter: - if (pc->npoints != 0) { - spdc_pen_finish(pc, FALSE); - ret = TRUE; - } - break; - case GDK_KEY_Escape: - if (pc->npoints != 0) { - // if drawing, cancel, otherwise pass it up for deselecting - pen_cancel (pc); - ret = TRUE; - } - break; - case GDK_KEY_z: - case GDK_KEY_Z: - if (MOD__CTRL_ONLY(event) && pc->npoints != 0) { - // if drawing, cancel, otherwise pass it up for undo - pen_cancel (pc); - ret = TRUE; - } - break; - case GDK_KEY_g: - case GDK_KEY_G: - if (MOD__SHIFT_ONLY(event)) { - sp_selection_to_guides(SP_EVENT_CONTEXT(pc)->desktop); - ret = true; - } - break; - case GDK_KEY_BackSpace: - case GDK_KEY_Delete: - case GDK_KEY_KP_Delete: - if ( pc->green_curve->is_empty() || (pc->green_curve->last_segment() == NULL) ) { - if (!pc->red_curve->is_empty()) { - pen_cancel (pc); - ret = TRUE; - } else { - // do nothing; this event should be handled upstream - } - } else { - // Reset red curve - pc->red_curve->reset(); - // Destroy topmost green bpath - if (pc->green_bpaths) { - if (pc->green_bpaths->data) - sp_canvas_item_destroy(SP_CANVAS_ITEM(pc->green_bpaths->data)); - pc->green_bpaths = g_slist_remove(pc->green_bpaths, pc->green_bpaths->data); - } - // Get last segment - if ( pc->green_curve->is_empty() ) { - g_warning("pen_handle_key_press, case GDK_KP_Delete: Green curve is empty"); - break; - } - // The code below assumes that pc->green_curve has only ONE path ! - Geom::Curve const * crv = pc->green_curve->last_segment(); - pc->p[0] = crv->initialPoint(); - if ( Geom::CubicBezier const * cubic = dynamic_cast(crv)) { - pc->p[1] = (*cubic)[1]; - } else { - pc->p[1] = pc->p[0]; - } - Geom::Point const pt(( pc->npoints < 4 - ? (Geom::Point)(crv->finalPoint()) - : pc->p[3] )); - pc->npoints = 2; - pc->green_curve->backspace(); - sp_canvas_item_hide(pc->c0); - sp_canvas_item_hide(pc->c1); - sp_canvas_item_hide(pc->cl0); - sp_canvas_item_hide(pc->cl1); - pc->state = PenTool::POINT; - spdc_pen_set_subsequent_point(pc, pt, true); - pen_last_paraxial_dir = !pen_last_paraxial_dir; - ret = TRUE; - } - break; - default: - break; - } - return ret; -} - -static void spdc_reset_colors(PenTool *pc) -{ - // Red - pc->red_curve->reset(); - sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(pc->red_bpath), NULL); - // Blue - pc->blue_curve->reset(); - sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(pc->blue_bpath), NULL); - // Green - while (pc->green_bpaths) { - sp_canvas_item_destroy(SP_CANVAS_ITEM(pc->green_bpaths->data)); - pc->green_bpaths = g_slist_remove(pc->green_bpaths, pc->green_bpaths->data); - } - pc->green_curve->reset(); - if (pc->green_anchor) { - pc->green_anchor = sp_draw_anchor_destroy(pc->green_anchor); - } - pc->sa = NULL; - pc->ea = NULL; - pc->npoints = 0; - pc->red_curve_is_valid = false; -} - - -static void spdc_pen_set_initial_point(PenTool *const pc, Geom::Point const p) -{ - g_assert( pc->npoints == 0 ); - - pc->p[0] = p; - pc->p[1] = p; - pc->npoints = 2; - sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(pc->red_bpath), NULL); - - pc->desktop->canvas->forceFullRedrawAfterInterruptions(5); -} - -/** - * Show the status message for the current line/curve segment. - * This type of message always shows angle/distance as the last - * two parameters ("angle %3.2f°, distance %s"). - */ -static void spdc_pen_set_angle_distance_status_message(PenTool *const pc, Geom::Point const p, int pc_point_to_compare, gchar const *message) -{ - g_assert(pc != NULL); - g_assert((pc_point_to_compare == 0) || (pc_point_to_compare == 3)); // exclude control handles - g_assert(message != NULL); - - SPDesktop *desktop = SP_EVENT_CONTEXT(pc)->desktop; - Geom::Point rel = p - pc->p[pc_point_to_compare]; - Inkscape::Util::Quantity q = Inkscape::Util::Quantity(Geom::L2(rel), "px"); - GString *dist = g_string_new(q.string(desktop->namedview->doc_units).c_str()); - double angle = atan2(rel[Geom::Y], rel[Geom::X]) * 180 / M_PI; - Inkscape::Preferences *prefs = Inkscape::Preferences::get(); - if (prefs->getBool("/options/compassangledisplay/value", 0) != 0) { - angle = 90 - angle; - if (angle < 0) { - angle += 360; - } - } - - pc->message_context->setF(Inkscape::IMMEDIATE_MESSAGE, message, angle, dist->str); - g_string_free(dist, FALSE); -} - -static void spdc_pen_set_subsequent_point(PenTool *const pc, Geom::Point const p, bool statusbar, guint status) -{ - g_assert( pc->npoints != 0 ); - // todo: Check callers to see whether 2 <= npoints is guaranteed. - - pc->p[2] = p; - pc->p[3] = p; - pc->p[4] = p; - pc->npoints = 5; - pc->red_curve->reset(); - bool is_curve; - pc->red_curve->moveto(pc->p[0]); - if (pc->polylines_paraxial && !statusbar) { - // we are drawing horizontal/vertical lines and hit an anchor; - Geom::Point const origin = pc->p[0]; - // if the previous point and the anchor are not aligned either horizontally or vertically... - if ((abs(p[Geom::X] - origin[Geom::X]) > 1e-9) && (abs(p[Geom::Y] - origin[Geom::Y]) > 1e-9)) { - // ...then we should draw an L-shaped path, consisting of two paraxial segments - Geom::Point intermed = p; - pen_set_to_nearest_horiz_vert(pc, intermed, status, false); - pc->red_curve->lineto(intermed); - } - pc->red_curve->lineto(p); - is_curve = false; - } else { - // one of the 'regular' modes - if (pc->p[1] != pc->p[0]) { - pc->red_curve->curveto(pc->p[1], p, p); - is_curve = true; - } else { - pc->red_curve->lineto(p); - is_curve = false; - } - } - - sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(pc->red_bpath), pc->red_curve); - - if (statusbar) { - gchar *message = is_curve ? - _("Curve segment: angle %3.2f°, distance %s; with Ctrl to snap angle, Enter to finish the path" ): - _("Line segment: angle %3.2f°, distance %s; with Ctrl to snap angle, Enter to finish the path"); - spdc_pen_set_angle_distance_status_message(pc, p, 0, message); - } -} - -static void spdc_pen_set_ctrl(PenTool *const pc, Geom::Point const p, guint const state) -{ - sp_canvas_item_show(pc->c1); - sp_canvas_item_show(pc->cl1); - - if ( pc->npoints == 2 ) { - pc->p[1] = p; - sp_canvas_item_hide(pc->c0); - sp_canvas_item_hide(pc->cl0); - SP_CTRL(pc->c1)->moveto(pc->p[1]); - pc->cl1->setCoords(pc->p[0], pc->p[1]); - - spdc_pen_set_angle_distance_status_message(pc, p, 0, _("Curve handle: angle %3.2f°, length %s; with Ctrl to snap angle")); - } else if ( pc->npoints == 5 ) { - pc->p[4] = p; - sp_canvas_item_show(pc->c0); - sp_canvas_item_show(pc->cl0); - bool is_symm = false; - if ( ( ( pc->mode == PenTool::MODE_CLICK ) && ( state & GDK_CONTROL_MASK ) ) || - ( ( pc->mode == PenTool::MODE_DRAG ) && !( state & GDK_SHIFT_MASK ) ) ) { - Geom::Point delta = p - pc->p[3]; - pc->p[2] = pc->p[3] - delta; - is_symm = true; - pc->red_curve->reset(); - pc->red_curve->moveto(pc->p[0]); - pc->red_curve->curveto(pc->p[1], pc->p[2], pc->p[3]); - sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(pc->red_bpath), pc->red_curve); - } - SP_CTRL(pc->c0)->moveto(pc->p[2]); - pc->cl0 ->setCoords(pc->p[3], pc->p[2]); - SP_CTRL(pc->c1)->moveto(pc->p[4]); - pc->cl1->setCoords(pc->p[3], pc->p[4]); - - gchar *message = is_symm ? - _("Curve handle, symmetric: angle %3.2f°, length %s; with Ctrl to snap angle, with Shift to move this handle only") : - _("Curve handle: angle %3.2f°, length %s; with Ctrl to snap angle, with Shift to move this handle only"); - spdc_pen_set_angle_distance_status_message(pc, p, 3, message); - } else { - g_warning("Something bad happened - npoints is %d", pc->npoints); - } -} - -static void spdc_pen_finish_segment(PenTool *const pc, Geom::Point const p, guint const state) -{ - if (pc->polylines_paraxial) { - pen_last_paraxial_dir = pen_next_paraxial_direction(pc, p, pc->p[0], state); - } - - ++pc->num_clicks; - - if (!pc->red_curve->is_empty()) { - pc->green_curve->append_continuous(pc->red_curve, 0.0625); - SPCurve *curve = pc->red_curve->copy(); - /// \todo fixme: - SPCanvasItem *cshape = sp_canvas_bpath_new(sp_desktop_sketch(pc->desktop), curve); - curve->unref(); - sp_canvas_bpath_set_stroke(SP_CANVAS_BPATH(cshape), pc->green_color, 1.0, SP_STROKE_LINEJOIN_MITER, SP_STROKE_LINECAP_BUTT); - - pc->green_bpaths = g_slist_prepend(pc->green_bpaths, cshape); - - pc->p[0] = pc->p[3]; - pc->p[1] = pc->p[4]; - pc->npoints = 2; - - pc->red_curve->reset(); - } -} - -static void spdc_pen_finish(PenTool *const pc, gboolean const closed) -{ - if (pc->expecting_clicks_for_LPE > 1) { - // don't let the path be finished before we have collected the required number of mouse clicks - return; - } - - pc->num_clicks = 0; - - pen_disable_events(pc); - - SPDesktop *const desktop = pc->desktop; - pc->message_context->clear(); - desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("Drawing finished")); - - pc->red_curve->reset(); - spdc_concat_colors_and_flush(pc, closed); - pc->sa = NULL; - pc->ea = NULL; - - pc->npoints = 0; - pc->state = PenTool::POINT; - - sp_canvas_item_hide(pc->c0); - sp_canvas_item_hide(pc->c1); - sp_canvas_item_hide(pc->cl0); - sp_canvas_item_hide(pc->cl1); - - if (pc->green_anchor) { - pc->green_anchor = sp_draw_anchor_destroy(pc->green_anchor); - } - - - pc->desktop->canvas->endForcedFullRedraws(); - - pen_enable_events(pc); -} - -static void pen_disable_events(PenTool *const pc) { - pc->events_disabled++; -} - -static void pen_enable_events(PenTool *const pc) { - g_return_if_fail(pc->events_disabled != 0); - - pc->events_disabled--; -} - -void sp_pen_context_wait_for_LPE_mouse_clicks(PenTool *pc, Inkscape::LivePathEffect::EffectType effect_type, - unsigned int num_clicks, bool use_polylines) -{ - if (effect_type == Inkscape::LivePathEffect::INVALID_LPE) - return; - - pc->waiting_LPE_type = effect_type; - pc->expecting_clicks_for_LPE = num_clicks; - pc->polylines_only = use_polylines; - pc->polylines_paraxial = false; // TODO: think if this is correct for all cases -} - -void sp_pen_context_cancel_waiting_for_LPE(PenTool *pc) -{ - pc->waiting_LPE_type = Inkscape::LivePathEffect::INVALID_LPE; - pc->expecting_clicks_for_LPE = 0; - sp_pen_context_set_polyline_mode(pc); -} - -static int pen_next_paraxial_direction(const PenTool *const pc, - Geom::Point const &pt, Geom::Point const &origin, guint state) { - // - // after the first mouse click we determine whether the mouse pointer is closest to a - // horizontal or vertical segment; for all subsequent mouse clicks, we use the direction - // orthogonal to the last one; pressing Shift toggles the direction - // - // num_clicks is not reliable because spdc_pen_finish_segment is sometimes called too early - // (on first mouse release), in which case num_clicks immediately becomes 1. - // if (pc->num_clicks == 0) { - - if (pc->green_curve->is_empty()) { - // first mouse click - double dist_h = fabs(pt[Geom::X] - origin[Geom::X]); - double dist_v = fabs(pt[Geom::Y] - origin[Geom::Y]); - int ret = (dist_h < dist_v) ? 1 : 0; // 0 = horizontal, 1 = vertical - pen_last_paraxial_dir = (state & GDK_SHIFT_MASK) ? 1 - ret : ret; - return pen_last_paraxial_dir; - } else { - // subsequent mouse click - return (state & GDK_SHIFT_MASK) ? pen_last_paraxial_dir : 1 - pen_last_paraxial_dir; - } -} - -void pen_set_to_nearest_horiz_vert(const PenTool *const pc, Geom::Point &pt, guint const state, bool snap) -{ - Geom::Point const origin = pc->p[0]; - - int next_dir = pen_next_paraxial_direction(pc, pt, origin, state); - - if (!snap) { - if (next_dir == 0) { - // line is forced to be horizontal - pt[Geom::Y] = origin[Geom::Y]; - } else { - // line is forced to be vertical - pt[Geom::X] = origin[Geom::X]; - } - } else { - // Create a horizontal or vertical constraint line - Inkscape::Snapper::SnapConstraint cl(origin, next_dir ? Geom::Point(0, 1) : Geom::Point(1, 0)); - - // Snap along the constraint line; if we didn't snap then still the constraint will be applied - SnapManager &m = pc->desktop->namedview->snap_manager; - - Inkscape::Selection *selection = sp_desktop_selection (pc->desktop); - // selection->singleItem() is the item that is currently being drawn. This item will not be snapped to (to avoid self-snapping) - // TODO: Allow snapping to the stationary parts of the item, and only ignore the last segment - - m.setup(pc->desktop, true, selection->singleItem()); - m.constrainedSnapReturnByRef(pt, Inkscape::SNAPSOURCE_NODE_HANDLE, cl); - m.unSetup(); - } -} - -} -} -} - -/* - Local Variables: - mode:c++ - c-file-style:"stroustrup" - c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) - indent-tabs-mode:nil - fill-column:99 - End: -*/ -// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/pen-context.h b/src/pen-context.h deleted file mode 100644 index 9c84ee540..000000000 --- a/src/pen-context.h +++ /dev/null @@ -1,105 +0,0 @@ -#ifndef SEEN_PEN_CONTEXT_H -#define SEEN_PEN_CONTEXT_H - -/** \file - * PenTool: a context for pen tool events. - */ - -#include "draw-context.h" -#include "live_effects/effect.h" - -#define SP_PEN_CONTEXT(obj) (dynamic_cast((Inkscape::UI::Tools::ToolBase*)obj)) -#define SP_IS_PEN_CONTEXT(obj) (dynamic_cast((const Inkscape::UI::Tools::ToolBase*)obj) != NULL) - -struct SPCtrlLine; - -namespace Inkscape { -namespace UI { -namespace Tools { - -/** - * PenTool: a context for pen tool events. - */ -class PenTool : public SPDrawContext { -public: - PenTool(); - virtual ~PenTool(); - - enum Mode { - MODE_CLICK, - MODE_DRAG - }; - - enum State { - POINT, - CONTROL, - CLOSE, - STOP - }; - - Geom::Point p[5]; - - /** \invar npoints in {0, 2, 5}. */ - // npoints somehow determines the type of the node (what does it mean, exactly? the number of Bezier handles?) - gint npoints; - - Mode mode; - State state; - - bool polylines_only; - bool polylines_paraxial; - int num_clicks; - - unsigned int expecting_clicks_for_LPE; // if positive, finish the path after this many clicks - Inkscape::LivePathEffect::Effect *waiting_LPE; // if NULL, waiting_LPE_type in SPDrawContext is taken into account - SPLPEItem *waiting_item; - - SPCanvasItem *c0; - SPCanvasItem *c1; - - SPCtrlLine *cl0; - SPCtrlLine *cl1; - - unsigned int events_disabled : 1; - - static const std::string prefsPath; - - virtual const std::string& getPrefsPath(); - -protected: - virtual void setup(); - virtual void finish(); - virtual void set(const Inkscape::Preferences::Entry& val); - virtual bool root_handler(GdkEvent* event); - virtual bool item_handler(SPItem* item, GdkEvent* event); -}; - -inline bool sp_pen_context_has_waiting_LPE(PenTool *pc) { - // note: waiting_LPE_type is defined in SPDrawContext - return (pc->waiting_LPE != NULL || - pc->waiting_LPE_type != Inkscape::LivePathEffect::INVALID_LPE); -} - -void sp_pen_context_set_polyline_mode(PenTool *const pc); -void sp_pen_context_wait_for_LPE_mouse_clicks(PenTool *pc, Inkscape::LivePathEffect::EffectType effect_type, - unsigned int num_clicks, bool use_polylines = true); -void sp_pen_context_cancel_waiting_for_LPE(PenTool *pc); -void sp_pen_context_put_into_waiting_mode(SPDesktop *desktop, Inkscape::LivePathEffect::EffectType effect_type, - unsigned int num_clicks, bool use_polylines = true); - -} -} -} - -#endif /* !SEEN_PEN_CONTEXT_H */ - -/* - Local Variables: - mode:c++ - c-file-style:"stroustrup" - c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) - indent-tabs-mode:nil - fill-column:99 - End: -*/ -// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/pencil-context.cpp b/src/pencil-context.cpp deleted file mode 100644 index bd9dac5dc..000000000 --- a/src/pencil-context.cpp +++ /dev/null @@ -1,916 +0,0 @@ -/** \file - * Pencil event context implementation. - */ - -/* - * Authors: - * Lauris Kaplinski - * bulia byak - * Jon A. Cruz - * - * Copyright (C) 2000 Lauris Kaplinski - * Copyright (C) 2000-2001 Ximian, Inc. - * Copyright (C) 2002 Lauris Kaplinski - * Copyright (C) 2004 Monash University - * - * Released under GNU GPL, read the file 'COPYING' for more information - */ - -#include - -#include "pencil-context.h" -#include "desktop.h" -#include "desktop-handles.h" -#include "selection.h" -#include "selection-chemistry.h" -#include "draw-anchor.h" -#include "message-stack.h" -#include "message-context.h" -#include "modifier-fns.h" -#include "sp-path.h" -#include "preferences.h" -#include "snap.h" -#include "pixmaps/cursor-pencil.xpm" -#include <2geom/sbasis-to-bezier.h> -#include <2geom/bezier-utils.h> -#include "display/canvas-bpath.h" -#include -#include "context-fns.h" -#include "sp-namedview.h" -#include "xml/repr.h" -#include "document.h" -#include "desktop-style.h" -#include "macros.h" -#include "display/sp-canvas.h" -#include "display/curve.h" -#include "livarot/Path.h" -#include "tool-factory.h" - -namespace Inkscape { -namespace UI { -namespace Tools { - -static gint pencil_handle_button_press(PencilTool *const pc, GdkEventButton const &bevent); -static gint pencil_handle_motion_notify(PencilTool *const pc, GdkEventMotion const &mevent); -static gint pencil_handle_button_release(PencilTool *const pc, GdkEventButton const &revent); -static gint pencil_handle_key_press(PencilTool *const pc, guint const keyval, guint const state); -static gint pencil_handle_key_release(PencilTool *const pc, guint const keyval, guint const state); - -static void spdc_set_startpoint(PencilTool *pc, Geom::Point const &p); -static void spdc_set_endpoint(PencilTool *pc, Geom::Point const &p); -static void spdc_finish_endpoint(PencilTool *pc); -static void spdc_add_freehand_point(PencilTool *pc, Geom::Point const &p, guint state); -static void fit_and_split(PencilTool *pc); -static void interpolate(PencilTool *pc); -static void sketch_interpolate(PencilTool *pc); - -static Geom::Point pencil_drag_origin_w(0, 0); -static bool pencil_within_tolerance = false; - -static bool in_svg_plane(Geom::Point const &p) { return Geom::LInfty(p) < 1e18; } - -namespace { - ToolBase* createPencilContext() { - return new PencilTool(); - } - - bool pencilContextRegistered = ToolFactory::instance().registerObject("/tools/freehand/pencil", createPencilContext); -} - -const std::string& PencilTool::getPrefsPath() { - return PencilTool::prefsPath; -} - -const std::string PencilTool::prefsPath = "/tools/freehand/pencil"; - -PencilTool::PencilTool() : - SPDrawContext(), - p(), - npoints(0), - state(SP_PENCIL_CONTEXT_IDLE), - req_tangent(0,0), - is_drawing(false), - ps(), - sketch_interpolation(Geom::Piecewise >())// since PencilTool is not properly constructed... -{ - this->cursor_shape = cursor_pencil_xpm; - this->hot_x = 4; - this->hot_y = 4; - this->sketch_n = 0; -} - -void PencilTool::setup() { - Inkscape::Preferences *prefs = Inkscape::Preferences::get(); - if (prefs->getBool("/tools/freehand/pencil/selcue")) { - this->enableSelectionCue(); - } - - SPDrawContext::setup(); - - this->is_drawing = false; - this->anchor_statusbar = false; -} - -PencilTool::~PencilTool() { -} - -/** Snaps new node relative to the previous node. */ -static void -spdc_endpoint_snap(PencilTool const *pc, Geom::Point &p, guint const state) -{ - if ((state & GDK_CONTROL_MASK)) { //CTRL enables constrained snapping - if (pc->npoints > 0) { - spdc_endpoint_snap_rotation(pc, p, pc->p[0], state); - } - } else { - if (!(state & GDK_SHIFT_MASK)) { //SHIFT disables all snapping, except the angular snapping above - //After all, the user explicitely asked for angular snapping by - //pressing CTRL - boost::optional origin = pc->npoints > 0 ? pc->p[0] : boost::optional(); - spdc_endpoint_snap_free(pc, p, origin, state); - } - } -} - -/** - * Callback for handling all pencil context events. - */ -bool PencilTool::root_handler(GdkEvent* event) { - gint ret = FALSE; - - switch (event->type) { - case GDK_BUTTON_PRESS: - ret = pencil_handle_button_press(this, event->button); - break; - - case GDK_MOTION_NOTIFY: - ret = pencil_handle_motion_notify(this, event->motion); - break; - - case GDK_BUTTON_RELEASE: - ret = pencil_handle_button_release(this, event->button); - break; - - case GDK_KEY_PRESS: - ret = pencil_handle_key_press(this, get_group0_keyval (&event->key), event->key.state); - break; - - case GDK_KEY_RELEASE: - ret = pencil_handle_key_release(this, get_group0_keyval (&event->key), event->key.state); - break; - - default: - break; - } - - if (!ret) { - ret = SPDrawContext::root_handler(event); - } - - return ret; -} - -static gint -pencil_handle_button_press(PencilTool *const pc, GdkEventButton const &bevent) -{ - gint ret = FALSE; - ToolBase *event_context = SP_EVENT_CONTEXT(pc); - if ( bevent.button == 1 && !event_context->space_panning) { - - SPDrawContext *dc = SP_DRAW_CONTEXT (pc); - SPDesktop *desktop = SP_EVENT_CONTEXT_DESKTOP(dc); - Inkscape::Selection *selection = sp_desktop_selection(desktop); - - if (Inkscape::have_viable_layer(desktop, dc->message_context) == false) { - return TRUE; - } - - if (!pc->grab) { - /* Grab mouse, so release will not pass unnoticed */ - pc->grab = SP_CANVAS_ITEM(desktop->acetate); - sp_canvas_item_grab(pc->grab, ( GDK_KEY_PRESS_MASK | GDK_BUTTON_PRESS_MASK | - GDK_BUTTON_RELEASE_MASK | - GDK_POINTER_MOTION_MASK ), - NULL, bevent.time); - } - - Geom::Point const button_w(bevent.x, bevent.y); - - /* Find desktop coordinates */ - Geom::Point p = pc->desktop->w2d(button_w); - - /* Test whether we hit any anchor. */ - SPDrawAnchor *anchor = spdc_test_inside(pc, button_w); - - pencil_drag_origin_w = Geom::Point(bevent.x,bevent.y); - pencil_within_tolerance = true; - - switch (pc->state) { - case SP_PENCIL_CONTEXT_ADDLINE: - /* Current segment will be finished with release */ - ret = TRUE; - break; - default: - /* Set first point of sequence */ - SnapManager &m = desktop->namedview->snap_manager; - - if (bevent.state & GDK_CONTROL_MASK) { - m.setup(desktop); - if (!(bevent.state & GDK_SHIFT_MASK)) { - m.freeSnapReturnByRef(p, Inkscape::SNAPSOURCE_NODE_HANDLE); - } - spdc_create_single_dot(event_context, p, "/tools/freehand/pencil", bevent.state); - m.unSetup(); - ret = true; - break; - } - if (anchor) { - p = anchor->dp; - desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("Continuing selected path")); - } else { - m.setup(desktop); - if (!(bevent.state & GDK_SHIFT_MASK)) { - // This is the first click of a new curve; deselect item so that - // this curve is not combined with it (unless it is drawn from its - // anchor, which is handled by the sibling branch above) - selection->clear(); - desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("Creating new path")); - m.freeSnapReturnByRef(p, Inkscape::SNAPSOURCE_NODE_HANDLE); - } else if (selection->singleItem() && SP_IS_PATH(selection->singleItem())) { - desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("Appending to selected path")); - m.freeSnapReturnByRef(p, Inkscape::SNAPSOURCE_NODE_HANDLE); - } - m.unSetup(); - } - pc->sa = anchor; - spdc_set_startpoint(pc, p); - ret = TRUE; - break; - } - - pc->is_drawing = true; - } - return ret; -} - -static gint -pencil_handle_motion_notify(PencilTool *const pc, GdkEventMotion const &mevent) -{ - SPDesktop *const dt = pc->desktop; - - if ((mevent.state & GDK_CONTROL_MASK) && (mevent.state & GDK_BUTTON1_MASK)) { - // mouse was accidentally moved during Ctrl+click; - // ignore the motion and create a single point - pc->is_drawing = false; - return TRUE; - } - gint ret = FALSE; - - ToolBase *event_context = SP_EVENT_CONTEXT(pc); - if (event_context->space_panning || mevent.state & GDK_BUTTON2_MASK || mevent.state & GDK_BUTTON3_MASK) { - // allow scrolling - return FALSE; - } - - if ( ( mevent.state & GDK_BUTTON1_MASK ) && !pc->grab && pc->is_drawing) { - /* Grab mouse, so release will not pass unnoticed */ - pc->grab = SP_CANVAS_ITEM(dt->acetate); - sp_canvas_item_grab(pc->grab, ( GDK_KEY_PRESS_MASK | GDK_BUTTON_PRESS_MASK | - GDK_BUTTON_RELEASE_MASK | - GDK_POINTER_MOTION_MASK ), - NULL, mevent.time); - } - - /* Find desktop coordinates */ - Geom::Point p = dt->w2d(Geom::Point(mevent.x, mevent.y)); - - /* Test whether we hit any anchor. */ - SPDrawAnchor *anchor = spdc_test_inside(pc, Geom::Point(mevent.x, mevent.y)); - - if (pencil_within_tolerance) { - Inkscape::Preferences *prefs = Inkscape::Preferences::get(); - gint const tolerance = prefs->getIntLimited("/options/dragtolerance/value", 0, 0, 100); - if ( Geom::LInfty( Geom::Point(mevent.x,mevent.y) - pencil_drag_origin_w ) < tolerance ) { - return FALSE; // Do not drag if we're within tolerance from origin. - } - } - - // Once the user has moved farther than tolerance from the original location - // (indicating they intend to move the object, not click), then always process the - // motion notify coordinates as given (no snapping back to origin) - pencil_within_tolerance = false; - - switch (pc->state) { - case SP_PENCIL_CONTEXT_ADDLINE: - /* Set red endpoint */ - if (anchor) { - p = anchor->dp; - } else { - Geom::Point ptnr(p); - spdc_endpoint_snap(pc, ptnr, mevent.state); - p = ptnr; - } - spdc_set_endpoint(pc, p); - ret = TRUE; - break; - default: - /* We may be idle or already freehand */ - if ( mevent.state & GDK_BUTTON1_MASK && pc->is_drawing ) { - if (pc->state == SP_PENCIL_CONTEXT_IDLE) { - sp_event_context_discard_delayed_snap_event(event_context); - } - pc->state = SP_PENCIL_CONTEXT_FREEHAND; - - if ( !pc->sa && !pc->green_anchor ) { - /* Create green anchor */ - pc->green_anchor = sp_draw_anchor_new(pc, pc->green_curve, TRUE, pc->p[0]); - } - if (anchor) { - p = anchor->dp; - } - - if ( pc->npoints != 0) { // buttonpress may have happened before we entered draw context! - if (pc->ps.empty()) { - // Only in freehand mode we have to add the first point also to pc->ps (apparently) - // - We cannot add this point in spdc_set_startpoint, because we only need it for freehand - // - We cannot do this in the button press handler because at that point we don't know yet - // wheter we're going into freehand mode or not - pc->ps.push_back(pc->p[0]); - } - spdc_add_freehand_point(pc, p, mevent.state); - ret = TRUE; - } - - if (anchor && !pc->anchor_statusbar) { - pc->message_context->set(Inkscape::NORMAL_MESSAGE, _("Release here to close and finish the path.")); - pc->anchor_statusbar = true; - } else if (!anchor && pc->anchor_statusbar) { - pc->message_context->clear(); - pc->anchor_statusbar = false; - } else if (!anchor) { - pc->message_context->set(Inkscape::NORMAL_MESSAGE, _("Drawing a freehand path")); - } - - } else { - if (anchor && !pc->anchor_statusbar) { - pc->message_context->set(Inkscape::NORMAL_MESSAGE, _("Drag to continue the path from this point.")); - pc->anchor_statusbar = true; - } else if (!anchor && pc->anchor_statusbar) { - pc->message_context->clear(); - pc->anchor_statusbar = false; - } - } - - // Show the pre-snap indicator to communicate to the user where we would snap to if he/she were to - // a) press the mousebutton to start a freehand drawing, or - // b) release the mousebutton to finish a freehand drawing - if (!sp_event_context_knot_mouseover(pc)) { - SnapManager &m = dt->namedview->snap_manager; - m.setup(dt); - m.preSnap(Inkscape::SnapCandidatePoint(p, Inkscape::SNAPSOURCE_NODE_HANDLE)); - m.unSetup(); - } - break; - } - return ret; -} - -static gint -pencil_handle_button_release(PencilTool *const pc, GdkEventButton const &revent) -{ - gint ret = FALSE; - - ToolBase *event_context = SP_EVENT_CONTEXT(pc); - if ( revent.button == 1 && pc->is_drawing && !event_context->space_panning) { - SPDesktop *const dt = pc->desktop; - - pc->is_drawing = false; - - /* Find desktop coordinates */ - Geom::Point p = dt->w2d(Geom::Point(revent.x, revent.y)); - - /* Test whether we hit any anchor. */ - SPDrawAnchor *anchor = spdc_test_inside(pc, Geom::Point(revent.x, - revent.y)); - - switch (pc->state) { - case SP_PENCIL_CONTEXT_IDLE: - /* Releasing button in idle mode means single click */ - /* We have already set up start point/anchor in button_press */ - if (!(revent.state & GDK_CONTROL_MASK)) { - // Ctrl+click creates a single point so only set context in ADDLINE mode when Ctrl isn't pressed - pc->state = SP_PENCIL_CONTEXT_ADDLINE; - } - ret = TRUE; - break; - case SP_PENCIL_CONTEXT_ADDLINE: - /* Finish segment now */ - if (anchor) { - p = anchor->dp; - } else { - spdc_endpoint_snap(pc, p, revent.state); - } - pc->ea = anchor; - spdc_set_endpoint(pc, p); - spdc_finish_endpoint(pc); - pc->state = SP_PENCIL_CONTEXT_IDLE; - sp_event_context_discard_delayed_snap_event(event_context); - ret = TRUE; - break; - case SP_PENCIL_CONTEXT_FREEHAND: - if (revent.state & GDK_MOD1_MASK) { - /* sketch mode: interpolate the sketched path and improve the current output path with the new interpolation. don't finish sketch */ - - sketch_interpolate(pc); - - if (pc->green_anchor) { - pc->green_anchor = sp_draw_anchor_destroy(pc->green_anchor); - } - - pc->state = SP_PENCIL_CONTEXT_SKETCH; - } else { - /* Finish segment now */ - /// \todo fixme: Clean up what follows (Lauris) - if (anchor) { - p = anchor->dp; - } else { - Geom::Point p_end = p; - spdc_endpoint_snap(pc, p_end, revent.state); - if (p_end != p) { - // then we must have snapped! - spdc_add_freehand_point(pc, p_end, revent.state); - } - } - - pc->ea = anchor; - /* Write curves to object */ - - dt->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("Finishing freehand")); - - interpolate(pc); - spdc_concat_colors_and_flush(pc, FALSE); - pc->sa = NULL; - pc->ea = NULL; - if (pc->green_anchor) { - pc->green_anchor = sp_draw_anchor_destroy(pc->green_anchor); - } - pc->state = SP_PENCIL_CONTEXT_IDLE; - // reset sketch mode too - pc->sketch_n = 0; - } - ret = TRUE; - break; - case SP_PENCIL_CONTEXT_SKETCH: - default: - break; - } - - if (pc->grab) { - /* Release grab now */ - sp_canvas_item_ungrab(pc->grab, revent.time); - pc->grab = NULL; - } - - ret = TRUE; - } - return ret; -} - -static void -pencil_cancel (PencilTool *const pc) -{ - if (pc->grab) { - /* Release grab now */ - sp_canvas_item_ungrab(pc->grab, 0); - pc->grab = NULL; - } - - pc->is_drawing = false; - pc->state = SP_PENCIL_CONTEXT_IDLE; - sp_event_context_discard_delayed_snap_event(SP_EVENT_CONTEXT(pc)); - - pc->red_curve->reset(); - sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(pc->red_bpath), NULL); - while (pc->green_bpaths) { - sp_canvas_item_destroy(SP_CANVAS_ITEM(pc->green_bpaths->data)); - pc->green_bpaths = g_slist_remove(pc->green_bpaths, pc->green_bpaths->data); - } - pc->green_curve->reset(); - if (pc->green_anchor) { - pc->green_anchor = sp_draw_anchor_destroy(pc->green_anchor); - } - - pc->message_context->clear(); - pc->message_context->flash(Inkscape::NORMAL_MESSAGE, _("Drawing cancelled")); - - pc->desktop->canvas->endForcedFullRedraws(); -} - -static gint -pencil_handle_key_press(PencilTool *const pc, guint const keyval, guint const state) -{ - gint ret = FALSE; - switch (keyval) { - case GDK_KEY_Up: - case GDK_KEY_Down: - case GDK_KEY_KP_Up: - case GDK_KEY_KP_Down: - // Prevent the zoom field from activation. - if (!mod_ctrl_only(state)) { - ret = TRUE; - } - break; - case GDK_KEY_Escape: - if (pc->npoints != 0) { - // if drawing, cancel, otherwise pass it up for deselecting - if (pc->state != SP_PENCIL_CONTEXT_IDLE) { - pencil_cancel (pc); - ret = TRUE; - } - } - break; - case GDK_KEY_z: - case GDK_KEY_Z: - if (mod_ctrl_only(state) && pc->npoints != 0) { - // if drawing, cancel, otherwise pass it up for undo - if (pc->state != SP_PENCIL_CONTEXT_IDLE) { - pencil_cancel (pc); - ret = TRUE; - } - } - break; - case GDK_KEY_g: - case GDK_KEY_G: - if (mod_shift_only(state)) { - sp_selection_to_guides(SP_EVENT_CONTEXT(pc)->desktop); - ret = true; - } - break; - case GDK_KEY_Alt_L: - case GDK_KEY_Alt_R: - case GDK_KEY_Meta_L: - case GDK_KEY_Meta_R: - if (pc->state == SP_PENCIL_CONTEXT_IDLE) { - pc->desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("Sketch mode: holding Alt interpolates between sketched paths. Release Alt to finalize.")); - } - break; - default: - break; - } - return ret; -} - -static gint -pencil_handle_key_release(PencilTool *const pc, guint const keyval, guint const /*state*/) -{ - gint ret = FALSE; - switch (keyval) { - case GDK_KEY_Alt_L: - case GDK_KEY_Alt_R: - case GDK_KEY_Meta_L: - case GDK_KEY_Meta_R: - if (pc->state == SP_PENCIL_CONTEXT_SKETCH) { - spdc_concat_colors_and_flush(pc, FALSE); - pc->sketch_n = 0; - pc->sa = NULL; - pc->ea = NULL; - if (pc->green_anchor) { - pc->green_anchor = sp_draw_anchor_destroy(pc->green_anchor); - } - pc->state = SP_PENCIL_CONTEXT_IDLE; - sp_event_context_discard_delayed_snap_event(SP_EVENT_CONTEXT(pc)); - pc->desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("Finishing freehand sketch")); - ret = TRUE; - } - break; - default: - break; - } - return ret; -} - -/** - * Reset points and set new starting point. - */ -static void -spdc_set_startpoint(PencilTool *const pc, Geom::Point const &p) -{ - pc->npoints = 0; - pc->red_curve_is_valid = false; - if (in_svg_plane(p)) { - pc->p[pc->npoints++] = p; - } -} - -/** - * Change moving endpoint position. - *
    - *
  • Ctrl constrains to moving to H/V direction, snapping in given direction. - *
  • Otherwise we snap freely to whatever attractors are available. - *
- * - * Number of points is (re)set to 2 always, 2nd point is modified. - * We change RED curve. - */ -static void -spdc_set_endpoint(PencilTool *const pc, Geom::Point const &p) -{ - if (pc->npoints == 0) { - return; - /* May occur if first point wasn't in SVG plane (e.g. weird w2d transform, perhaps from bad - * zoom setting). - */ - } - g_return_if_fail( pc->npoints > 0 ); - - pc->red_curve->reset(); - if ( ( p == pc->p[0] ) - || !in_svg_plane(p) ) - { - pc->npoints = 1; - } else { - pc->p[1] = p; - pc->npoints = 2; - - pc->red_curve->moveto(pc->p[0]); - pc->red_curve->lineto(pc->p[1]); - pc->red_curve_is_valid = true; - - sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(pc->red_bpath), pc->red_curve); - } -} - -/** - * Finalize addline. - * - * \todo - * fixme: I'd like remove red reset from concat colors (lauris). - * Still not sure, how it will make most sense. - */ -static void -spdc_finish_endpoint(PencilTool *const pc) -{ - if ( ( pc->red_curve->is_empty() ) - || ( *(pc->red_curve->first_point()) == *(pc->red_curve->second_point()) ) ) - { - pc->red_curve->reset(); - sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(pc->red_bpath), NULL); - } else { - /* Write curves to object. */ - spdc_concat_colors_and_flush(pc, FALSE); - pc->sa = NULL; - pc->ea = NULL; - } -} - - -static void -spdc_add_freehand_point(PencilTool *pc, Geom::Point const &p, guint /*state*/) -{ - g_assert( pc->npoints > 0 ); - g_return_if_fail(unsigned(pc->npoints) < G_N_ELEMENTS(pc->p)); - - if ( ( p != pc->p[ pc->npoints - 1 ] ) - && in_svg_plane(p) ) - { - pc->ps.push_back(p); - pc->p[pc->npoints++] = p; - fit_and_split(pc); - } -} - -static inline double -square(double const x) -{ - return x * x; -} - -static void -interpolate(PencilTool *pc) -{ - if ( pc->ps.size() <= 1 ) { - return; - } - - Inkscape::Preferences *prefs = Inkscape::Preferences::get(); - double const tol = prefs->getDoubleLimited("/tools/freehand/pencil/tolerance", 10.0, 1.0, 100.0) * 0.4; - double const tolerance_sq = 0.02 * square( pc->desktop->w2d().descrim() * - tol) * exp(0.2*tol - 2); - - g_assert(is_zero(pc->req_tangent) - || is_unit_vector(pc->req_tangent)); - Geom::Point const tHatEnd(0, 0); - - guint n_points = pc->ps.size(); - pc->green_curve->reset(); - pc->red_curve->reset(); - pc->red_curve_is_valid = false; - - Geom::Point * b = g_new(Geom::Point, 4*n_points); - Geom::Point * points = g_new(Geom::Point, 4*n_points); - for (unsigned int i = 0; i < pc->ps.size(); i++) { - points[i] = pc->ps[i]; - } - - // worst case gives us a segment per point - int max_segs = 4*n_points; - - int const n_segs = Geom::bezier_fit_cubic_r(b, points, n_points, - tolerance_sq, max_segs); - - if ( n_segs > 0) - { - /* Fit and draw and reset state */ - pc->green_curve->moveto(b[0]); - for (int c = 0; c < n_segs; c++) { - pc->green_curve->curveto(b[4*c+1], b[4*c+2], b[4*c+3]); - } - sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(pc->red_bpath), pc->green_curve); - - /* Fit and draw and copy last point */ - g_assert(!pc->green_curve->is_empty()); - - /* Set up direction of next curve. */ - { - Geom::Curve const * last_seg = pc->green_curve->last_segment(); - g_assert( last_seg ); // Relevance: validity of (*last_seg) - pc->p[0] = last_seg->finalPoint(); - pc->npoints = 1; - Geom::Curve *last_seg_reverse = last_seg->reverse(); - Geom::Point const req_vec( -last_seg_reverse->unitTangentAt(0) ); - delete last_seg_reverse; - pc->req_tangent = ( ( Geom::is_zero(req_vec) || !in_svg_plane(req_vec) ) - ? Geom::Point(0, 0) - : Geom::unit_vector(req_vec) ); - } - } - g_free(b); - g_free(points); - pc->ps.clear(); -} - - -/* interpolates the sketched curve and tweaks the current sketch interpolation*/ -static void -sketch_interpolate(PencilTool *pc) -{ - if ( pc->ps.size() <= 1 ) { - return; - } - - Inkscape::Preferences *prefs = Inkscape::Preferences::get(); - double const tol = prefs->getDoubleLimited("/tools/freehand/pencil/tolerance", 10.0, 1.0, 100.0) * 0.4; - double const tolerance_sq = 0.02 * square( pc->desktop->w2d().descrim() * - tol) * exp(0.2*tol - 2); - - bool average_all_sketches = prefs->getBool("/tools/freehand/pencil/average_all_sketches", true); - - g_assert(is_zero(pc->req_tangent) - || is_unit_vector(pc->req_tangent)); - Geom::Point const tHatEnd(0, 0); - - guint n_points = pc->ps.size(); - pc->red_curve->reset(); - pc->red_curve_is_valid = false; - - Geom::Point * b = g_new(Geom::Point, 4*n_points); - Geom::Point * points = g_new(Geom::Point, 4*n_points); - for (unsigned i = 0; i < pc->ps.size(); i++) { - points[i] = pc->ps[i]; - } - - // worst case gives us a segment per point - int max_segs = 4*n_points; - - int const n_segs = Geom::bezier_fit_cubic_r(b, points, n_points, - tolerance_sq, max_segs); - - if ( n_segs > 0) - { - Geom::Path fit(b[0]); - for (int c = 0; c < n_segs; c++) { - fit.appendNew(b[4*c+1], b[4*c+2], b[4*c+3]); - } - Geom::Piecewise > fit_pwd2 = fit.toPwSb(); - - if ( pc->sketch_n > 0 ) { - double t =0.; - if (average_all_sketches) { - // Average = (sum of all) / n - // = (sum of all + new one) / n+1 - // = ((old average)*n + new one) / n+1 - t = pc->sketch_n / (pc->sketch_n + 1.); - } else { - t = 0.5; - } - pc->sketch_interpolation = Geom::lerp(t, fit_pwd2, pc->sketch_interpolation); - // simplify path, to eliminate small segments - Path *path = new Path; - path->LoadPathVector(Geom::path_from_piecewise(pc->sketch_interpolation, 0.01)); - path->Simplify(0.5); - Geom::PathVector *pathv = path->MakePathVector(); - pc->sketch_interpolation = (*pathv)[0].toPwSb(); - delete path; - delete pathv; - } else { - pc->sketch_interpolation = fit_pwd2; - } - pc->sketch_n++; - - pc->green_curve->reset(); - pc->green_curve->set_pathvector(Geom::path_from_piecewise(pc->sketch_interpolation, 0.01)); - sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(pc->red_bpath), pc->green_curve); - - /* Fit and draw and copy last point */ - g_assert(!pc->green_curve->is_empty()); - - /* Set up direction of next curve. */ - { - Geom::Curve const * last_seg = pc->green_curve->last_segment(); - g_assert( last_seg ); // Relevance: validity of (*last_seg) - pc->p[0] = last_seg->finalPoint(); - pc->npoints = 1; - Geom::Curve *last_seg_reverse = last_seg->reverse(); - Geom::Point const req_vec( -last_seg_reverse->unitTangentAt(0) ); - delete last_seg_reverse; - pc->req_tangent = ( ( Geom::is_zero(req_vec) || !in_svg_plane(req_vec) ) - ? Geom::Point(0, 0) - : Geom::unit_vector(req_vec) ); - } - } - g_free(b); - g_free(points); - pc->ps.clear(); -} - -static void -fit_and_split(PencilTool *pc) -{ - g_assert( pc->npoints > 1 ); - - double const tolerance_sq = 0; - - Geom::Point b[4]; - g_assert(is_zero(pc->req_tangent) - || is_unit_vector(pc->req_tangent)); - Geom::Point const tHatEnd(0, 0); - int const n_segs = Geom::bezier_fit_cubic_full(b, NULL, pc->p, pc->npoints, - pc->req_tangent, tHatEnd, - tolerance_sq, 1); - if ( n_segs > 0 - && unsigned(pc->npoints) < G_N_ELEMENTS(pc->p) ) - { - /* Fit and draw and reset state */ - pc->red_curve->reset(); - pc->red_curve->moveto(b[0]); - pc->red_curve->curveto(b[1], b[2], b[3]); - sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(pc->red_bpath), pc->red_curve); - pc->red_curve_is_valid = true; - } else { - /* Fit and draw and copy last point */ - - g_assert(!pc->red_curve->is_empty()); - - /* Set up direction of next curve. */ - { - Geom::Curve const * last_seg = pc->red_curve->last_segment(); - g_assert( last_seg ); // Relevance: validity of (*last_seg) - pc->p[0] = last_seg->finalPoint(); - pc->npoints = 1; - Geom::Curve *last_seg_reverse = last_seg->reverse(); - Geom::Point const req_vec( -last_seg_reverse->unitTangentAt(0) ); - delete last_seg_reverse; - pc->req_tangent = ( ( Geom::is_zero(req_vec) || !in_svg_plane(req_vec) ) - ? Geom::Point(0, 0) - : Geom::unit_vector(req_vec) ); - } - - - pc->green_curve->append_continuous(pc->red_curve, 0.0625); - SPCurve *curve = pc->red_curve->copy(); - - /// \todo fixme: - SPCanvasItem *cshape = sp_canvas_bpath_new(sp_desktop_sketch(pc->desktop), curve); - curve->unref(); - sp_canvas_bpath_set_stroke(SP_CANVAS_BPATH(cshape), pc->green_color, 1.0, SP_STROKE_LINEJOIN_MITER, SP_STROKE_LINECAP_BUTT); - - pc->green_bpaths = g_slist_prepend(pc->green_bpaths, cshape); - - pc->red_curve_is_valid = false; - } -} - -} -} -} - -/* - Local Variables: - mode:c++ - c-file-style:"stroustrup" - c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) - indent-tabs-mode:nil - fill-column:99 - End: -*/ -// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/pencil-context.h b/src/pencil-context.h deleted file mode 100644 index ad6ed23b9..000000000 --- a/src/pencil-context.h +++ /dev/null @@ -1,68 +0,0 @@ -#ifndef SEEN_PENCIL_CONTEXT_H -#define SEEN_PENCIL_CONTEXT_H - -/** \file - * PencilTool: a context for pencil tool events - */ - -#include "draw-context.h" - -#define SP_PENCIL_CONTEXT(obj) (dynamic_cast((ToolBase*)obj)) -#define SP_IS_PENCIL_CONTEXT(obj) (dynamic_cast((const ToolBase*)obj) != NULL) - -namespace Inkscape { -namespace UI { -namespace Tools { - -enum PencilState { - SP_PENCIL_CONTEXT_IDLE, - SP_PENCIL_CONTEXT_ADDLINE, - SP_PENCIL_CONTEXT_FREEHAND, - SP_PENCIL_CONTEXT_SKETCH -}; - -/** - * PencilTool: a context for pencil tool events - */ -class PencilTool : public SPDrawContext { -public: - PencilTool(); - virtual ~PencilTool(); - - Geom::Point p[16]; - gint npoints; - PencilState state; - Geom::Point req_tangent; - - bool is_drawing; - - std::vector ps; - - Geom::Piecewise > sketch_interpolation; // the current proposal from the sketched paths - unsigned sketch_n; // number of sketches done - - static const std::string prefsPath; - - virtual const std::string& getPrefsPath(); - -protected: - virtual void setup(); - virtual bool root_handler(GdkEvent* event); -}; - -} -} -} - -#endif /* !SEEN_PENCIL_CONTEXT_H */ - -/* - Local Variables: - mode:c++ - c-file-style:"stroustrup" - c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) - indent-tabs-mode:nil - fill-column:99 - End: -*/ -// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/persp3d.cpp b/src/persp3d.cpp index a737c0634..b10e5f23b 100644 --- a/src/persp3d.cpp +++ b/src/persp3d.cpp @@ -17,7 +17,7 @@ #include "document-private.h" #include "document-undo.h" #include "vanishing-point.h" -#include "box3d-context.h" +#include "ui/tools/box3d-tool.h" #include "box3d.h" #include "xml/document.h" #include "xml/node-event-vector.h" diff --git a/src/rect-context.cpp b/src/rect-context.cpp deleted file mode 100644 index 95a7f1f2d..000000000 --- a/src/rect-context.cpp +++ /dev/null @@ -1,527 +0,0 @@ -/* - * Rectangle drawing context - * - * Author: - * Lauris Kaplinski - * bulia byak - * Jon A. Cruz - * Abhishek Sharma - * - * Copyright (C) 2006 Johan Engelen - * Copyright (C) 2000-2005 authors - * Copyright (C) 2000-2001 Ximian, Inc. - * - * Released under GNU GPL, read the file 'COPYING' for more information - */ - -#include "config.h" - -#include -#include -#include - -#include "macros.h" -#include "display/sp-canvas.h" -#include "sp-rect.h" -#include "document.h" -#include "document-undo.h" -#include "sp-namedview.h" -#include "selection.h" -#include "selection-chemistry.h" -#include "desktop-handles.h" -#include "snap.h" -#include "desktop.h" -#include "desktop-style.h" -#include "message-context.h" -#include "pixmaps/cursor-rect.xpm" -#include "rect-context.h" -#include -#include "xml/repr.h" -#include "xml/node-event-vector.h" -#include "preferences.h" -#include "context-fns.h" -#include "shape-editor.h" -#include "verbs.h" -#include "display/sp-canvas-item.h" - -using Inkscape::DocumentUndo; - -#include "tool-factory.h" - -namespace Inkscape { -namespace UI { -namespace Tools { - -namespace { - ToolBase* createRectContext() { - return new RectTool(); - } - - bool rectContextRegistered = ToolFactory::instance().registerObject("/tools/shapes/rect", createRectContext); -} - -const std::string& RectTool::getPrefsPath() { - return RectTool::prefsPath; -} - -const std::string RectTool::prefsPath = "/tools/shapes/rect"; - -RectTool::RectTool() : ToolBase() { - this->cursor_shape = cursor_rect_xpm; - this->hot_x = 4; - this->hot_y = 4; - this->xp = 0; - this->yp = 0; - this->tolerance = 0; - this->within_tolerance = false; - this->item_to_select = NULL; - - this->rect = NULL; - - this->rx = 0.0; - this->ry = 0.0; -} - -void RectTool::finish() { - sp_canvas_item_ungrab(SP_CANVAS_ITEM(this->desktop->acetate), GDK_CURRENT_TIME); - - this->finishItem(); - this->sel_changed_connection.disconnect(); - - ToolBase::finish(); -} - -RectTool::~RectTool() { - this->enableGrDrag(false); - - this->sel_changed_connection.disconnect(); - - delete this->shape_editor; - this->shape_editor = NULL; - - /* fixme: This is necessary because we do not grab */ - if (this->rect) { - this->finishItem(); - } -} - -/** - * Callback that processes the "changed" signal on the selection; - * destroys old and creates new knotholder. - */ -void RectTool::selection_changed(Inkscape::Selection* selection) { - this->shape_editor->unset_item(SH_KNOTHOLDER); - this->shape_editor->set_item(selection->singleItem(), SH_KNOTHOLDER); -} - -void RectTool::setup() { - ToolBase::setup(); - - this->shape_editor = new ShapeEditor(this->desktop); - - SPItem *item = sp_desktop_selection(this->desktop)->singleItem(); - if (item) { - this->shape_editor->set_item(item, SH_KNOTHOLDER); - } - - this->sel_changed_connection.disconnect(); - this->sel_changed_connection = sp_desktop_selection(this->desktop)->connectChanged( - sigc::mem_fun(this, &RectTool::selection_changed) - ); - - sp_event_context_read(this, "rx"); - sp_event_context_read(this, "ry"); - - Inkscape::Preferences *prefs = Inkscape::Preferences::get(); - if (prefs->getBool("/tools/shapes/selcue")) { - this->enableSelectionCue(); - } - - if (prefs->getBool("/tools/shapes/gradientdrag")) { - this->enableGrDrag(); - } -} - -void RectTool::set(const Inkscape::Preferences::Entry& val) { - /* fixme: Proper error handling for non-numeric data. Use a locale-independent function like - * g_ascii_strtod (or a thin wrapper that does the right thing for invalid values inf/nan). */ - Glib::ustring name = val.getEntryName(); - - if ( name == "rx" ) { - this->rx = val.getDoubleLimited(); // prevents NaN and +/-Inf from messing up - } else if ( name == "ry" ) { - this->ry = val.getDoubleLimited(); - } -} - -bool RectTool::item_handler(SPItem* item, GdkEvent* event) { - gint ret = FALSE; - - switch (event->type) { - case GDK_BUTTON_PRESS: - if ( event->button.button == 1 && !this->space_panning) { - Inkscape::setup_for_drag_start(desktop, this, event); - ret = TRUE; - } - break; - // motion and release are always on root (why?) - default: - break; - } - - ret = ToolBase::item_handler(item, event); - - return ret; -} - -bool RectTool::root_handler(GdkEvent* event) { - static bool dragging; - - SPDesktop *desktop = this->desktop; - Inkscape::Selection *selection = sp_desktop_selection (desktop); - - Inkscape::Preferences *prefs = Inkscape::Preferences::get(); - - this->tolerance = prefs->getIntLimited("/options/dragtolerance/value", 0, 0, 100); - - gint ret = FALSE; - - switch (event->type) { - case GDK_BUTTON_PRESS: - if (event->button.button == 1 && !this->space_panning) { - Geom::Point const button_w(event->button.x, event->button.y); - - // save drag origin - this->xp = (gint) button_w[Geom::X]; - this->yp = (gint) button_w[Geom::Y]; - this->within_tolerance = true; - - // remember clicked item, disregarding groups, honoring Alt - this->item_to_select = sp_event_context_find_item (desktop, button_w, event->button.state & GDK_MOD1_MASK, TRUE); - - dragging = true; - - /* Position center */ - Geom::Point button_dt(desktop->w2d(button_w)); - this->center = button_dt; - - /* Snap center */ - SnapManager &m = desktop->namedview->snap_manager; - m.setup(desktop); - m.freeSnapReturnByRef(button_dt, Inkscape::SNAPSOURCE_NODE_HANDLE); - m.unSetup(); - this->center = button_dt; - - sp_canvas_item_grab(SP_CANVAS_ITEM(desktop->acetate), - ( GDK_KEY_PRESS_MASK | - GDK_BUTTON_RELEASE_MASK | - GDK_POINTER_MOTION_MASK | - GDK_POINTER_MOTION_HINT_MASK | - GDK_BUTTON_PRESS_MASK ), - NULL, event->button.time); - - ret = TRUE; - } - break; - case GDK_MOTION_NOTIFY: - if ( dragging - && (event->motion.state & GDK_BUTTON1_MASK) && !this->space_panning) - { - if ( this->within_tolerance - && ( abs( (gint) event->motion.x - this->xp ) < this->tolerance ) - && ( abs( (gint) event->motion.y - this->yp ) < this->tolerance ) ) { - break; // do not drag if we're within tolerance from origin - } - // Once the user has moved farther than tolerance from the original location - // (indicating they intend to draw, not click), then always process the - // motion notify coordinates as given (no snapping back to origin) - this->within_tolerance = false; - - Geom::Point const motion_w(event->motion.x, event->motion.y); - Geom::Point motion_dt(desktop->w2d(motion_w)); - - this->drag(motion_dt, event->motion.state); // this will also handle the snapping - gobble_motion_events(GDK_BUTTON1_MASK); - ret = TRUE; - } else if (!sp_event_context_knot_mouseover(this)) { - SnapManager &m = desktop->namedview->snap_manager; - m.setup(desktop); - - Geom::Point const motion_w(event->motion.x, event->motion.y); - Geom::Point motion_dt(desktop->w2d(motion_w)); - - m.preSnap(Inkscape::SnapCandidatePoint(motion_dt, Inkscape::SNAPSOURCE_NODE_HANDLE)); - m.unSetup(); - } - break; - case GDK_BUTTON_RELEASE: - this->xp = this->yp = 0; - if (event->button.button == 1 && !this->space_panning) { - dragging = false; - sp_event_context_discard_delayed_snap_event(this); - - if (!this->within_tolerance) { - // we've been dragging, finish the rect - this->finishItem(); - } else if (this->item_to_select) { - // no dragging, select clicked item if any - if (event->button.state & GDK_SHIFT_MASK) { - selection->toggle(this->item_to_select); - } else { - selection->set(this->item_to_select); - } - } else { - // click in an empty space - selection->clear(); - } - - this->item_to_select = NULL; - ret = TRUE; - sp_canvas_item_ungrab(SP_CANVAS_ITEM(desktop->acetate), - event->button.time); - } - break; - case GDK_KEY_PRESS: - switch (get_group0_keyval (&event->key)) { - case GDK_KEY_Alt_L: - case GDK_KEY_Alt_R: - case GDK_KEY_Control_L: - case GDK_KEY_Control_R: - case GDK_KEY_Shift_L: - case GDK_KEY_Shift_R: - case GDK_KEY_Meta_L: // Meta is when you press Shift+Alt (at least on my machine) - case GDK_KEY_Meta_R: - if (!dragging){ - sp_event_show_modifier_tip (this->defaultMessageContext(), event, - _("Ctrl: make square or integer-ratio rect, lock a rounded corner circular"), - _("Shift: draw around the starting point"), - NULL); - } - break; - case GDK_KEY_Up: - case GDK_KEY_Down: - case GDK_KEY_KP_Up: - case GDK_KEY_KP_Down: - // prevent the zoom field from activation - if (!MOD__CTRL_ONLY(event)) - ret = TRUE; - break; - - case GDK_KEY_x: - case GDK_KEY_X: - if (MOD__ALT_ONLY(event)) { - desktop->setToolboxFocusTo ("altx-rect"); - ret = TRUE; - } - break; - - case GDK_KEY_g: - case GDK_KEY_G: - if (MOD__SHIFT_ONLY(event)) { - sp_selection_to_guides(desktop); - ret = true; - } - break; - - case GDK_KEY_Escape: - if (dragging) { - dragging = false; - sp_event_context_discard_delayed_snap_event(this); - // if drawing, cancel, otherwise pass it up for deselecting - this->cancel(); - ret = TRUE; - } - break; - - case GDK_KEY_space: - if (dragging) { - sp_canvas_item_ungrab(SP_CANVAS_ITEM(desktop->acetate), - event->button.time); - dragging = false; - sp_event_context_discard_delayed_snap_event(this); - - if (!this->within_tolerance) { - // we've been dragging, finish the rect - this->finishItem(); - } - // do not return true, so that space would work switching to selector - } - break; - - case GDK_KEY_Delete: - case GDK_KEY_KP_Delete: - case GDK_KEY_BackSpace: - ret = this->deleteSelectedDrag(MOD__CTRL_ONLY(event)); - break; - - default: - break; - } - break; - case GDK_KEY_RELEASE: - switch (get_group0_keyval (&event->key)) { - case GDK_KEY_Alt_L: - case GDK_KEY_Alt_R: - case GDK_KEY_Control_L: - case GDK_KEY_Control_R: - case GDK_KEY_Shift_L: - case GDK_KEY_Shift_R: - case GDK_KEY_Meta_L: // Meta is when you press Shift+Alt - case GDK_KEY_Meta_R: - this->defaultMessageContext()->clear(); - break; - default: - break; - } - break; - default: - break; - } - - if (!ret) { - ret = ToolBase::root_handler(event); - } - - return ret; -} - -void RectTool::drag(Geom::Point const pt, guint state) { - SPDesktop *desktop = this->desktop; - - if (!this->rect) { - if (Inkscape::have_viable_layer(desktop, this->message_context) == false) { - return; - } - - // Create object - Inkscape::XML::Document *xml_doc = this->desktop->doc()->getReprDoc(); - Inkscape::XML::Node *repr = xml_doc->createElement("svg:rect"); - - // Set style - sp_desktop_apply_style_tool (desktop, repr, "/tools/shapes/rect", false); - - this->rect = SP_RECT(desktop->currentLayer()->appendChildRepr(repr)); - Inkscape::GC::release(repr); - - this->rect->transform = SP_ITEM(desktop->currentLayer())->i2doc_affine().inverse(); - this->rect->updateRepr(); - - desktop->canvas->forceFullRedrawAfterInterruptions(5); - } - - Geom::Rect const r = Inkscape::snap_rectangular_box(desktop, this->rect, pt, this->center, state); - - this->rect->setPosition(r.min()[Geom::X], r.min()[Geom::Y], r.dimensions()[Geom::X], r.dimensions()[Geom::Y]); - - if (this->rx != 0.0) { - this->rect->setRx(true, this->rx); - } - - if (this->ry != 0.0) { - if (this->rx == 0.0) - this->rect->setRy(true, CLAMP(this->ry, 0, MIN(r.dimensions()[Geom::X], r.dimensions()[Geom::Y])/2)); - else - this->rect->setRy(true, CLAMP(this->ry, 0, r.dimensions()[Geom::Y])); - } - - // status text - double rdimx = r.dimensions()[Geom::X]; - double rdimy = r.dimensions()[Geom::Y]; - - Inkscape::Util::Quantity rdimx_q = Inkscape::Util::Quantity(rdimx, "px"); - Inkscape::Util::Quantity rdimy_q = Inkscape::Util::Quantity(rdimy, "px"); - GString *xs = g_string_new(rdimx_q.string(desktop->namedview->doc_units).c_str()); - GString *ys = g_string_new(rdimy_q.string(desktop->namedview->doc_units).c_str()); - - if (state & GDK_CONTROL_MASK) { - int ratio_x, ratio_y; - bool is_golden_ratio = false; - - if (fabs (rdimx) > fabs (rdimy)) { - if (fabs(rdimx / rdimy - goldenratio) < 1e-6) { - is_golden_ratio = true; - } - - ratio_x = (int) rint (rdimx / rdimy); - ratio_y = 1; - } else { - if (fabs(rdimy / rdimx - goldenratio) < 1e-6) { - is_golden_ratio = true; - } - - ratio_x = 1; - ratio_y = (int) rint (rdimy / rdimx); - } - - if (!is_golden_ratio) { - this->message_context->setF(Inkscape::IMMEDIATE_MESSAGE, _("Rectangle: %s × %s (constrained to ratio %d:%d); with Shift to draw around the starting point"), xs->str, ys->str, ratio_x, ratio_y); - } else { - if (ratio_y == 1) { - this->message_context->setF(Inkscape::IMMEDIATE_MESSAGE, _("Rectangle: %s × %s (constrained to golden ratio 1.618 : 1); with Shift to draw around the starting point"), xs->str, ys->str); - } else { - this->message_context->setF(Inkscape::IMMEDIATE_MESSAGE, _("Rectangle: %s × %s (constrained to golden ratio 1 : 1.618); with Shift to draw around the starting point"), xs->str, ys->str); - } - } - } else { - this->message_context->setF(Inkscape::IMMEDIATE_MESSAGE, _("Rectangle: %s × %s; with Ctrl to make square or integer-ratio rectangle; with Shift to draw around the starting point"), xs->str, ys->str); - } - - g_string_free(xs, FALSE); - g_string_free(ys, FALSE); -} - -void RectTool::finishItem() { - this->message_context->clear(); - - if (this->rect != NULL) { - if (this->rect->width.computed == 0 || this->rect->height.computed == 0) { - this->cancel(); // Don't allow the creating of zero sized rectangle, for example when the start and and point snap to the snap grid point - return; - } - - this->rect->updateRepr(); - this->rect->doWriteTransform(this->rect->getRepr(), this->rect->transform, NULL, true); - - this->desktop->canvas->endForcedFullRedraws(); - - sp_desktop_selection(this->desktop)->set(this->rect); - - DocumentUndo::done(sp_desktop_document(this->desktop), SP_VERB_CONTEXT_RECT, _("Create rectangle")); - - this->rect = NULL; - } -} - -void RectTool::cancel(){ - sp_desktop_selection(this->desktop)->clear(); - sp_canvas_item_ungrab(SP_CANVAS_ITEM(this->desktop->acetate), 0); - - if (this->rect != NULL) { - this->rect->deleteObject(); - this->rect = NULL; - } - - this->within_tolerance = false; - this->xp = 0; - this->yp = 0; - this->item_to_select = NULL; - - this->desktop->canvas->endForcedFullRedraws(); - - DocumentUndo::cancel(sp_desktop_document(this->desktop)); -} - -} -} -} - -/* - Local Variables: - mode:c++ - c-file-style:"stroustrup" - c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) - indent-tabs-mode:nil - fill-column:99 - End: -*/ -// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/rect-context.h b/src/rect-context.h deleted file mode 100644 index cf1682620..000000000 --- a/src/rect-context.h +++ /dev/null @@ -1,65 +0,0 @@ -#ifndef __SP_RECT_CONTEXT_H__ -#define __SP_RECT_CONTEXT_H__ - -/* - * Rectangle drawing context - * - * Author: - * Lauris Kaplinski - * - * Copyright (C) 2000 Lauris Kaplinski - * Copyright (C) 2000-2001 Ximian, Inc. - * Copyright (C) 2002 Lauris Kaplinski - * - * Released under GNU GPL - */ - -#include -#include -#include <2geom/point.h> -#include "event-context.h" - -#include "sp-rect.h" - -#define SP_RECT_CONTEXT(obj) (dynamic_cast((Inkscape::UI::Tools::ToolBase*)obj)) -#define SP_IS_RECT_CONTEXT(obj) (dynamic_cast((const Inkscape::UI::Tools::ToolBase*)obj) != NULL) - -namespace Inkscape { -namespace UI { -namespace Tools { - -class RectTool : public ToolBase { -public: - RectTool(); - virtual ~RectTool(); - - static const std::string prefsPath; - - virtual void setup(); - virtual void finish(); - virtual void set(const Inkscape::Preferences::Entry& val); - virtual bool root_handler(GdkEvent* event); - virtual bool item_handler(SPItem* item, GdkEvent* event); - - virtual const std::string& getPrefsPath(); - -private: - SPRect *rect; - Geom::Point center; - - gdouble rx; /* roundness radius (x direction) */ - gdouble ry; /* roundness radius (y direction) */ - - sigc::connection sel_changed_connection; - - void drag(Geom::Point const pt, guint state); - void finishItem(); - void cancel(); - void selection_changed(Inkscape::Selection* selection); -}; - -} -} -} - -#endif diff --git a/src/select-context.cpp b/src/select-context.cpp deleted file mode 100644 index 11b2e6298..000000000 --- a/src/select-context.cpp +++ /dev/null @@ -1,1252 +0,0 @@ -/* - * Selection and transformation context - * - * Authors: - * Lauris Kaplinski - * bulia byak - * Abhishek Sharma - * Jon A. Cruz - * - * Copyright (C) 2010 authors - * Copyright (C) 2006 Johan Engelen - * Copyright (C) 1999-2005 Authors - * - * Released under GNU GPL, read the file 'COPYING' for more information - */ - -#ifdef HAVE_CONFIG_H -# include "config.h" -#endif -#include -#include -#include -#include "macros.h" -#include "rubberband.h" -#include "document.h" -#include "document-undo.h" -#include "selection.h" -#include "sp-cursor.h" -#include "style.h" -#include "pixmaps/cursor-select-m.xpm" -#include "pixmaps/cursor-select-d.xpm" -#include "pixmaps/handles.xpm" -#include - -#include "select-context.h" -#include "selection-chemistry.h" -#ifdef WITH_DBUS -#include "extension/dbus/document-interface.h" -#endif -#include "desktop.h" -#include "desktop-handles.h" -#include "sp-root.h" -#include "preferences.h" -#include "tools-switch.h" -#include "message-stack.h" -#include "selection-describer.h" -#include "seltrans.h" -#include "box3d.h" -#include "display/sp-canvas.h" -#include "display/sp-canvas-item.h" -#include "display/drawing-item.h" -#include "tool-factory.h" - -using Inkscape::DocumentUndo; - -GdkPixbuf *handles[13]; - -namespace Inkscape { -namespace UI { -namespace Tools { - -static GdkCursor *CursorSelectMouseover = NULL; -static GdkCursor *CursorSelectDragging = NULL; - -static gint rb_escaped = 0; // if non-zero, rubberband was canceled by esc, so the next button release should not deselect -static gint drag_escaped = 0; // if non-zero, drag was canceled by esc - -namespace { - ToolBase* createSelectContext() { - return new SelectTool(); - } - - bool selectContextRegistered = ToolFactory::instance().registerObject("/tools/select", createSelectContext); -} - -const std::string& SelectTool::getPrefsPath() { - return SelectTool::prefsPath; -} - -const std::string SelectTool::prefsPath = "/tools/select"; - - -//Creates rotated variations for handles -static void -sp_load_handles(int start, int count, char const **xpm) { - handles[start] = gdk_pixbuf_new_from_xpm_data((gchar const **)xpm); - for(int i = start + 1; i < start + count; i++) { - // We use either the original at *start or previous loop item to rotate - handles[i] = gdk_pixbuf_rotate_simple(handles[i-1], GDK_PIXBUF_ROTATE_CLOCKWISE); - } -} - -SelectTool::SelectTool() : ToolBase() { - this->grabbed = 0; - this->item = 0; - - this->dragging = FALSE; - this->moved = FALSE; - this->button_press_shift = false; - this->button_press_ctrl = false; - this->button_press_alt = false; - this->cycling_items = NULL; - this->cycling_items_cmp = NULL; - this->cycling_items_selected_before = NULL; - this->cycling_cur_item = NULL; - this->cycling_wrap = true; - this->_seltrans = NULL; - this->_describer = NULL; - - - // cursors in select context - CursorSelectMouseover = sp_cursor_new_from_xpm(cursor_select_m_xpm , 1, 1); - CursorSelectDragging = sp_cursor_new_from_xpm(cursor_select_d_xpm , 1, 1); - - // selection handles - sp_load_handles(0, 2, handle_scale_xpm); - sp_load_handles(2, 2, handle_stretch_xpm); - sp_load_handles(4, 4, handle_rotate_xpm); - sp_load_handles(8, 4, handle_skew_xpm); - sp_load_handles(12, 1, handle_center_xpm); -} - -//static gint xp = 0, yp = 0; // where drag started -//static gint tolerance = 0; -//static bool within_tolerance = false; -static bool is_cycling = false; -static bool moved_while_cycling = false; -ToolBase *prev_event_context = NULL; - - -SelectTool::~SelectTool() { - this->enableGrDrag(false); - - if (this->grabbed) { - sp_canvas_item_ungrab(this->grabbed, GDK_CURRENT_TIME); - this->grabbed = NULL; - } - - delete this->_seltrans; - this->_seltrans = NULL; - - delete this->_describer; - this->_describer = NULL; - - if (CursorSelectDragging) { -#if GTK_CHECK_VERSION(3,0,0) - g_object_unref(CursorSelectDragging); -#else - gdk_cursor_unref (CursorSelectDragging); -#endif - CursorSelectDragging = NULL; - } - - if (CursorSelectMouseover) { -#if GTK_CHECK_VERSION(3,0,0) - g_object_unref(CursorSelectMouseover); -#else - gdk_cursor_unref (CursorSelectMouseover); -#endif - CursorSelectMouseover = NULL; - } -} - -void SelectTool::setup() { - ToolBase::setup(); - - this->_describer = new Inkscape::SelectionDescriber( - desktop->selection, - desktop->messageStack(), - _("Click selection to toggle scale/rotation handles"), - _("No objects selected. Click, Shift+click, Alt+scroll mouse on top of objects, or drag around objects to select.") - ); - - this->_seltrans = new Inkscape::SelTrans(desktop); - - sp_event_context_read(this, "show"); - sp_event_context_read(this, "transform"); - - Inkscape::Preferences *prefs = Inkscape::Preferences::get(); - - if (prefs->getBool("/tools/select/gradientdrag")) { - this->enableGrDrag(); - } -} - -void SelectTool::set(const Inkscape::Preferences::Entry& val) { - Glib::ustring path = val.getEntryName(); - - if (path == "show") { - if (val.getString() == "outline") { - this->_seltrans->setShow(Inkscape::SelTrans::SHOW_OUTLINE); - } else { - this->_seltrans->setShow(Inkscape::SelTrans::SHOW_CONTENT); - } - } -} - -bool SelectTool::sp_select_context_abort() { - Inkscape::SelTrans *seltrans = this->_seltrans; - - if (this->dragging) { - if (this->moved) { // cancel dragging an object - seltrans->ungrab(); - this->moved = FALSE; - this->dragging = FALSE; - sp_event_context_discard_delayed_snap_event(this); - drag_escaped = 1; - - if (this->item) { - // only undo if the item is still valid - if (this->item->document) { - DocumentUndo::undo(sp_desktop_document(desktop)); - } - - sp_object_unref( this->item, NULL); - } else if (this->button_press_ctrl) { - // NOTE: This is a workaround to a bug. - // When the ctrl key is held, sc->item is not defined - // so in this case (only), we skip the object doc check - DocumentUndo::undo(sp_desktop_document(desktop)); - } - this->item = NULL; - - SP_EVENT_CONTEXT(this)->desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("Move canceled.")); - return true; - } - } else { - if (Inkscape::Rubberband::get(desktop)->is_started()) { - Inkscape::Rubberband::get(desktop)->stop(); - rb_escaped = 1; - SP_EVENT_CONTEXT(this)->defaultMessageContext()->clear(); - SP_EVENT_CONTEXT(this)->desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("Selection canceled.")); - return true; - } - } - return false; -} - -static bool -key_is_a_modifier (guint key) { - return (key == GDK_KEY_Alt_L || - key == GDK_KEY_Alt_R || - key == GDK_KEY_Control_L || - key == GDK_KEY_Control_R || - key == GDK_KEY_Shift_L || - key == GDK_KEY_Shift_R || - key == GDK_KEY_Meta_L || // Meta is when you press Shift+Alt (at least on my machine) - key == GDK_KEY_Meta_R); -} - -static void -sp_select_context_up_one_layer(SPDesktop *desktop) -{ - /* Click in empty place, go up one level -- but don't leave a layer to root. - * - * (Rationale: we don't usually allow users to go to the root, since that - * detracts from the layer metaphor: objects at the root level can in front - * of or behind layers. Whereas it's fine to go to the root if editing - * a document that has no layers (e.g. a non-Inkscape document).) - * - * Once we support editing SVG "islands" (e.g. embedded in an xhtml - * document), we might consider further restricting the below to disallow - * leaving a layer to go to a non-layer. - */ - SPObject *const current_layer = desktop->currentLayer(); - if (current_layer) { - SPObject *const parent = current_layer->parent; - if ( parent - && ( parent->parent - || !( SP_IS_GROUP(current_layer) - && ( SPGroup::LAYER - == SP_GROUP(current_layer)->layerMode() ) ) ) ) - { - desktop->setCurrentLayer(parent); - if (SP_IS_GROUP(current_layer) && SPGroup::LAYER != SP_GROUP(current_layer)->layerMode()) - sp_desktop_selection(desktop)->set(current_layer); - } - } -} - -bool SelectTool::item_handler(SPItem* item, GdkEvent* event) { - gint ret = FALSE; - - Inkscape::Preferences *prefs = Inkscape::Preferences::get(); - tolerance = prefs->getIntLimited("/options/dragtolerance/value", 0, 0, 100); - - // make sure we still have valid objects to move around - if (this->item && this->item->document == NULL) { - this->sp_select_context_abort(); - } - - switch (event->type) { - case GDK_BUTTON_PRESS: - if (event->button.button == 1 && !this->space_panning) { - /* Left mousebutton */ - - // save drag origin - xp = (gint) event->button.x; - yp = (gint) event->button.y; - within_tolerance = true; - - // remember what modifiers were on before button press - this->button_press_shift = (event->button.state & GDK_SHIFT_MASK) ? true : false; - this->button_press_ctrl = (event->button.state & GDK_CONTROL_MASK) ? true : false; - this->button_press_alt = (event->button.state & GDK_MOD1_MASK) ? true : false; - - if (event->button.state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK | GDK_MOD1_MASK)) { - // if shift or ctrl was pressed, do not move objects; - // pass the event to root handler which will perform rubberband, shift-click, ctrl-click, ctrl-drag - } else { - GdkWindow* window = gtk_widget_get_window (GTK_WIDGET (sp_desktop_canvas(desktop))); - - this->dragging = TRUE; - this->moved = FALSE; - - gdk_window_set_cursor(window, CursorSelectDragging); - - desktop->canvas->forceFullRedrawAfterInterruptions(5); - - // remember the clicked item in this->item: - if (this->item) { - sp_object_unref(this->item, NULL); - this->item = NULL; - } - - this->item = sp_event_context_find_item (desktop, - Geom::Point(event->button.x, event->button.y), event->button.state & GDK_MOD1_MASK, FALSE); - sp_object_ref(this->item, NULL); - - rb_escaped = drag_escaped = 0; - - if (this->grabbed) { - sp_canvas_item_ungrab(this->grabbed, event->button.time); - this->grabbed = NULL; - } - - sp_canvas_item_grab(SP_CANVAS_ITEM(desktop->drawing), - GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK | GDK_BUTTON_RELEASE_MASK | GDK_BUTTON_PRESS_MASK | - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK, - NULL, event->button.time); - - this->grabbed = SP_CANVAS_ITEM(desktop->drawing); - - desktop->canvas->forceFullRedrawAfterInterruptions(5); - - ret = TRUE; - } - } else if (event->button.button == 3) { - // right click; do not eat it so that right-click menu can appear, but cancel dragging & rubberband - this->sp_select_context_abort(); - } - break; - - case GDK_ENTER_NOTIFY: { - if (!desktop->isWaitingCursor() && !this->dragging) { - GdkWindow* window = gtk_widget_get_window (GTK_WIDGET (sp_desktop_canvas(desktop))); - - gdk_window_set_cursor(window, CursorSelectMouseover); - } - break; - } - case GDK_LEAVE_NOTIFY: - if (!desktop->isWaitingCursor() && !this->dragging) { - GdkWindow* window = gtk_widget_get_window (GTK_WIDGET (sp_desktop_canvas(desktop))); - - gdk_window_set_cursor(window, this->cursor); - } - break; - - case GDK_KEY_PRESS: - if (get_group0_keyval (&event->key) == GDK_KEY_space) { - if (this->dragging && this->grabbed) { - /* stamping mode: show content mode moving */ - _seltrans->stamp(); - ret = TRUE; - } - } else if (get_group0_keyval (&event->key) == GDK_KEY_Tab) { - if (this->dragging && this->grabbed) { - _seltrans->getNextClosestPoint(false); - ret = TRUE; - } - } else if (get_group0_keyval (&event->key) == GDK_KEY_ISO_Left_Tab) { - if (this->dragging && this->grabbed) { - _seltrans->getNextClosestPoint(true); - ret = TRUE; - } - } - break; - - default: - break; - } - - if (!ret) { - ret = ToolBase::item_handler(item, event); - } - - return ret; -} - -void SelectTool::sp_select_context_cycle_through_items(Inkscape::Selection *selection, GdkEventScroll *scroll_event, bool shift_pressed) { - if (!this->cycling_cur_item) { - return; - } - - Inkscape::DrawingItem *arenaitem; - SPItem *item = SP_ITEM(this->cycling_cur_item->data); - - // Deactivate current item - if (!g_list_find(this->cycling_items_selected_before, item) && selection->includes(item)) { - selection->remove(item); - } - - arenaitem = item->get_arenaitem(desktop->dkey); - arenaitem->setOpacity(0.3); - - // Find next item and activate it - GList *next; - if (scroll_event->direction == GDK_SCROLL_UP) { - next = this->cycling_cur_item->next; - if (next == NULL && this->cycling_wrap) - next = this->cycling_items; - } else { - next = this->cycling_cur_item->prev; - if (next == NULL && this->cycling_wrap) - next = g_list_last(this->cycling_items); - } - - if (next) { - this->cycling_cur_item = next; - item = SP_ITEM(this->cycling_cur_item->data); - } - - arenaitem = item->get_arenaitem(desktop->dkey); - arenaitem->setOpacity(1.0); - - if (shift_pressed) { - selection->add(item); - } else { - selection->set(item); - } -} - - -static void -sp_select_context_reset_opacities(ToolBase *event_context) -{ - // SPDesktop *desktop = event_context->desktop; - SelectTool *sc = SP_SELECT_CONTEXT(event_context); - Inkscape::DrawingItem *arenaitem; - for (GList *l = sc->cycling_items; l != NULL; l = g_list_next(l)) { - arenaitem = SP_ITEM(l->data)->get_arenaitem(event_context->desktop->dkey); - arenaitem->setOpacity(SP_SCALE24_TO_FLOAT(SP_ITEM(l->data)->style->opacity.value)); - } - g_list_free(sc->cycling_items); - g_list_free(sc->cycling_items_selected_before); - g_list_free(sc->cycling_items_cmp); - sc->cycling_items = NULL; - sc->cycling_items_selected_before = NULL; - sc->cycling_cur_item = NULL; - sc->cycling_items_cmp = NULL; -} - -bool SelectTool::root_handler(GdkEvent* event) { - SPItem *item = NULL; - SPItem *item_at_point = NULL, *group_at_point = NULL, *item_in_group = NULL; - gint ret = FALSE; - - Inkscape::Selection *selection = sp_desktop_selection(desktop); - Inkscape::Preferences *prefs = Inkscape::Preferences::get(); - - // make sure we still have valid objects to move around - if (this->item && this->item->document == NULL) { - this->sp_select_context_abort(); - } - - switch (event->type) { - case GDK_2BUTTON_PRESS: - if (event->button.button == 1) { - if (!selection->isEmpty()) { - SPItem *clicked_item = static_cast(selection->itemList()->data); - - if (SP_IS_GROUP(clicked_item) && !SP_IS_BOX3D(clicked_item)) { // enter group if it's not a 3D box - desktop->setCurrentLayer(reinterpret_cast(clicked_item)); - sp_desktop_selection(desktop)->clear(); - this->dragging = false; - sp_event_context_discard_delayed_snap_event(this); - - desktop->canvas->endForcedFullRedraws(); - } else { // switch tool - Geom::Point const button_pt(event->button.x, event->button.y); - Geom::Point const p(desktop->w2d(button_pt)); - tools_switch_by_item (desktop, clicked_item, p); - } - } else { - sp_select_context_up_one_layer(desktop); - } - - ret = TRUE; - } - break; - - case GDK_BUTTON_PRESS: - if (event->button.button == 1 && !this->space_panning) { - // save drag origin - xp = (gint) event->button.x; - yp = (gint) event->button.y; - within_tolerance = true; - - Geom::Point const button_pt(event->button.x, event->button.y); - Geom::Point const p(desktop->w2d(button_pt)); - - if (event->button.state & GDK_MOD1_MASK) { - Inkscape::Rubberband::get(desktop)->setMode(RUBBERBAND_MODE_TOUCHPATH); - } - - Inkscape::Rubberband::get(desktop)->start(desktop, p); - - if (this->grabbed) { - sp_canvas_item_ungrab(this->grabbed, event->button.time); - this->grabbed = NULL; - } - - sp_canvas_item_grab(SP_CANVAS_ITEM(desktop->acetate), - GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK | GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK, - NULL, event->button.time); - - this->grabbed = SP_CANVAS_ITEM(desktop->acetate); - - // remember what modifiers were on before button press - this->button_press_shift = (event->button.state & GDK_SHIFT_MASK) ? true : false; - this->button_press_ctrl = (event->button.state & GDK_CONTROL_MASK) ? true : false; - this->button_press_alt = (event->button.state & GDK_MOD1_MASK) ? true : false; - - this->moved = FALSE; - - rb_escaped = drag_escaped = 0; - - ret = TRUE; - } else if (event->button.button == 3) { - // right click; do not eat it so that right-click menu can appear, but cancel dragging & rubberband - this->sp_select_context_abort(); - } - break; - - case GDK_MOTION_NOTIFY: - { - if (is_cycling) - { - moved_while_cycling = true; - prev_event_context = this; - } - - tolerance = prefs->getIntLimited("/options/dragtolerance/value", 0, 0, 100); - - if ((event->motion.state & GDK_BUTTON1_MASK) && !this->space_panning) { - Geom::Point const motion_pt(event->motion.x, event->motion.y); - Geom::Point const p(desktop->w2d(motion_pt)); - - if ( within_tolerance - && ( abs( (gint) event->motion.x - xp ) < tolerance ) - && ( abs( (gint) event->motion.y - yp ) < tolerance ) ) { - break; // do not drag if we're within tolerance from origin - } - // Once the user has moved farther than tolerance from the original location - // (indicating they intend to move the object, not click), then always process the - // motion notify coordinates as given (no snapping back to origin) - within_tolerance = false; - - if (this->button_press_ctrl || (this->button_press_alt && !this->button_press_shift && !selection->isEmpty())) { - // if it's not click and ctrl or alt was pressed (the latter with some selection - // but not with shift) we want to drag rather than rubberband - this->dragging = TRUE; - - GdkWindow* window = gtk_widget_get_window (GTK_WIDGET (sp_desktop_canvas(desktop))); - - gdk_window_set_cursor(window, CursorSelectDragging); - - desktop->canvas->forceFullRedrawAfterInterruptions(5); - } - - if (this->dragging) { - /* User has dragged fast, so we get events on root (lauris)*/ - // not only that; we will end up here when ctrl-dragging as well - // and also when we started within tolerance, but trespassed tolerance outside of item - Inkscape::Rubberband::get(desktop)->stop(); - this->defaultMessageContext()->clear(); - - item_at_point = desktop->getItemAtPoint(Geom::Point(event->button.x, event->button.y), FALSE); - - if (!item_at_point) { // if no item at this point, try at the click point (bug 1012200) - item_at_point = desktop->getItemAtPoint(Geom::Point(xp, yp), FALSE); - } - - if (item_at_point || this->moved || this->button_press_alt) { - // drag only if starting from an item, or if something is already grabbed, or if alt-dragging - if (!this->moved) { - item_in_group = desktop->getItemAtPoint(Geom::Point(event->button.x, event->button.y), TRUE); - group_at_point = desktop->getGroupAtPoint(Geom::Point(event->button.x, event->button.y)); - - if (SP_IS_LAYER(selection->single())) { - group_at_point = SP_GROUP(selection->single()); - } - - // group-at-point is meant to be topmost item if it's a group, - // not topmost group of all items at point - if (group_at_point != item_in_group && - !(group_at_point && item_at_point && - group_at_point->isAncestorOf(item_at_point))) { - group_at_point = NULL; - } - - // if neither a group nor an item (possibly in a group) at point are selected, set selection to the item at point - if ((!item_in_group || !selection->includes(item_in_group)) && - (!group_at_point || !selection->includes(group_at_point)) - && !this->button_press_alt) { - // select what is under cursor - if (!_seltrans->isEmpty()) { - _seltrans->resetState(); - } - - // when simply ctrl-dragging, we don't want to go into groups - if (item_at_point && !selection->includes(item_at_point)) { - selection->set(item_at_point); - } - } // otherwise, do not change selection so that dragging selected-within-group items, as well as alt-dragging, is possible - - _seltrans->grab(p, -1, -1, FALSE, TRUE); - this->moved = TRUE; - } - - if (!_seltrans->isEmpty()) { - _seltrans->moveTo(p, event->button.state); - } - - desktop->scroll_to_point(p); - gobble_motion_events(GDK_BUTTON1_MASK); - ret = TRUE; - } else { - this->dragging = FALSE; - sp_event_context_discard_delayed_snap_event(this); - desktop->canvas->endForcedFullRedraws(); - } - } else { - if (Inkscape::Rubberband::get(desktop)->is_started()) { - Inkscape::Rubberband::get(desktop)->move(p); - - if (Inkscape::Rubberband::get(desktop)->getMode() == RUBBERBAND_MODE_TOUCHPATH) { - this->defaultMessageContext()->set(Inkscape::NORMAL_MESSAGE, _("Draw over objects to select them; release Alt to switch to rubberband selection")); - } else { - this->defaultMessageContext()->set(Inkscape::NORMAL_MESSAGE, _("Drag around objects to select them; press Alt to switch to touch selection")); - } - - gobble_motion_events(GDK_BUTTON1_MASK); - } - } - } - break; - } - case GDK_BUTTON_RELEASE: - xp = yp = 0; - - if ((event->button.button == 1) && (this->grabbed) && !this->space_panning) { - if (this->dragging) { - GdkWindow* window; - - if (this->moved) { - // item has been moved - _seltrans->ungrab(); - this->moved = FALSE; -#ifdef WITH_DBUS - dbus_send_ping(desktop, this->item); -#endif - } else if (this->item && !drag_escaped) { - // item has not been moved -> simply a click, do selecting - if (!selection->isEmpty()) { - if (event->button.state & GDK_SHIFT_MASK) { - // with shift, toggle selection - _seltrans->resetState(); - selection->toggle(this->item); - } else { - SPObject* single = selection->single(); - // without shift, increase state (i.e. toggle scale/rotation handles) - if (selection->includes(this->item)) { - _seltrans->increaseState(); - } else if (SP_IS_LAYER(single) && single->isAncestorOf(this->item)) { - _seltrans->increaseState(); - } else { - _seltrans->resetState(); - selection->set(this->item); - } - } - } else { // simple or shift click, no previous selection - _seltrans->resetState(); - selection->set(this->item); - } - } - - this->dragging = FALSE; - window = gtk_widget_get_window (GTK_WIDGET (sp_desktop_canvas(desktop))); - - gdk_window_set_cursor(window, CursorSelectMouseover); - sp_event_context_discard_delayed_snap_event(this); - desktop->canvas->endForcedFullRedraws(); - - if (this->item) { - sp_object_unref( this->item, NULL); - } - - this->item = NULL; - } else { - Inkscape::Rubberband *r = Inkscape::Rubberband::get(desktop); - - if (r->is_started() && !within_tolerance) { - // this was a rubberband drag - GSList *items = NULL; - - if (r->getMode() == RUBBERBAND_MODE_RECT) { - Geom::OptRect const b = r->getRectangle(); - items = sp_desktop_document(desktop)->getItemsInBox(desktop->dkey, *b); - } else if (r->getMode() == RUBBERBAND_MODE_TOUCHPATH) { - items = sp_desktop_document(desktop)->getItemsAtPoints(desktop->dkey, r->getPoints()); - } - - _seltrans->resetState(); - r->stop(); - this->defaultMessageContext()->clear(); - - if (event->button.state & GDK_SHIFT_MASK) { - // with shift, add to selection - selection->addList (items); - } else { - // without shift, simply select anew - selection->setList (items); - } - - g_slist_free (items); - } else { // it was just a click, or a too small rubberband - r->stop(); - - if (this->button_press_shift && !rb_escaped && !drag_escaped) { - // this was a shift+click or alt+shift+click, select what was clicked upon - this->button_press_shift = false; - - if (this->button_press_ctrl) { - // go into groups, honoring Alt - item = sp_event_context_find_item (desktop, - Geom::Point(event->button.x, event->button.y), event->button.state & GDK_MOD1_MASK, TRUE); - this->button_press_ctrl = FALSE; - } else { - // don't go into groups, honoring Alt - item = sp_event_context_find_item (desktop, - Geom::Point(event->button.x, event->button.y), event->button.state & GDK_MOD1_MASK, FALSE); - } - - if (item) { - selection->toggle(item); - item = NULL; - } - - } else if ((this->button_press_ctrl || this->button_press_alt) && !rb_escaped && !drag_escaped) { // ctrl+click, alt+click - item = sp_event_context_find_item (desktop, - Geom::Point(event->button.x, event->button.y), this->button_press_alt, this->button_press_ctrl); - - this->button_press_ctrl = FALSE; - this->button_press_alt = FALSE; - - if (item) { - if (selection->includes(item)) { - _seltrans->increaseState(); - } else { - _seltrans->resetState(); - selection->set(item); - } - - item = NULL; - } - } else { // click without shift, simply deselect, unless with Alt or something was cancelled - if (!selection->isEmpty()) { - if (!(rb_escaped) && !(drag_escaped) && !(event->button.state & GDK_MOD1_MASK)) { - selection->clear(); - } - - rb_escaped = 0; - ret = TRUE; - } - } - } - - ret = TRUE; - } - - if (this->grabbed) { - sp_canvas_item_ungrab(this->grabbed, event->button.time); - this->grabbed = NULL; - } - - desktop->updateNow(); - } - - if (event->button.button == 1) { - Inkscape::Rubberband::get(desktop)->stop(); // might have been started in another tool! - } - - this->button_press_shift = false; - this->button_press_ctrl = false; - this->button_press_alt = false; - break; - - case GDK_SCROLL: { - GdkEventScroll *scroll_event = (GdkEventScroll*) event; - - if (scroll_event->state & GDK_MOD1_MASK) { // alt modified pressed - if (moved_while_cycling) - { - moved_while_cycling = false; - sp_select_context_reset_opacities(prev_event_context); - prev_event_context = NULL; - } - - is_cycling = true; - - bool shift_pressed = scroll_event->state & GDK_SHIFT_MASK; - - /* Rebuild list of items underneath the mouse pointer */ - Geom::Point p = desktop->d2w(desktop->point()); - SPItem *item = desktop->getItemAtPoint(p, true, NULL); - - // Save pointer to current cycle-item so that we can find it again later, in the freshly built list - SPItem *tmp_cur_item = this->cycling_cur_item ? SP_ITEM(this->cycling_cur_item->data) : NULL; - g_list_free(this->cycling_items); - this->cycling_items = NULL; - this->cycling_cur_item = NULL; - - while(item != NULL) { - this->cycling_items = g_list_append(this->cycling_items, item); - item = desktop->getItemAtPoint(p, true, item); - } - - /* Compare current item list with item list during previous scroll ... */ - GList *l1, *l2; - bool item_lists_differ = false; - - // Note that we can do an 'or' comparison in the loop because it is safe to call g_list_next with a NULL pointer. - for (l1 = this->cycling_items, l2 = this->cycling_items_cmp; l1 != NULL || l2 != NULL; l1 = g_list_next(l1), l2 = g_list_next(l2)) { - if ((l1 !=NULL && l2 == NULL) || (l1 == NULL && l2 != NULL) || (l1->data != l2->data)) { - item_lists_differ = true; - break; - } - } - - /* If list of items under mouse pointer hasn't changed ... */ - if (!item_lists_differ) { - // ... find current item in the freshly built list and continue cycling ... - // TODO: This wouldn't be necessary if cycling_cur_item pointed to an element of cycling_items_cmp instead - this->cycling_cur_item = g_list_find(this->cycling_items, tmp_cur_item); - g_assert(this->cycling_cur_item != NULL || this->cycling_items == NULL); - } else { - // ... otherwise reset opacities for outdated items ... - Inkscape::DrawingItem *arenaitem; - - for(GList *l = this->cycling_items_cmp; l != NULL; l = l->next) { - arenaitem = SP_ITEM(l->data)->get_arenaitem(desktop->dkey); - arenaitem->setOpacity(1.0); - //if (!shift_pressed && !g_list_find(this->cycling_items_selected_before, SP_ITEM(l->data)) && selection->includes(SP_ITEM(l->data))) - if (!g_list_find(this->cycling_items_selected_before, SP_ITEM(l->data)) && selection->includes(SP_ITEM(l->data))) { - selection->remove(SP_ITEM(l->data)); - } - } - - // ... clear the lists ... - g_list_free(this->cycling_items_cmp); - g_list_free(this->cycling_items_selected_before); - - this->cycling_items_cmp = NULL; - this->cycling_items_selected_before = NULL; - this->cycling_cur_item = NULL; - - // ... and rebuild them with the new items. - this->cycling_items_cmp = g_list_copy(this->cycling_items); - SPItem *item; - - for(GList *l = this->cycling_items; l != NULL; l = l->next) { - item = SP_ITEM(l->data); - arenaitem = item->get_arenaitem(desktop->dkey); - arenaitem->setOpacity(0.3); - - if (selection->includes(item)) { - // already selected items are stored separately, too - this->cycling_items_selected_before = g_list_append(this->cycling_items_selected_before, item); - } - } - - // set the current item to the bottommost one so that the cycling step below re-starts at the top - this->cycling_cur_item = g_list_last(this->cycling_items); - } - - this->cycling_wrap = prefs->getBool("/options/selection/cycleWrap", true); - - // Cycle through the items underneath the mouse pointer, one-by-one - this->sp_select_context_cycle_through_items(selection, scroll_event, shift_pressed); - - ret = TRUE; - - GtkWindow *w =GTK_WINDOW(gtk_widget_get_toplevel( GTK_WIDGET(desktop->canvas) )); - if (w) - { - gtk_window_present(w); - gtk_widget_grab_focus (GTK_WIDGET(desktop->canvas)); - } - } - break; - } - - case GDK_KEY_PRESS: // keybindings for select context - { - { - guint keyval = get_group0_keyval(&event->key); - - bool alt = ( MOD__ALT(event) - || (keyval == GDK_KEY_Alt_L) - || (keyval == GDK_KEY_Alt_R) - || (keyval == GDK_KEY_Meta_L) - || (keyval == GDK_KEY_Meta_R)); - - if (!key_is_a_modifier (keyval)) { - this->defaultMessageContext()->clear(); - } else if (this->grabbed || _seltrans->isGrabbed()) { - if (Inkscape::Rubberband::get(desktop)->is_started()) { - // if Alt then change cursor to moving cursor: - if (alt) { - Inkscape::Rubberband::get(desktop)->setMode(RUBBERBAND_MODE_TOUCHPATH); - } - } else { - // do not change the statusbar text when mousekey is down to move or transform the object, - // because the statusbar text is already updated somewhere else. - break; - } - } else { - sp_event_show_modifier_tip (this->defaultMessageContext(), event, - _("Ctrl: click to select in groups; drag to move hor/vert"), - _("Shift: click to toggle select; drag for rubberband selection"), - _("Alt: click to select under; scroll mouse-wheel to cycle-select; drag to move selected or select by touch")); - - // if Alt and nonempty selection, show moving cursor ("move selected"): - if (alt && !selection->isEmpty() && !desktop->isWaitingCursor()) { - GdkWindow* window = gtk_widget_get_window (GTK_WIDGET (sp_desktop_canvas(desktop))); - - gdk_window_set_cursor(window, CursorSelectDragging); - } - //*/ - break; - } - } - - gdouble const nudge = prefs->getDoubleLimited("/options/nudgedistance/value", 2, 0, 1000, "px"); // in px - gdouble const offset = prefs->getDoubleLimited("/options/defaultscale/value", 2, 0, 1000, "px"); - int const snaps = prefs->getInt("/options/rotationsnapsperpi/value", 12); - - switch (get_group0_keyval (&event->key)) { - case GDK_KEY_Left: // move selection left - case GDK_KEY_KP_Left: - if (!MOD__CTRL(event)) { // not ctrl - gint mul = 1 + gobble_key_events( get_group0_keyval(&event->key), 0); // with any mask - - if (MOD__ALT(event)) { // alt - if (MOD__SHIFT(event)) { - sp_selection_move_screen(sp_desktop_selection(desktop), mul*-10, 0); // shift - } else { - sp_selection_move_screen(sp_desktop_selection(desktop), mul*-1, 0); // no shift - } - } else { // no alt - if (MOD__SHIFT(event)) { - sp_selection_move(sp_desktop_selection(desktop), mul*-10*nudge, 0); // shift - } else { - sp_selection_move(sp_desktop_selection(desktop), mul*-nudge, 0); // no shift - } - } - - ret = TRUE; - } - break; - - case GDK_KEY_Up: // move selection up - case GDK_KEY_KP_Up: - if (!MOD__CTRL(event)) { // not ctrl - gint mul = 1 + gobble_key_events(get_group0_keyval(&event->key), 0); // with any mask - - if (MOD__ALT(event)) { // alt - if (MOD__SHIFT(event)) { - sp_selection_move_screen(sp_desktop_selection(desktop), 0, mul*10); // shift - } else { - sp_selection_move_screen(sp_desktop_selection(desktop), 0, mul*1); // no shift - } - } else { // no alt - if (MOD__SHIFT(event)) { - sp_selection_move(sp_desktop_selection(desktop), 0, mul*10*nudge); // shift - } else { - sp_selection_move(sp_desktop_selection(desktop), 0, mul*nudge); // no shift - } - } - - ret = TRUE; - } - break; - - case GDK_KEY_Right: // move selection right - case GDK_KEY_KP_Right: - if (!MOD__CTRL(event)) { // not ctrl - gint mul = 1 + gobble_key_events(get_group0_keyval(&event->key), 0); // with any mask - - if (MOD__ALT(event)) { // alt - if (MOD__SHIFT(event)) { - sp_selection_move_screen(sp_desktop_selection(desktop), mul*10, 0); // shift - } else { - sp_selection_move_screen(sp_desktop_selection(desktop), mul*1, 0); // no shift - } - } else { // no alt - if (MOD__SHIFT(event)) { - sp_selection_move(sp_desktop_selection(desktop), mul*10*nudge, 0); // shift - } else { - sp_selection_move(sp_desktop_selection(desktop), mul*nudge, 0); // no shift - } - } - - ret = TRUE; - } - break; - - case GDK_KEY_Down: // move selection down - case GDK_KEY_KP_Down: - if (!MOD__CTRL(event)) { // not ctrl - gint mul = 1 + gobble_key_events(get_group0_keyval(&event->key), 0); // with any mask - - if (MOD__ALT(event)) { // alt - if (MOD__SHIFT(event)) { - sp_selection_move_screen(sp_desktop_selection(desktop), 0, mul*-10); // shift - } else { - sp_selection_move_screen(sp_desktop_selection(desktop), 0, mul*-1); // no shift - } - } else { // no alt - if (MOD__SHIFT(event)) { - sp_selection_move(sp_desktop_selection(desktop), 0, mul*-10*nudge); // shift - } else { - sp_selection_move(sp_desktop_selection(desktop), 0, mul*-nudge); // no shift - } - } - - ret = TRUE; - } - break; - - case GDK_KEY_Escape: - if (!this->sp_select_context_abort()) { - selection->clear(); - } - - ret = TRUE; - break; - - case GDK_KEY_a: - case GDK_KEY_A: - if (MOD__CTRL_ONLY(event)) { - sp_edit_select_all(desktop); - ret = TRUE; - } - break; - - case GDK_KEY_space: - /* stamping mode: show outline mode moving */ - /* FIXME: Is next condition ok? (lauris) */ - if (this->dragging && this->grabbed) { - _seltrans->stamp(); - ret = TRUE; - } - break; - - case GDK_KEY_x: - case GDK_KEY_X: - if (MOD__ALT_ONLY(event)) { - desktop->setToolboxFocusTo ("altx"); - ret = TRUE; - } - break; - - case GDK_KEY_bracketleft: - if (MOD__ALT(event)) { - gint mul = 1 + gobble_key_events(get_group0_keyval(&event->key), 0); // with any mask - sp_selection_rotate_screen(selection, mul*1); - } else if (MOD__CTRL(event)) { - sp_selection_rotate(selection, 90); - } else if (snaps) { - sp_selection_rotate(selection, 180.0/snaps); - } - - ret = TRUE; - break; - - case GDK_KEY_bracketright: - if (MOD__ALT(event)) { - gint mul = 1 + gobble_key_events(get_group0_keyval(&event->key), 0); // with any mask - sp_selection_rotate_screen(selection, -1*mul); - } else if (MOD__CTRL(event)) { - sp_selection_rotate(selection, -90); - } else if (snaps) { - sp_selection_rotate(selection, -180.0/snaps); - } - - ret = TRUE; - break; - - case GDK_KEY_less: - case GDK_KEY_comma: - if (MOD__ALT(event)) { - gint mul = 1 + gobble_key_events(get_group0_keyval(&event->key), 0); // with any mask - sp_selection_scale_screen(selection, -2*mul); - } else if (MOD__CTRL(event)) { - sp_selection_scale_times(selection, 0.5); - } else { - gint mul = 1 + gobble_key_events(get_group0_keyval(&event->key), 0); // with any mask - sp_selection_scale(selection, -offset*mul); - } - - ret = TRUE; - break; - - case GDK_KEY_greater: - case GDK_KEY_period: - if (MOD__ALT(event)) { - gint mul = 1 + gobble_key_events(get_group0_keyval(&event->key), 0); // with any mask - sp_selection_scale_screen(selection, 2*mul); - } else if (MOD__CTRL(event)) { - sp_selection_scale_times(selection, 2); - } else { - gint mul = 1 + gobble_key_events(get_group0_keyval(&event->key), 0); // with any mask - sp_selection_scale(selection, offset*mul); - } - - ret = TRUE; - break; - - case GDK_KEY_Return: - if (MOD__CTRL_ONLY(event)) { - if (selection->singleItem()) { - SPItem *clicked_item = selection->singleItem(); - - if ( SP_IS_GROUP(clicked_item) || SP_IS_BOX3D(clicked_item)) { // enter group or a 3D box - desktop->setCurrentLayer(reinterpret_cast(clicked_item)); - sp_desktop_selection(desktop)->clear(); - } else { - this->desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("Selected object is not a group. Cannot enter.")); - } - } - - ret = TRUE; - } - break; - - case GDK_KEY_BackSpace: - if (MOD__CTRL_ONLY(event)) { - sp_select_context_up_one_layer(desktop); - ret = TRUE; - } - break; - - case GDK_KEY_s: - case GDK_KEY_S: - if (MOD__SHIFT_ONLY(event)) { - if (!selection->isEmpty()) { - _seltrans->increaseState(); - } - - ret = TRUE; - } - break; - - case GDK_KEY_g: - case GDK_KEY_G: - if (MOD__SHIFT_ONLY(event)) { - sp_selection_to_guides(desktop); - ret = true; - } - break; - - default: - break; - } - break; - } - case GDK_KEY_RELEASE: { - guint keyval = get_group0_keyval(&event->key); - if (key_is_a_modifier (keyval)) { - this->defaultMessageContext()->clear(); - } - - bool alt = ( MOD__ALT(event) - || (keyval == GDK_KEY_Alt_L) - || (keyval == GDK_KEY_Alt_R) - || (keyval == GDK_KEY_Meta_L) - || (keyval == GDK_KEY_Meta_R)); - - if (Inkscape::Rubberband::get(desktop)->is_started()) { - // if Alt then change cursor to moving cursor: - if (alt) { - Inkscape::Rubberband::get(desktop)->setMode(RUBBERBAND_MODE_RECT); - } - } else { - if (alt) { // TODO: Should we have a variable like is_cycling or is it harmless to run this piece of code each time? - // quit cycle-selection and reset opacities - if (is_cycling) - { - sp_select_context_reset_opacities(this); - is_cycling = false; - } - - } - } - - } - // set cursor to default. - if (!desktop->isWaitingCursor()) { - // Do we need to reset the cursor here on key release ? - //GdkWindow* window = gtk_widget_get_window (GTK_WIDGET (sp_desktop_canvas(desktop))); - //gdk_window_set_cursor(window, event_context->cursor); - } - break; - - default: - break; - } - - if (!ret) { - ret = ToolBase::root_handler(event); - } - - return ret; -} - -} -} -} - -/* - Local Variables: - mode:c++ - c-file-style:"stroustrup" - c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) - indent-tabs-mode:nil - fill-column:99 - End: -*/ -// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 : diff --git a/src/select-context.h b/src/select-context.h deleted file mode 100644 index ed4c02648..000000000 --- a/src/select-context.h +++ /dev/null @@ -1,73 +0,0 @@ -#ifndef __SP_SELECT_CONTEXT_H__ -#define __SP_SELECT_CONTEXT_H__ - -/* - * Select tool - * - * Authors: - * Lauris Kaplinski - * - * Copyright (C) 1999-2002 authors - * - * Released under GNU GPL, read the file 'COPYING' for more information - */ - -#include "event-context.h" -#include - -#define SP_SELECT_CONTEXT(obj) (dynamic_cast((Inkscape::UI::Tools::ToolBase*)obj)) -#define SP_IS_SELECT_CONTEXT(obj) (dynamic_cast((const Inkscape::UI::Tools::ToolBase*)obj) != NULL) - -struct SPCanvasItem; - -namespace Inkscape { - class MessageContext; - class SelTrans; - class SelectionDescriber; -} - -namespace Inkscape { -namespace UI { -namespace Tools { - -class SelectTool : public ToolBase { -public: - SelectTool(); - virtual ~SelectTool(); - - guint dragging : 1; - guint moved : 1; - bool button_press_shift; - bool button_press_ctrl; - bool button_press_alt; - - GList *cycling_items; - GList *cycling_items_cmp; - GList *cycling_items_selected_before; - GList *cycling_cur_item; - bool cycling_wrap; - - SPItem *item; - SPCanvasItem *grabbed; - Inkscape::SelTrans *_seltrans; - Inkscape::SelectionDescriber *_describer; - - static const std::string prefsPath; - - virtual void setup(); - virtual void set(const Inkscape::Preferences::Entry& val); - virtual bool root_handler(GdkEvent* event); - virtual bool item_handler(SPItem* item, GdkEvent* event); - - virtual const std::string& getPrefsPath(); - -private: - bool sp_select_context_abort(); - void sp_select_context_cycle_through_items(Inkscape::Selection *selection, GdkEventScroll *scroll_event, bool shift_pressed); -}; - -} -} -} - -#endif diff --git a/src/selection-chemistry.cpp b/src/selection-chemistry.cpp index ae85df295..26e575439 100644 --- a/src/selection-chemistry.cpp +++ b/src/selection-chemistry.cpp @@ -56,11 +56,11 @@ SPCycleType SP_CYCLING = SP_CYCLE_FOCUS; #include "sp-polyline.h" #include "sp-line.h" #include "text-editing.h" -#include "text-context.h" -#include "connector-context.h" +#include "ui/tools/text-tool.h" +#include "ui/tools/connector-tool.h" #include "sp-path.h" #include "sp-conn-end.h" -#include "dropper-context.h" +#include "ui/tools/dropper-tool.h" #include #include <2geom/transforms.h> #include "xml/repr.h" @@ -74,7 +74,7 @@ SPCycleType SP_CYCLING = SP_CYCLE_FOCUS; #include "sp-pattern.h" #include "sp-symbol.h" #include "sp-radial-gradient.h" -#include "gradient-context.h" +#include "ui/tools/gradient-tool.h" #include "sp-namedview.h" #include "preferences.h" #include "sp-offset.h" @@ -111,7 +111,7 @@ SPCycleType SP_CYCLING = SP_CYCLE_FOCUS; // For clippath editing #include "tools-switch.h" -#include "ui/tool/node-tool.h" +#include "ui/tools/node-tool.h" #include "ui/clipboard.h" #include "verbs.h" diff --git a/src/seltrans.cpp b/src/seltrans.cpp index 568ad6a09..f961e309d 100644 --- a/src/seltrans.cpp +++ b/src/seltrans.cpp @@ -31,7 +31,7 @@ #include "knot.h" #include "snap.h" #include "selection.h" -#include "select-context.h" +#include "ui/tools/select-tool.h" #include "sp-item.h" #include "sp-item-transform.h" #include "seltrans-handles.h" diff --git a/src/shortcuts.cpp b/src/shortcuts.cpp index ab8e5f761..1e43c98c6 100644 --- a/src/shortcuts.cpp +++ b/src/shortcuts.cpp @@ -43,7 +43,7 @@ #include "xml/repr.h" #include "document.h" #include "preferences.h" -#include "event-context.h" +#include "ui/tools/tool-base.h" #include "inkscape.h" #include "desktop.h" #include "path-prefix.h" diff --git a/src/snap.cpp b/src/snap.cpp index 15f24ef53..ecf799cdc 100644 --- a/src/snap.cpp +++ b/src/snap.cpp @@ -32,7 +32,7 @@ #include "selection.h" #include "sp-guide.h" #include "preferences.h" -#include "event-context.h" +#include "ui/tools/tool-base.h" #include "util/mathfns.h" using std::vector; using Inkscape::Util::round_to_upper_multiple_plus; diff --git a/src/sp-path.cpp b/src/sp-path.cpp index 563c5b326..cbb61b0f6 100644 --- a/src/sp-path.cpp +++ b/src/sp-path.cpp @@ -43,7 +43,7 @@ #include "desktop.h" #include "desktop-handles.h" #include "desktop-style.h" -#include "event-context.h" +#include "ui/tools/tool-base.h" #include "inkscape.h" #include "style.h" #include "message-stack.h" diff --git a/src/spiral-context.cpp b/src/spiral-context.cpp deleted file mode 100644 index d8e51c91e..000000000 --- a/src/spiral-context.cpp +++ /dev/null @@ -1,467 +0,0 @@ -/* - * Spiral drawing context - * - * Authors: - * Mitsuru Oka - * Lauris Kaplinski - * bulia byak - * Jon A. Cruz - * Abhishek Sharma - * - * Copyright (C) 1999-2001 Lauris Kaplinski - * Copyright (C) 2001-2002 Mitsuru Oka - * - * Released under GNU GPL - */ - -#include "config.h" - -#include -#include -#include - -#include "macros.h" -#include "display/sp-canvas.h" -#include "sp-spiral.h" -#include "document.h" -#include "document-undo.h" -#include "sp-namedview.h" -#include "selection.h" -#include "desktop-handles.h" -#include "snap.h" -#include "desktop.h" -#include "desktop-style.h" -#include "message-context.h" -#include "pixmaps/cursor-spiral.xpm" -#include "spiral-context.h" -#include -#include "xml/repr.h" -#include "xml/node-event-vector.h" -#include "preferences.h" -#include "context-fns.h" -#include "shape-editor.h" -#include "verbs.h" -#include "display/sp-canvas-item.h" - -using Inkscape::DocumentUndo; - -#include "tool-factory.h" - -namespace Inkscape { -namespace UI { -namespace Tools { - -namespace { - ToolBase* createSpiralContext() { - return new SpiralTool(); - } - - bool spiralContextRegistered = ToolFactory::instance().registerObject("/tools/shapes/spiral", createSpiralContext); -} - -const std::string& SpiralTool::getPrefsPath() { - return SpiralTool::prefsPath; -} - -const std::string SpiralTool::prefsPath = "/tools/shapes/spiral"; - -SpiralTool::SpiralTool() : ToolBase() { - this->cursor_shape = cursor_spiral_xpm; - this->hot_x = 4; - this->hot_y = 4; - this->xp = 0; - this->yp = 0; - this->tolerance = 0; - this->within_tolerance = false; - this->item_to_select = NULL; - - this->spiral = NULL; - - this->revo = 3.0; - this->exp = 1.0; - this->t0 = 0.0; -} - -void SpiralTool::finish() { - SPDesktop *desktop = this->desktop; - - sp_canvas_item_ungrab(SP_CANVAS_ITEM(desktop->acetate), GDK_CURRENT_TIME); - - this->finishItem(); - this->sel_changed_connection.disconnect(); - - ToolBase::finish(); -} - -SpiralTool::~SpiralTool() { - this->enableGrDrag(false); - - this->sel_changed_connection.disconnect(); - - delete this->shape_editor; - this->shape_editor = NULL; - - /* fixme: This is necessary because we do not grab */ - if (this->spiral) { - this->finishItem(); - } -} - -/** - * Callback that processes the "changed" signal on the selection; - * destroys old and creates new knotholder. - */ -void SpiralTool::selection_changed(Inkscape::Selection *selection) { - this->shape_editor->unset_item(SH_KNOTHOLDER); - this->shape_editor->set_item(selection->singleItem(), SH_KNOTHOLDER); -} - -void SpiralTool::setup() { - ToolBase::setup(); - - sp_event_context_read(this, "expansion"); - sp_event_context_read(this, "revolution"); - sp_event_context_read(this, "t0"); - - this->shape_editor = new ShapeEditor(this->desktop); - - SPItem *item = sp_desktop_selection(this->desktop)->singleItem(); - if (item) { - this->shape_editor->set_item(item, SH_KNOTHOLDER); - } - - Inkscape::Selection *selection = sp_desktop_selection(this->desktop); - this->sel_changed_connection.disconnect(); - - this->sel_changed_connection = selection->connectChanged(sigc::mem_fun(this, &SpiralTool::selection_changed)); - - Inkscape::Preferences *prefs = Inkscape::Preferences::get(); - - if (prefs->getBool("/tools/shapes/selcue")) { - this->enableSelectionCue(); - } - - if (prefs->getBool("/tools/shapes/gradientdrag")) { - this->enableGrDrag(); - } -} - -void SpiralTool::set(const Inkscape::Preferences::Entry& val) { - Glib::ustring name = val.getEntryName(); - - if (name == "expansion") { - this->exp = CLAMP(val.getDouble(), 0.0, 1000.0); - } else if (name == "revolution") { - this->revo = CLAMP(val.getDouble(3.0), 0.05, 40.0); - } else if (name == "t0") { - this->t0 = CLAMP(val.getDouble(), 0.0, 0.999); - } -} - -bool SpiralTool::root_handler(GdkEvent* event) { - static gboolean dragging; - - SPDesktop *desktop = this->desktop; - Inkscape::Selection *selection = sp_desktop_selection (desktop); - - Inkscape::Preferences *prefs = Inkscape::Preferences::get(); - this->tolerance = prefs->getIntLimited("/options/dragtolerance/value", 0, 0, 100); - - gint ret = FALSE; - - switch (event->type) { - case GDK_BUTTON_PRESS: - if (event->button.button == 1 && !this->space_panning) { - dragging = TRUE; - - this->center = Inkscape::setup_for_drag_start(desktop, this, event); - - SnapManager &m = desktop->namedview->snap_manager; - m.setup(desktop); - m.freeSnapReturnByRef(this->center, Inkscape::SNAPSOURCE_NODE_HANDLE); - m.unSetup(); - - sp_canvas_item_grab(SP_CANVAS_ITEM(desktop->acetate), - ( GDK_KEY_PRESS_MASK | - GDK_BUTTON_RELEASE_MASK | - GDK_POINTER_MOTION_MASK | - GDK_POINTER_MOTION_HINT_MASK | - GDK_BUTTON_PRESS_MASK ), - NULL, event->button.time); - ret = TRUE; - } - break; - - case GDK_MOTION_NOTIFY: - if (dragging && (event->motion.state & GDK_BUTTON1_MASK) && !this->space_panning) { - if ( this->within_tolerance - && ( abs( (gint) event->motion.x - this->xp ) < this->tolerance ) - && ( abs( (gint) event->motion.y - this->yp ) < this->tolerance ) ) { - break; // do not drag if we're within tolerance from origin - } - // Once the user has moved farther than tolerance from the original location - // (indicating they intend to draw, not click), then always process the - // motion notify coordinates as given (no snapping back to origin) - this->within_tolerance = false; - - Geom::Point const motion_w(event->motion.x, event->motion.y); - Geom::Point motion_dt(this->desktop->w2d(motion_w)); - - SnapManager &m = desktop->namedview->snap_manager; - m.setup(desktop, true, this->spiral); - m.freeSnapReturnByRef(motion_dt, Inkscape::SNAPSOURCE_NODE_HANDLE); - m.unSetup(); - - this->drag(motion_dt, event->motion.state); - - gobble_motion_events(GDK_BUTTON1_MASK); - - ret = TRUE; - } else if (!sp_event_context_knot_mouseover(this)) { - SnapManager &m = desktop->namedview->snap_manager; - m.setup(desktop); - Geom::Point const motion_w(event->motion.x, event->motion.y); - Geom::Point motion_dt(desktop->w2d(motion_w)); - m.preSnap(Inkscape::SnapCandidatePoint(motion_dt, Inkscape::SNAPSOURCE_NODE_HANDLE)); - m.unSetup(); - } - break; - - case GDK_BUTTON_RELEASE: - this->xp = this->yp = 0; - if (event->button.button == 1 && !this->space_panning) { - dragging = FALSE; - sp_event_context_discard_delayed_snap_event(this); - - if (!this->within_tolerance) { - // we've been dragging, finish the spiral - this->finishItem(); - } else if (this->item_to_select) { - // no dragging, select clicked item if any - if (event->button.state & GDK_SHIFT_MASK) { - selection->toggle(this->item_to_select); - } else { - selection->set(this->item_to_select); - } - } else { - // click in an empty space - selection->clear(); - } - - this->item_to_select = NULL; - ret = TRUE; - sp_canvas_item_ungrab(SP_CANVAS_ITEM(desktop->acetate), event->button.time); - } - break; - - case GDK_KEY_PRESS: - switch (get_group0_keyval(&event->key)) { - case GDK_KEY_Alt_R: - case GDK_KEY_Control_L: - case GDK_KEY_Control_R: - case GDK_KEY_Shift_L: - case GDK_KEY_Shift_R: - case GDK_KEY_Meta_L: // Meta is when you press Shift+Alt (at least on my machine) - case GDK_KEY_Meta_R: - sp_event_show_modifier_tip(this->defaultMessageContext(), event, - _("Ctrl: snap angle"), - NULL, - _("Alt: lock spiral radius")); - break; - case GDK_KEY_Up: - case GDK_KEY_Down: - case GDK_KEY_KP_Up: - case GDK_KEY_KP_Down: - // prevent the zoom field from activation - if (!MOD__CTRL_ONLY(event)) - ret = TRUE; - break; - - case GDK_KEY_x: - case GDK_KEY_X: - if (MOD__ALT_ONLY(event)) { - desktop->setToolboxFocusTo ("altx-spiral"); - ret = TRUE; - } - break; - - case GDK_KEY_Escape: - if (dragging) { - dragging = false; - sp_event_context_discard_delayed_snap_event(this); - // if drawing, cancel, otherwise pass it up for deselecting - this->cancel(); - ret = TRUE; - } - break; - - case GDK_KEY_space: - if (dragging) { - sp_canvas_item_ungrab(SP_CANVAS_ITEM(desktop->acetate), - event->button.time); - dragging = false; - sp_event_context_discard_delayed_snap_event(this); - - if (!this->within_tolerance) { - // we've been dragging, finish the spiral - this->finish(); - } - // do not return true, so that space would work switching to selector - } - break; - - case GDK_KEY_Delete: - case GDK_KEY_KP_Delete: - case GDK_KEY_BackSpace: - ret = this->deleteSelectedDrag(MOD__CTRL_ONLY(event)); - break; - - default: - break; - } - break; - - case GDK_KEY_RELEASE: - switch (get_group0_keyval(&event->key)) { - case GDK_KEY_Alt_L: - case GDK_KEY_Alt_R: - case GDK_KEY_Control_L: - case GDK_KEY_Control_R: - case GDK_KEY_Shift_L: - case GDK_KEY_Shift_R: - case GDK_KEY_Meta_L: // Meta is when you press Shift+Alt - case GDK_KEY_Meta_R: - this->defaultMessageContext()->clear(); - break; - - default: - break; - } - break; - - default: - break; - } - - if (!ret) { - ret = ToolBase::root_handler(event); - } - - return ret; -} - -void SpiralTool::drag(Geom::Point const &p, guint state) { - SPDesktop *desktop = SP_EVENT_CONTEXT(this)->desktop; - - Inkscape::Preferences *prefs = Inkscape::Preferences::get(); - int const snaps = prefs->getInt("/options/rotationsnapsperpi/value", 12); - - if (!this->spiral) { - if (Inkscape::have_viable_layer(desktop, this->message_context) == false) { - return; - } - - // Create object - Inkscape::XML::Document *xml_doc = SP_EVENT_CONTEXT_DOCUMENT(this)->getReprDoc(); - Inkscape::XML::Node *repr = xml_doc->createElement("svg:path"); - repr->setAttribute("sodipodi:type", "spiral"); - - // Set style - sp_desktop_apply_style_tool(desktop, repr, "/tools/shapes/spiral", false); - - this->spiral = SP_SPIRAL(desktop->currentLayer()->appendChildRepr(repr)); - Inkscape::GC::release(repr); - this->spiral->transform = SP_ITEM(desktop->currentLayer())->i2doc_affine().inverse(); - this->spiral->updateRepr(); - - desktop->canvas->forceFullRedrawAfterInterruptions(5); - } - - SnapManager &m = desktop->namedview->snap_manager; - m.setup(desktop, true, this->spiral); - Geom::Point pt2g = p; - m.freeSnapReturnByRef(pt2g, Inkscape::SNAPSOURCE_NODE_HANDLE); - m.unSetup(); - Geom::Point const p0 = desktop->dt2doc(this->center); - Geom::Point const p1 = desktop->dt2doc(pt2g); - - Geom::Point const delta = p1 - p0; - gdouble const rad = Geom::L2(delta); - - gdouble arg = Geom::atan2(delta) - 2.0*M_PI*this->spiral->revo; - - if (state & GDK_CONTROL_MASK) { - arg = sp_round(arg, M_PI/snaps); - } - - /* Fixme: these parameters should be got from dialog box */ - this->spiral->setPosition(p0[Geom::X], p0[Geom::Y], - /*expansion*/ this->exp, - /*revolution*/ this->revo, - rad, arg, - /*t0*/ this->t0); - - /* status text */ - Inkscape::Util::Quantity q = Inkscape::Util::Quantity(rad, "px"); - GString *rads = g_string_new(q.string(desktop->namedview->doc_units).c_str()); - this->message_context->setF(Inkscape::IMMEDIATE_MESSAGE, - _("Spiral: radius %s, angle %5g°; with Ctrl to snap angle"), - rads->str, sp_round((arg + 2.0*M_PI*this->spiral->revo)*180/M_PI, 0.0001)); - g_string_free(rads, FALSE); -} - -void SpiralTool::finishItem() { - this->message_context->clear(); - - if (this->spiral != NULL) { - if (this->spiral->rad == 0) { - this->cancel(); // Don't allow the creating of zero sized spiral, for example when the start and and point snap to the snap grid point - return; - } - - spiral->set_shape(); - spiral->updateRepr(SP_OBJECT_WRITE_EXT); - spiral->doWriteTransform(spiral->getRepr(), spiral->transform, NULL, true); - - this->desktop->canvas->endForcedFullRedraws(); - - sp_desktop_selection(this->desktop)->set(this->spiral); - DocumentUndo::done(sp_desktop_document(this->desktop), SP_VERB_CONTEXT_SPIRAL, _("Create spiral")); - - this->spiral = NULL; - } -} - -void SpiralTool::cancel() { - sp_desktop_selection(this->desktop)->clear(); - sp_canvas_item_ungrab(SP_CANVAS_ITEM(this->desktop->acetate), 0); - - if (this->spiral != NULL) { - this->spiral->deleteObject(); - this->spiral = NULL; - } - - this->within_tolerance = false; - this->xp = 0; - this->yp = 0; - this->item_to_select = NULL; - - this->desktop->canvas->endForcedFullRedraws(); - - DocumentUndo::cancel(sp_desktop_document(this->desktop)); -} - -} -} -} - -/* - Local Variables: - mode:c++ - c-file-style:"stroustrup" - c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) - indent-tabs-mode:nil - fill-column:99 - End: -*/ -// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/spiral-context.h b/src/spiral-context.h deleted file mode 100644 index e6b5eeb0c..000000000 --- a/src/spiral-context.h +++ /dev/null @@ -1,66 +0,0 @@ -#ifndef __SP_SPIRAL_CONTEXT_H__ -#define __SP_SPIRAL_CONTEXT_H__ - -/** \file - * Spiral drawing context - */ -/* - * Authors: - * Mitsuru Oka - * Lauris Kaplinski - * - * Copyright (C) 1999-2001 Lauris Kaplinski - * Copyright (C) 2001-2002 Mitsuru Oka - * - * Released under GNU GPL - */ - -#include -#include -#include -#include <2geom/point.h> -#include "event-context.h" - -#include "sp-spiral.h" - -#define SP_SPIRAL_CONTEXT(obj) (dynamic_cast((Inkscape::UI::Tools::ToolBase*)obj)) -#define SP_IS_SPIRAL_CONTEXT(obj) (dynamic_cast((const Inkscape::UI::Tools::ToolBase*)obj) != NULL) - -namespace Inkscape { -namespace UI { -namespace Tools { - -class SpiralTool : public ToolBase { -public: - SpiralTool(); - virtual ~SpiralTool(); - - static const std::string prefsPath; - - virtual void setup(); - virtual void finish(); - virtual void set(const Inkscape::Preferences::Entry& val); - virtual bool root_handler(GdkEvent* event); - - virtual const std::string& getPrefsPath(); - -private: - SPSpiral * spiral; - Geom::Point center; - gdouble revo; - gdouble exp; - gdouble t0; - - sigc::connection sel_changed_connection; - - void drag(Geom::Point const &p, guint state); - void finishItem(); - void cancel(); - void selection_changed(Inkscape::Selection *selection); -}; - -} -} -} - -#endif diff --git a/src/spray-context.cpp b/src/spray-context.cpp deleted file mode 100644 index 5a2346dab..000000000 --- a/src/spray-context.cpp +++ /dev/null @@ -1,890 +0,0 @@ -/* - * Spray Tool - * - * Authors: - * Pierre-Antoine MARC - * Pierre CACLIN - * Aurel-Aimé MARMION - * Julien LERAY - * Benoît LAVORATA - * Vincent MONTAGNE - * Pierre BARBRY-BLOT - * Steren GIANNINI (steren.giannini@gmail.com) - * Jon A. Cruz - * Abhishek Sharma - * - * Copyright (C) 2009 authors - * - * Released under GNU GPL, read the file 'COPYING' for more information - */ - -#include "config.h" - -#include - -#include "ui/dialog/dialog-manager.h" - -#include "svg/svg.h" - -#include -#include "macros.h" -#include "document.h" -#include "document-undo.h" -#include "selection.h" -#include "desktop.h" -#include "desktop-events.h" -#include "desktop-handles.h" -#include "message-context.h" -#include "pixmaps/cursor-spray.xpm" -#include -#include "xml/repr.h" -#include "context-fns.h" -#include "sp-item.h" -#include "inkscape.h" - -#include "splivarot.h" -#include "sp-item-group.h" -#include "sp-shape.h" -#include "sp-path.h" -#include "path-chemistry.h" - -#include "sp-text.h" -#include "sp-flowtext.h" -#include "display/sp-canvas.h" -#include "display/canvas-bpath.h" -#include "display/canvas-arena.h" -#include "display/curve.h" -#include "livarot/Shape.h" -#include <2geom/circle.h> -#include <2geom/transforms.h> -#include "preferences.h" -#include "style.h" -#include "box3d.h" -#include "sp-item-transform.h" -#include "filter-chemistry.h" - -#include "spray-context.h" -#include "helper/action.h" -#include "verbs.h" - -#include - -#include -#include -#include - -using Inkscape::DocumentUndo; -using namespace std; - -#define DDC_RED_RGBA 0xff0000ff -#define DYNA_MIN_WIDTH 1.0e-6 - -#include "tool-factory.h" - -namespace Inkscape { -namespace UI { -namespace Tools { - -namespace { - ToolBase* createSprayContext() { - return new SprayTool(); - } - - bool sprayContextRegistered = ToolFactory::instance().registerObject("/tools/spray", createSprayContext); -} - -const std::string& SprayTool::getPrefsPath() { - return SprayTool::prefsPath; -} - -const std::string SprayTool::prefsPath = "/tools/spray"; - -/** - * This function returns pseudo-random numbers from a normal distribution - * @param mu : mean - * @param sigma : standard deviation ( > 0 ) - */ -inline double NormalDistribution(double mu, double sigma) -{ - // use Box Muller's algorithm - return mu + sigma * sqrt( -2.0 * log(g_random_double_range(0, 1)) ) * cos( 2.0*M_PI*g_random_double_range(0, 1) ); -} - -/* Method to rotate items */ -static void sp_spray_rotate_rel(Geom::Point c, SPDesktop */*desktop*/, SPItem *item, Geom::Rotate const &rotation) -{ - Geom::Translate const s(c); - Geom::Affine affine = Geom::Affine(s).inverse() * Geom::Affine(rotation) * Geom::Affine(s); - // Rotate item. - item->set_i2d_affine(item->i2dt_affine() * (Geom::Affine)affine); - // Use each item's own transform writer, consistent with sp_selection_apply_affine() - item->doWriteTransform(item->getRepr(), item->transform); - // Restore the center position (it's changed because the bbox center changed) - if (item->isCenterSet()) { - item->setCenter(c); - item->updateRepr(); - } -} - -/* Method to scale items */ -static void sp_spray_scale_rel(Geom::Point c, SPDesktop */*desktop*/, SPItem *item, Geom::Scale const &scale) -{ - Geom::Translate const s(c); - item->set_i2d_affine(item->i2dt_affine() * s.inverse() * scale * s); - item->doWriteTransform(item->getRepr(), item->transform); -} - -SprayTool::SprayTool() : ToolBase() { - this->usetilt = 0; - this->dilate_area = 0; - this->usetext = false; - this->population = 0; - this->is_drawing = false; - this->mode = 0; - this->usepressure = 0; - - this->cursor_shape = cursor_spray_xpm; - this->hot_x = 4; - this->hot_y = 4; - - /* attributes */ - this->dragging = FALSE; - this->distrib = 1; - this->width = 0.2; - this->force = 0.2; - this->ratio = 0; - this->tilt = 0; - this->mean = 0.2; - this->rotation_variation = 0; - this->standard_deviation = 0.2; - this->scale = 1; - this->scale_variation = 1; - this->pressure = TC_DEFAULT_PRESSURE; - - this->is_dilating = false; - this->has_dilated = false; -} - -SprayTool::~SprayTool() { - this->enableGrDrag(false); - this->style_set_connection.disconnect(); - - if (this->dilate_area) { - sp_canvas_item_destroy(this->dilate_area); - this->dilate_area = NULL; - } -} - -static bool is_transform_modes(gint mode) -{ - return (mode == SPRAY_MODE_COPY || - mode == SPRAY_MODE_CLONE || - mode == SPRAY_MODE_SINGLE_PATH || - mode == SPRAY_OPTION); -} - -void SprayTool::update_cursor(bool /*with_shift*/) { - guint num = 0; - gchar *sel_message = NULL; - - if (!desktop->selection->isEmpty()) { - num = g_slist_length(const_cast(desktop->selection->itemList())); - sel_message = g_strdup_printf(ngettext("%i object selected","%i objects selected",num), num); - } else { - sel_message = g_strdup_printf("%s", _("Nothing selected")); - } - - switch (this->mode) { - case SPRAY_MODE_COPY: - this->message_context->setF(Inkscape::NORMAL_MESSAGE, _("%s. Drag, click or click and scroll to spray copies of the initial selection."), sel_message); - break; - case SPRAY_MODE_CLONE: - this->message_context->setF(Inkscape::NORMAL_MESSAGE, _("%s. Drag, click or click and scroll to spray clones of the initial selection."), sel_message); - break; - case SPRAY_MODE_SINGLE_PATH: - this->message_context->setF(Inkscape::NORMAL_MESSAGE, _("%s. Drag, click or click and scroll to spray in a single path of the initial selection."), sel_message); - break; - default: - break; - } - - this->sp_event_context_update_cursor(); - g_free(sel_message); -} - -void SprayTool::setup() { - ToolBase::setup(); - - { - /* TODO: have a look at sp_dyna_draw_context_setup where the same is done.. generalize? at least make it an arcto! */ - Geom::PathVector path; - Geom::Circle(0, 0, 1).getPath(path); - - SPCurve *c = new SPCurve(path); - - this->dilate_area = sp_canvas_bpath_new(sp_desktop_controls(this->desktop), c); - c->unref(); - sp_canvas_bpath_set_fill(SP_CANVAS_BPATH(this->dilate_area), 0x00000000,(SPWindRule)0); - sp_canvas_bpath_set_stroke(SP_CANVAS_BPATH(this->dilate_area), 0xff9900ff, 1.0, SP_STROKE_LINEJOIN_MITER, SP_STROKE_LINECAP_BUTT); - sp_canvas_item_hide(this->dilate_area); - } - - this->is_drawing = false; - - sp_event_context_read(this, "distrib"); - sp_event_context_read(this, "width"); - sp_event_context_read(this, "ratio"); - sp_event_context_read(this, "tilt"); - sp_event_context_read(this, "rotation_variation"); - sp_event_context_read(this, "scale_variation"); - sp_event_context_read(this, "mode"); - sp_event_context_read(this, "population"); - sp_event_context_read(this, "force"); - sp_event_context_read(this, "mean"); - sp_event_context_read(this, "standard_deviation"); - sp_event_context_read(this, "usepressure"); - sp_event_context_read(this, "Scale"); - - Inkscape::Preferences *prefs = Inkscape::Preferences::get(); - if (prefs->getBool("/tools/spray/selcue")) { - this->enableSelectionCue(); - } - if (prefs->getBool("/tools/spray/gradientdrag")) { - this->enableGrDrag(); - } -} - -void SprayTool::set(const Inkscape::Preferences::Entry& val) { - Glib::ustring path = val.getEntryName(); - - if (path == "mode") { - this->mode = val.getInt(); - this->update_cursor(false); - } else if (path == "width") { - this->width = 0.01 * CLAMP(val.getInt(10), 1, 100); - } else if (path == "usepressure") { - this->usepressure = val.getBool(); - } else if (path == "population") { - this->population = 0.01 * CLAMP(val.getInt(10), 1, 100); - } else if (path == "rotation_variation") { - this->rotation_variation = CLAMP(val.getDouble(0.0), 0, 100.0); - } else if (path == "scale_variation") { - this->scale_variation = CLAMP(val.getDouble(1.0), 0, 100.0); - } else if (path == "standard_deviation") { - this->standard_deviation = 0.01 * CLAMP(val.getInt(10), 1, 100); - } else if (path == "mean") { - this->mean = 0.01 * CLAMP(val.getInt(10), 1, 100); -// Not implemented in the toolbar and preferences yet - } else if (path == "distribution") { - this->distrib = val.getInt(1); - } else if (path == "tilt") { - this->tilt = CLAMP(val.getDouble(0.1), 0, 1000.0); - } else if (path == "ratio") { - this->ratio = CLAMP(val.getDouble(), 0.0, 0.9); - } else if (path == "force") { - this->force = CLAMP(val.getDouble(1.0), 0, 1.0); - } -} - -static void sp_spray_extinput(SprayTool *tc, GdkEvent *event) -{ - if (gdk_event_get_axis(event, GDK_AXIS_PRESSURE, &tc->pressure)) { - tc->pressure = CLAMP(tc->pressure, TC_MIN_PRESSURE, TC_MAX_PRESSURE); - } else { - tc->pressure = TC_DEFAULT_PRESSURE; - } -} - -static double get_dilate_radius(SprayTool *tc) -{ - return 250 * tc->width/SP_EVENT_CONTEXT(tc)->desktop->current_zoom(); -} - -static double get_path_force(SprayTool *tc) -{ - double force = 8 * (tc->usepressure? tc->pressure : TC_DEFAULT_PRESSURE) - /sqrt(SP_EVENT_CONTEXT(tc)->desktop->current_zoom()); - if (force > 3) { - force += 4 * (force - 3); - } - return force * tc->force; -} - -static double get_path_mean(SprayTool *tc) -{ - return tc->mean; -} - -static double get_path_standard_deviation(SprayTool *tc) -{ - return tc->standard_deviation; -} - -static double get_move_force(SprayTool *tc) -{ - double force = (tc->usepressure? tc->pressure : TC_DEFAULT_PRESSURE); - return force * tc->force; -} - -static double get_move_mean(SprayTool *tc) -{ - return tc->mean; -} - -static double get_move_standard_deviation(SprayTool *tc) -{ - return tc->standard_deviation; -} - -/** - * Method to handle the distribution of the items - * @param[out] radius : radius of the position of the sprayed object - * @param[out] angle : angle of the position of the sprayed object - * @param[in] a : mean - * @param[in] s : standard deviation - * @param[in] choice : - - */ -static void random_position(double &radius, double &angle, double &a, double &s, int /*choice*/) -{ - // angle is taken from an uniform distribution - angle = g_random_double_range(0, M_PI*2.0); - - // radius is taken from a Normal Distribution - double radius_temp =-1; - while(!((radius_temp >= 0) && (radius_temp <=1 ))) - { - radius_temp = NormalDistribution(a, s); - } - // Because we are in polar coordinates, a special treatment has to be done to the radius. - // Otherwise, positions taken from an uniform repartition on radius and angle will not seam to - // be uniformily distributed on the disk (more at the center and less at the boundary). - // We counter this effect with a 0.5 exponent. This is empiric. - radius = pow(radius_temp, 0.5); - -} - -static bool sp_spray_recursive(SPDesktop *desktop, - Inkscape::Selection *selection, - SPItem *item, - Geom::Point p, - Geom::Point /*vector*/, - gint mode, - double radius, - double /*force*/, - double population, - double &scale, - double scale_variation, - bool /*reverse*/, - double mean, - double standard_deviation, - double ratio, - double tilt, - double rotation_variation, - gint _distrib) -{ - bool did = false; - - if (SP_IS_BOX3D(item) ) { - // convert 3D boxes to ordinary groups before spraying their shapes - item = box3d_convert_to_group(SP_BOX3D(item)); - selection->add(item); - } - - double _fid = g_random_double_range(0, 1); - double angle = g_random_double_range( - rotation_variation / 100.0 * M_PI , rotation_variation / 100.0 * M_PI ); - double _scale = g_random_double_range( 1.0 - scale_variation / 100.0, 1.0 + scale_variation / 100.0 ); - double dr; double dp; - random_position( dr, dp, mean, standard_deviation, _distrib ); - dr=dr*radius; - - if (mode == SPRAY_MODE_COPY) { - Geom::OptRect a = item->documentVisualBounds(); - if (a) { - SPItem *item_copied; - if(_fid <= population) - { - // duplicate - SPDocument *doc = item->document; - Inkscape::XML::Document* xml_doc = doc->getReprDoc(); - Inkscape::XML::Node *old_repr = item->getRepr(); - Inkscape::XML::Node *parent = old_repr->parent(); - Inkscape::XML::Node *copy = old_repr->duplicate(xml_doc); - parent->appendChild(copy); - - SPObject *new_obj = doc->getObjectByRepr(copy); - item_copied = SP_ITEM(new_obj); //convertion object->item - Geom::Point center=item->getCenter(); - sp_spray_scale_rel(center,desktop, item_copied, Geom::Scale(_scale,_scale)); - sp_spray_scale_rel(center,desktop, item_copied, Geom::Scale(scale,scale)); - - sp_spray_rotate_rel(center,desktop,item_copied, Geom::Rotate(angle)); - //Move the cursor p - Geom::Point move = (Geom::Point(cos(tilt)*cos(dp)*dr/(1-ratio)+sin(tilt)*sin(dp)*dr/(1+ratio), -sin(tilt)*cos(dp)*dr/(1-ratio)+cos(tilt)*sin(dp)*dr/(1+ratio)))+(p-a->midpoint()); - sp_item_move_rel(item_copied, Geom::Translate(move[Geom::X], -move[Geom::Y])); - did = true; - } - } - } else if (mode == SPRAY_MODE_SINGLE_PATH) { - - SPItem *father = NULL; //initial Object - SPItem *item_copied = NULL; //Projected Object - SPItem *unionResult = NULL; //previous union - SPItem *son = NULL; //father copy - - int i=1; - for (GSList *items = g_slist_copy(const_cast(selection->itemList())); - items != NULL; - items = items->next) { - - SPItem *item1 = SP_ITEM(items->data); - if (i == 1) { - father = item1; - } - if (i == 2) { - unionResult = item1; - } - i++; - } - SPDocument *doc = father->document; - Inkscape::XML::Document* xml_doc = doc->getReprDoc(); - Inkscape::XML::Node *old_repr = father->getRepr(); - Inkscape::XML::Node *parent = old_repr->parent(); - - Geom::OptRect a = father->documentVisualBounds(); - if (a) { - if (i == 2) { - Inkscape::XML::Node *copy1 = old_repr->duplicate(xml_doc); - parent->appendChild(copy1); - SPObject *new_obj1 = doc->getObjectByRepr(copy1); - son = SP_ITEM(new_obj1); // conversion object->item - unionResult = son; - Inkscape::GC::release(copy1); - } - - if (_fid <= population) { // Rules the population of objects sprayed - // duplicates the father - Inkscape::XML::Node *copy2 = old_repr->duplicate(xml_doc); - parent->appendChild(copy2); - SPObject *new_obj2 = doc->getObjectByRepr(copy2); - item_copied = SP_ITEM(new_obj2); - - // Move around the cursor - Geom::Point move = (Geom::Point(cos(tilt)*cos(dp)*dr/(1-ratio)+sin(tilt)*sin(dp)*dr/(1+ratio), -sin(tilt)*cos(dp)*dr/(1-ratio)+cos(tilt)*sin(dp)*dr/(1+ratio)))+(p-a->midpoint()); - - Geom::Point center=father->getCenter(); - sp_spray_scale_rel(center, desktop, item_copied, Geom::Scale(_scale, _scale)); - sp_spray_scale_rel(center, desktop, item_copied, Geom::Scale(scale, scale)); - sp_spray_rotate_rel(center, desktop, item_copied, Geom::Rotate(angle)); - sp_item_move_rel(item_copied, Geom::Translate(move[Geom::X], -move[Geom::Y])); - - // union and duplication - selection->clear(); - selection->add(item_copied); - selection->add(unionResult); - sp_selected_path_union_skip_undo(selection, selection->desktop()); - selection->add(father); - Inkscape::GC::release(copy2); - did = true; - } - } - } else if (mode == SPRAY_MODE_CLONE) { - Geom::OptRect a = item->documentVisualBounds(); - if (a) { - if(_fid <= population) { - SPItem *item_copied; - SPDocument *doc = item->document; - Inkscape::XML::Document* xml_doc = doc->getReprDoc(); - Inkscape::XML::Node *old_repr = item->getRepr(); - Inkscape::XML::Node *parent = old_repr->parent(); - - // Creation of the clone - Inkscape::XML::Node *clone = xml_doc->createElement("svg:use"); - // Ad the clone to the list of the father's sons - parent->appendChild(clone); - // Generates the link between father and son attributes - gchar *href_str = g_strdup_printf("#%s", old_repr->attribute("id")); - clone->setAttribute("xlink:href", href_str, false); - g_free(href_str); - - SPObject *clone_object = doc->getObjectByRepr(clone); - // conversion object->item - item_copied = SP_ITEM(clone_object); - Geom::Point center = item->getCenter(); - sp_spray_scale_rel(center, desktop, item_copied, Geom::Scale(_scale, _scale)); - sp_spray_scale_rel(center, desktop, item_copied, Geom::Scale(scale, scale)); - sp_spray_rotate_rel(center, desktop, item_copied, Geom::Rotate(angle)); - Geom::Point move = (Geom::Point(cos(tilt)*cos(dp)*dr/(1-ratio)+sin(tilt)*sin(dp)*dr/(1+ratio), -sin(tilt)*cos(dp)*dr/(1-ratio)+cos(tilt)*sin(dp)*dr/(1+ratio)))+(p-a->midpoint()); - sp_item_move_rel(item_copied, Geom::Translate(move[Geom::X], -move[Geom::Y])); - - Inkscape::GC::release(clone); - - did = true; - } - } - } - - return did; -} - -static bool sp_spray_dilate(SprayTool *tc, Geom::Point /*event_p*/, Geom::Point p, Geom::Point vector, bool reverse) -{ - Inkscape::Selection *selection = sp_desktop_selection(SP_EVENT_CONTEXT(tc)->desktop); - SPDesktop *desktop = SP_EVENT_CONTEXT(tc)->desktop; - - if (selection->isEmpty()) { - return false; - } - - bool did = false; - double radius = get_dilate_radius(tc); - double path_force = get_path_force(tc); - if (radius == 0 || path_force == 0) { - return false; - } - double path_mean = get_path_mean(tc); - if (radius == 0 || path_mean == 0) { - return false; - } - double path_standard_deviation = get_path_standard_deviation(tc); - if (radius == 0 || path_standard_deviation == 0) { - return false; - } - double move_force = get_move_force(tc); - double move_mean = get_move_mean(tc); - double move_standard_deviation = get_move_standard_deviation(tc); - - for (GSList *items = g_slist_copy(const_cast(selection->itemList())); - items != NULL; - items = items->next) { - - SPItem *item = SP_ITEM(items->data); - - if (is_transform_modes(tc->mode)) { - if (sp_spray_recursive(desktop, selection, item, p, vector, tc->mode, radius, move_force, tc->population, tc->scale, tc->scale_variation, reverse, move_mean, move_standard_deviation, tc->ratio, tc->tilt, tc->rotation_variation, tc->distrib)) - did = true; - } else { - if (sp_spray_recursive(desktop, selection, item, p, vector, tc->mode, radius, path_force, tc->population, tc->scale, tc->scale_variation, reverse, path_mean, path_standard_deviation, tc->ratio, tc->tilt, tc->rotation_variation, tc->distrib)) - did = true; - } - } - - return did; -} - -static void sp_spray_update_area(SprayTool *tc) -{ - double radius = get_dilate_radius(tc); - Geom::Affine const sm ( Geom::Scale(radius/(1-tc->ratio), radius/(1+tc->ratio)) ); - sp_canvas_item_affine_absolute(tc->dilate_area, (sm* Geom::Rotate(tc->tilt))* Geom::Translate(SP_EVENT_CONTEXT(tc)->desktop->point())); - sp_canvas_item_show(tc->dilate_area); -} - -static void sp_spray_switch_mode(SprayTool *tc, gint mode, bool with_shift) -{ - // select the button mode - SP_EVENT_CONTEXT(tc)->desktop->setToolboxSelectOneValue("spray_tool_mode", mode); - // need to set explicitly, because the prefs may not have changed by the previous - tc->mode = mode; - tc->update_cursor(with_shift); -} - -bool SprayTool::root_handler(GdkEvent* event) { - gint ret = FALSE; - - switch (event->type) { - case GDK_ENTER_NOTIFY: - sp_canvas_item_show(this->dilate_area); - break; - case GDK_LEAVE_NOTIFY: - sp_canvas_item_hide(this->dilate_area); - break; - case GDK_BUTTON_PRESS: - if (event->button.button == 1 && !this->space_panning) { - if (Inkscape::have_viable_layer(desktop, this->message_context) == false) { - return TRUE; - } - - Geom::Point const motion_w(event->button.x, event->button.y); - Geom::Point const motion_dt(desktop->w2d(motion_w)); - this->last_push = desktop->dt2doc(motion_dt); - - sp_spray_extinput(this, event); - - desktop->canvas->forceFullRedrawAfterInterruptions(3); - this->is_drawing = true; - this->is_dilating = true; - this->has_dilated = false; - - if(this->is_dilating && event->button.button == 1 && !this->space_panning) { - sp_spray_dilate(this, motion_w, desktop->dt2doc(motion_dt), Geom::Point(0,0), MOD__SHIFT(event)); - } - - this->has_dilated = true; - ret = TRUE; - } - break; - case GDK_MOTION_NOTIFY: { - Geom::Point const motion_w(event->motion.x, - event->motion.y); - Geom::Point motion_dt(desktop->w2d(motion_w)); - Geom::Point motion_doc(desktop->dt2doc(motion_dt)); - sp_spray_extinput(this, event); - - // draw the dilating cursor - double radius = get_dilate_radius(this); - Geom::Affine const sm (Geom::Scale(radius/(1-this->ratio), radius/(1+this->ratio)) ); - sp_canvas_item_affine_absolute(this->dilate_area, (sm*Geom::Rotate(this->tilt))*Geom::Translate(desktop->w2d(motion_w))); - sp_canvas_item_show(this->dilate_area); - - guint num = 0; - if (!desktop->selection->isEmpty()) { - num = g_slist_length(const_cast(desktop->selection->itemList())); - } - if (num == 0) { - this->message_context->flash(Inkscape::ERROR_MESSAGE, _("Nothing selected! Select objects to spray.")); - } - - // dilating: - if (this->is_drawing && ( event->motion.state & GDK_BUTTON1_MASK )) { - sp_spray_dilate(this, motion_w, motion_doc, motion_doc - this->last_push, event->button.state & GDK_SHIFT_MASK? true : false); - //this->last_push = motion_doc; - this->has_dilated = true; - - // it's slow, so prevent clogging up with events - gobble_motion_events(GDK_BUTTON1_MASK); - return TRUE; - } - } - break; - /*Spray with the scroll*/ - case GDK_SCROLL: { - if (event->scroll.state & GDK_BUTTON1_MASK) { - double temp ; - temp = this->population; - this->population = 1.0; - desktop->setToolboxAdjustmentValue("population", this->population * 100); - Geom::Point const scroll_w(event->button.x, event->button.y); - Geom::Point const scroll_dt = desktop->point();; - Geom::Point motion_doc(desktop->dt2doc(scroll_dt)); - switch (event->scroll.direction) { - case GDK_SCROLL_DOWN: - case GDK_SCROLL_UP: { - if (Inkscape::have_viable_layer(desktop, this->message_context) == false) { - return TRUE; - } - this->last_push = desktop->dt2doc(scroll_dt); - sp_spray_extinput(this, event); - desktop->canvas->forceFullRedrawAfterInterruptions(3); - this->is_drawing = true; - this->is_dilating = true; - this->has_dilated = false; - if(this->is_dilating && !this->space_panning) { - sp_spray_dilate(this, scroll_w, desktop->dt2doc(scroll_dt), Geom::Point(0,0), false); - } - this->has_dilated = true; - - this->population = temp; - desktop->setToolboxAdjustmentValue("population", this->population * 100); - - ret = TRUE; - } - break; - case GDK_SCROLL_RIGHT: - {} break; - case GDK_SCROLL_LEFT: - {} break; - } - } - break; - } - - case GDK_BUTTON_RELEASE: { - Geom::Point const motion_w(event->button.x, event->button.y); - Geom::Point const motion_dt(desktop->w2d(motion_w)); - - desktop->canvas->endForcedFullRedraws(); - this->is_drawing = false; - - if (this->is_dilating && event->button.button == 1 && !this->space_panning) { - if (!this->has_dilated) { - // if we did not rub, do a light tap - this->pressure = 0.03; - sp_spray_dilate(this, motion_w, desktop->dt2doc(motion_dt), Geom::Point(0,0), MOD__SHIFT(event)); - } - this->is_dilating = false; - this->has_dilated = false; - switch (this->mode) { - case SPRAY_MODE_COPY: - DocumentUndo::done(sp_desktop_document(SP_EVENT_CONTEXT(this)->desktop), - SP_VERB_CONTEXT_SPRAY, _("Spray with copies")); - break; - case SPRAY_MODE_CLONE: - DocumentUndo::done(sp_desktop_document(SP_EVENT_CONTEXT(this)->desktop), - SP_VERB_CONTEXT_SPRAY, _("Spray with clones")); - break; - case SPRAY_MODE_SINGLE_PATH: - DocumentUndo::done(sp_desktop_document(SP_EVENT_CONTEXT(this)->desktop), - SP_VERB_CONTEXT_SPRAY, _("Spray in single path")); - break; - } - } - break; - } - - case GDK_KEY_PRESS: - switch (get_group0_keyval (&event->key)) { - case GDK_KEY_j: - case GDK_KEY_J: - if (MOD__SHIFT_ONLY(event)) { - sp_spray_switch_mode(this, SPRAY_MODE_COPY, MOD__SHIFT(event)); - ret = TRUE; - } - break; - case GDK_KEY_k: - case GDK_KEY_K: - if (MOD__SHIFT_ONLY(event)) { - sp_spray_switch_mode(this, SPRAY_MODE_CLONE, MOD__SHIFT(event)); - ret = TRUE; - } - break; - case GDK_KEY_l: - case GDK_KEY_L: - if (MOD__SHIFT_ONLY(event)) { - sp_spray_switch_mode(this, SPRAY_MODE_SINGLE_PATH, MOD__SHIFT(event)); - ret = TRUE; - } - break; - case GDK_KEY_Up: - case GDK_KEY_KP_Up: - if (!MOD__CTRL_ONLY(event)) { - this->population += 0.01; - if (this->population > 1.0) { - this->population = 1.0; - } - desktop->setToolboxAdjustmentValue("spray-population", this->population * 100); - ret = TRUE; - } - break; - case GDK_KEY_Down: - case GDK_KEY_KP_Down: - if (!MOD__CTRL_ONLY(event)) { - this->population -= 0.01; - if (this->population < 0.0) { - this->population = 0.0; - } - desktop->setToolboxAdjustmentValue("spray-population", this->population * 100); - ret = TRUE; - } - break; - case GDK_KEY_Right: - case GDK_KEY_KP_Right: - if (!MOD__CTRL_ONLY(event)) { - this->width += 0.01; - if (this->width > 1.0) { - this->width = 1.0; - } - // the same spinbutton is for alt+x - desktop->setToolboxAdjustmentValue("altx-spray", this->width * 100); - sp_spray_update_area(this); - ret = TRUE; - } - break; - case GDK_KEY_Left: - case GDK_KEY_KP_Left: - if (!MOD__CTRL_ONLY(event)) { - this->width -= 0.01; - if (this->width < 0.01) { - this->width = 0.01; - } - desktop->setToolboxAdjustmentValue("altx-spray", this->width * 100); - sp_spray_update_area(this); - ret = TRUE; - } - break; - case GDK_KEY_Home: - case GDK_KEY_KP_Home: - this->width = 0.01; - desktop->setToolboxAdjustmentValue("altx-spray", this->width * 100); - sp_spray_update_area(this); - ret = TRUE; - break; - case GDK_KEY_End: - case GDK_KEY_KP_End: - this->width = 1.0; - desktop->setToolboxAdjustmentValue("altx-spray", this->width * 100); - sp_spray_update_area(this); - ret = TRUE; - break; - case GDK_KEY_x: - case GDK_KEY_X: - if (MOD__ALT_ONLY(event)) { - desktop->setToolboxFocusTo("altx-spray"); - ret = TRUE; - } - break; - case GDK_KEY_Shift_L: - case GDK_KEY_Shift_R: - this->update_cursor(true); - break; - case GDK_KEY_Control_L: - case GDK_KEY_Control_R: - break; - case GDK_KEY_Delete: - case GDK_KEY_KP_Delete: - case GDK_KEY_BackSpace: - ret = this->deleteSelectedDrag(MOD__CTRL_ONLY(event)); - break; - - default: - break; - } - break; - - case GDK_KEY_RELEASE: { - Inkscape::Preferences *prefs = Inkscape::Preferences::get(); - switch (get_group0_keyval(&event->key)) { - case GDK_KEY_Shift_L: - case GDK_KEY_Shift_R: - this->update_cursor(false); - break; - case GDK_KEY_Control_L: - case GDK_KEY_Control_R: - sp_spray_switch_mode (this, prefs->getInt("/tools/spray/mode"), MOD__SHIFT(event)); - this->message_context->clear(); - break; - default: - sp_spray_switch_mode (this, prefs->getInt("/tools/spray/mode"), MOD__SHIFT(event)); - break; - } - } - - default: - break; - } - - if (!ret) { -// if ((SP_EVENT_CONTEXT_CLASS(sp_spray_context_parent_class))->root_handler) { -// ret = (SP_EVENT_CONTEXT_CLASS(sp_spray_context_parent_class))->root_handler(event_context, event); -// } - ret = ToolBase::root_handler(event); - } - - return ret; -} - -} -} -} - -/* - Local Variables: - mode:c++ - c-file-style:"stroustrup" - c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) - indent-tabs-mode:nil - fill-column:99 - End: -*/ -// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : - diff --git a/src/spray-context.h b/src/spray-context.h deleted file mode 100644 index ce03a8bc0..000000000 --- a/src/spray-context.h +++ /dev/null @@ -1,121 +0,0 @@ -#ifndef __SP_SPRAY_CONTEXT_H__ -#define __SP_SPRAY_CONTEXT_H__ - -/* - * Spray Tool - * - * Authors: - * Pierre-Antoine MARC - * Pierre CACLIN - * Aurel-Aimé MARMION - * Julien LERAY - * Benoît LAVORATA - * Vincent MONTAGNE - * Pierre BARBRY-BLOT - * - * Copyright (C) 2009 authors - * - * Released under GNU GPL, read the file 'COPYING' for more information - */ - -#include <2geom/point.h> -#include "event-context.h" - -#define SP_SPRAY_CONTEXT(obj) (dynamic_cast((Inkscape::UI::Tools::ToolBase*)obj)) -#define SP_IS_SPRAY_CONTEXT(obj) (dynamic_cast((const Inkscape::UI::Tools::ToolBase*)obj) != NULL) - -namespace Inkscape { - namespace UI { - namespace Dialog { - class Dialog; - } - } -} - - -#define SAMPLING_SIZE 8 /* fixme: ?? */ - -#define TC_MIN_PRESSURE 0.0 -#define TC_MAX_PRESSURE 1.0 -#define TC_DEFAULT_PRESSURE 0.35 - -namespace Inkscape { -namespace UI { -namespace Tools { - -enum { - SPRAY_MODE_COPY, - SPRAY_MODE_CLONE, - SPRAY_MODE_SINGLE_PATH, - SPRAY_OPTION, -}; - -class SprayTool : public ToolBase { -public: - SprayTool(); - virtual ~SprayTool(); - - //ToolBase event_context; - //Inkscape::UI::Dialog::Dialog *dialog_option;//Attribut de type SprayOptionClass, localisé dans scr/ui/dialog - /* extended input data */ - gdouble pressure; - - /* attributes */ - guint dragging : 1; /* mouse state: mouse is dragging */ - guint usepressure : 1; - guint usetilt : 1; - bool usetext ; - - double width; - double ratio; - double tilt; - double rotation_variation; - double force; - double population; - double scale_variation; - double scale; - double mean; - double standard_deviation; - - gint distrib; - - gint mode; - - bool is_drawing; - - bool is_dilating; - bool has_dilated; - Geom::Point last_push; - SPCanvasItem *dilate_area; - - sigc::connection style_set_connection; - - static const std::string prefsPath; - - virtual void setup(); - virtual void set(const Inkscape::Preferences::Entry& val); - virtual bool root_handler(GdkEvent* event); - - virtual const std::string& getPrefsPath(); - - - void update_cursor(bool /*with_shift*/); -}; - -} -} -} - -#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:fileencoding=utf-8:textwidth=99 : - diff --git a/src/star-context.cpp b/src/star-context.cpp deleted file mode 100644 index 69eb40370..000000000 --- a/src/star-context.cpp +++ /dev/null @@ -1,494 +0,0 @@ -/* - * Star drawing context - * - * Authors: - * Mitsuru Oka - * Lauris Kaplinski - * bulia byak - * Jon A. Cruz - * Abhishek Sharma - * - * Copyright (C) 1999-2002 Lauris Kaplinski - * Copyright (C) 2001-2002 Mitsuru Oka - * - * Released under GNU GPL, read the file 'COPYING' for more information - */ - -#ifdef HAVE_CONFIG_H -# include "config.h" -#endif - -#include -#include - -#include - -#include "macros.h" -#include "display/sp-canvas.h" -#include "sp-star.h" -#include "document.h" -#include "document-undo.h" -#include "sp-namedview.h" -#include "selection.h" -#include "desktop-handles.h" -#include "snap.h" -#include "desktop.h" -#include "desktop-style.h" -#include "message-context.h" -#include "pixmaps/cursor-star.xpm" -#include -#include "preferences.h" -#include "xml/repr.h" -#include "xml/node-event-vector.h" -#include "context-fns.h" -#include "shape-editor.h" -#include "verbs.h" -#include "display/sp-canvas-item.h" - -#include "star-context.h" - -using Inkscape::DocumentUndo; - -#include "tool-factory.h" - -namespace Inkscape { -namespace UI { -namespace Tools { - -namespace { - ToolBase* createStarContext() { - return new StarTool(); - } - - bool starContextRegistered = ToolFactory::instance().registerObject("/tools/shapes/star", createStarContext); -} - -const std::string& StarTool::getPrefsPath() { - return StarTool::prefsPath; -} - -const std::string StarTool::prefsPath = "/tools/shapes/star"; - -StarTool::StarTool() : ToolBase() { - this->randomized = 0; - this->rounded = 0; - - this->cursor_shape = cursor_star_xpm; - this->hot_x = 4; - this->hot_y = 4; - this->xp = 0; - this->yp = 0; - this->tolerance = 0; - this->within_tolerance = false; - this->item_to_select = NULL; - //this->tool_url = "/tools/shapes/star"; - - this->star = NULL; - - this->magnitude = 5; - this->proportion = 0.5; - this->isflatsided = false; -} - -void StarTool::finish() { - sp_canvas_item_ungrab(SP_CANVAS_ITEM(desktop->acetate), GDK_CURRENT_TIME); - - this->finishItem(); - this->sel_changed_connection.disconnect(); - - ToolBase::finish(); -} - -StarTool::~StarTool() { - this->enableGrDrag(false); - - this->sel_changed_connection.disconnect(); - - delete this->shape_editor; - this->shape_editor = NULL; - - /* fixme: This is necessary because we do not grab */ - if (this->star) { - this->finishItem(); - } -} - -/** - * Callback that processes the "changed" signal on the selection; - * destroys old and creates new knotholder. - * - * @param selection Should not be NULL. - */ -void StarTool::selection_changed(Inkscape::Selection* selection) { - g_assert (selection != NULL); - - this->shape_editor->unset_item(SH_KNOTHOLDER); - this->shape_editor->set_item(selection->singleItem(), SH_KNOTHOLDER); -} - -void StarTool::setup() { - ToolBase::setup(); - - sp_event_context_read(this, "magnitude"); - sp_event_context_read(this, "proportion"); - sp_event_context_read(this, "isflatsided"); - sp_event_context_read(this, "rounded"); - sp_event_context_read(this, "randomized"); - - this->shape_editor = new ShapeEditor(this->desktop); - - SPItem *item = sp_desktop_selection(this->desktop)->singleItem(); - if (item) { - this->shape_editor->set_item(item, SH_KNOTHOLDER); - } - - Inkscape::Selection *selection = sp_desktop_selection(this->desktop); - - this->sel_changed_connection.disconnect(); - - this->sel_changed_connection = selection->connectChanged(sigc::mem_fun(this, &StarTool::selection_changed)); - - Inkscape::Preferences *prefs = Inkscape::Preferences::get(); - if (prefs->getBool("/tools/shapes/selcue")) { - this->enableSelectionCue(); - } - - if (prefs->getBool("/tools/shapes/gradientdrag")) { - this->enableGrDrag(); - } -} - -void StarTool::set(const Inkscape::Preferences::Entry& val) { - Glib::ustring path = val.getEntryName(); - - if (path == "magnitude") { - this->magnitude = CLAMP(val.getInt(5), 3, 1024); - } else if (path == "proportion") { - this->proportion = CLAMP(val.getDouble(0.5), 0.01, 2.0); - } else if (path == "isflatsided") { - this->isflatsided = val.getBool(); - } else if (path == "rounded") { - this->rounded = val.getDouble(); - } else if (path == "randomized") { - this->randomized = val.getDouble(); - } -} - -bool StarTool::root_handler(GdkEvent* event) { - static bool dragging; - - SPDesktop *desktop = this->desktop; - Inkscape::Selection *selection = sp_desktop_selection (desktop); - Inkscape::Preferences *prefs = Inkscape::Preferences::get(); - - this->tolerance = prefs->getIntLimited("/options/dragtolerance/value", 0, 0, 100); - - gint ret = FALSE; - - switch (event->type) { - case GDK_BUTTON_PRESS: - if (event->button.button == 1 && !this->space_panning) { - dragging = true; - - this->center = Inkscape::setup_for_drag_start(desktop, this, event); - - /* Snap center */ - SnapManager &m = desktop->namedview->snap_manager; - m.setup(desktop, true); - m.freeSnapReturnByRef(this->center, Inkscape::SNAPSOURCE_NODE_HANDLE); - m.unSetup(); - - sp_canvas_item_grab(SP_CANVAS_ITEM(desktop->acetate), - GDK_KEY_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | - GDK_POINTER_MOTION_MASK | - GDK_POINTER_MOTION_HINT_MASK | - GDK_BUTTON_PRESS_MASK, - NULL, event->button.time); - ret = TRUE; - } - break; - - case GDK_MOTION_NOTIFY: - if (dragging && (event->motion.state & GDK_BUTTON1_MASK) && !this->space_panning) { - if ( this->within_tolerance - && ( abs( (gint) event->motion.x - this->xp ) < this->tolerance ) - && ( abs( (gint) event->motion.y - this->yp ) < this->tolerance ) ) { - break; // do not drag if we're within tolerance from origin - } - // Once the user has moved farther than tolerance from the original location - // (indicating they intend to draw, not click), then always process the - // motion notify coordinates as given (no snapping back to origin) - this->within_tolerance = false; - - Geom::Point const motion_w(event->motion.x, event->motion.y); - Geom::Point motion_dt(desktop->w2d(motion_w)); - - this->drag(motion_dt, event->motion.state); - - gobble_motion_events(GDK_BUTTON1_MASK); - - ret = TRUE; - } else if (!sp_event_context_knot_mouseover(this)) { - SnapManager &m = desktop->namedview->snap_manager; - m.setup(desktop); - - Geom::Point const motion_w(event->motion.x, event->motion.y); - Geom::Point motion_dt(desktop->w2d(motion_w)); - - m.preSnap(Inkscape::SnapCandidatePoint(motion_dt, Inkscape::SNAPSOURCE_NODE_HANDLE)); - m.unSetup(); - } - break; - case GDK_BUTTON_RELEASE: - this->xp = this->yp = 0; - - if (event->button.button == 1 && !this->space_panning) { - dragging = false; - - sp_event_context_discard_delayed_snap_event(this); - - if (!this->within_tolerance) { - // we've been dragging, finish the star - this->finishItem(); - } else if (this->item_to_select) { - // no dragging, select clicked item if any - if (event->button.state & GDK_SHIFT_MASK) { - selection->toggle(this->item_to_select); - } else { - selection->set(this->item_to_select); - } - } else { - // click in an empty space - selection->clear(); - } - - this->item_to_select = NULL; - ret = TRUE; - sp_canvas_item_ungrab(SP_CANVAS_ITEM (desktop->acetate), event->button.time); - } - break; - - case GDK_KEY_PRESS: - switch (get_group0_keyval(&event->key)) { - case GDK_KEY_Alt_R: - case GDK_KEY_Control_L: - case GDK_KEY_Control_R: - case GDK_KEY_Shift_L: - case GDK_KEY_Shift_R: - case GDK_KEY_Meta_L: // Meta is when you press Shift+Alt (at least on my machine) - case GDK_KEY_Meta_R: - sp_event_show_modifier_tip(this->defaultMessageContext(), event, - _("Ctrl: snap angle; keep rays radial"), - NULL, - NULL); - break; - - case GDK_KEY_Up: - case GDK_KEY_Down: - case GDK_KEY_KP_Up: - case GDK_KEY_KP_Down: - // prevent the zoom field from activation - if (!MOD__CTRL_ONLY(event)) - ret = TRUE; - break; - - case GDK_KEY_x: - case GDK_KEY_X: - if (MOD__ALT_ONLY(event)) { - desktop->setToolboxFocusTo ("altx-star"); - ret = TRUE; - } - break; - - case GDK_KEY_Escape: - if (dragging) { - dragging = false; - sp_event_context_discard_delayed_snap_event(this); - // if drawing, cancel, otherwise pass it up for deselecting - this->cancel(); - ret = TRUE; - } - break; - - case GDK_KEY_space: - if (dragging) { - sp_canvas_item_ungrab(SP_CANVAS_ITEM(desktop->acetate), event->button.time); - - dragging = false; - - sp_event_context_discard_delayed_snap_event(this); - - if (!this->within_tolerance) { - // we've been dragging, finish the star - this->finishItem(); - } - // do not return true, so that space would work switching to selector - } - break; - - case GDK_KEY_Delete: - case GDK_KEY_KP_Delete: - case GDK_KEY_BackSpace: - ret = this->deleteSelectedDrag(MOD__CTRL_ONLY(event)); - break; - - default: - break; - } - break; - - case GDK_KEY_RELEASE: - switch (get_group0_keyval (&event->key)) { - case GDK_KEY_Alt_L: - case GDK_KEY_Alt_R: - case GDK_KEY_Control_L: - case GDK_KEY_Control_R: - case GDK_KEY_Shift_L: - case GDK_KEY_Shift_R: - case GDK_KEY_Meta_L: // Meta is when you press Shift+Alt - case GDK_KEY_Meta_R: - this->defaultMessageContext()->clear(); - break; - - default: - break; - } - break; - - default: - break; - } - - if (!ret) { - ret = ToolBase::root_handler(event); - } - - return ret; -} - -void StarTool::drag(Geom::Point p, guint state) -{ - SPDesktop *desktop = this->desktop; - - Inkscape::Preferences *prefs = Inkscape::Preferences::get(); - int const snaps = prefs->getInt("/options/rotationsnapsperpi/value", 12); - - if (!this->star) { - if (Inkscape::have_viable_layer(desktop, this->message_context) == false) { - return; - } - - // Create object - Inkscape::XML::Document *xml_doc = this->desktop->doc()->getReprDoc(); - Inkscape::XML::Node *repr = xml_doc->createElement("svg:path"); - repr->setAttribute("sodipodi:type", "star"); - - // Set style - sp_desktop_apply_style_tool(desktop, repr, "/tools/shapes/star", false); - - this->star = SP_STAR(desktop->currentLayer()->appendChildRepr(repr)); - - Inkscape::GC::release(repr); - this->star->transform = SP_ITEM(desktop->currentLayer())->i2doc_affine().inverse(); - this->star->updateRepr(); - - desktop->canvas->forceFullRedrawAfterInterruptions(5); - } - - /* Snap corner point with no constraints */ - SnapManager &m = desktop->namedview->snap_manager; - - m.setup(desktop, true, this->star); - Geom::Point pt2g = p; - m.freeSnapReturnByRef(pt2g, Inkscape::SNAPSOURCE_NODE_HANDLE); - m.unSetup(); - - Geom::Point const p0 = desktop->dt2doc(this->center); - Geom::Point const p1 = desktop->dt2doc(pt2g); - - double const sides = (gdouble) this->magnitude; - Geom::Point const d = p1 - p0; - Geom::Coord const r1 = Geom::L2(d); - double arg1 = atan2(d); - - if (state & GDK_CONTROL_MASK) { - /* Snap angle */ - arg1 = sp_round(arg1, M_PI / snaps); - } - - sp_star_position_set(this->star, this->magnitude, p0, r1, r1 * this->proportion, - arg1, arg1 + M_PI / sides, this->isflatsided, this->rounded, this->randomized); - - /* status text */ - Inkscape::Util::Quantity q = Inkscape::Util::Quantity(r1, "px"); - GString *rads = g_string_new(q.string(desktop->namedview->doc_units).c_str()); - this->message_context->setF(Inkscape::IMMEDIATE_MESSAGE, - ( this->isflatsided? - _("Polygon: radius %s, angle %5g°; with Ctrl to snap angle") - : _("Star: radius %s, angle %5g°; with Ctrl to snap angle") ), - rads->str, sp_round((arg1) * 180 / M_PI, 0.0001)); - - g_string_free(rads, FALSE); -} - -void StarTool::finishItem() { - this->message_context->clear(); - - if (this->star != NULL) { - if (this->star->r[1] == 0) { - // Don't allow the creating of zero sized arc, for example - // when the start and and point snap to the snap grid point - this->cancel(); - return; - } - - // Set transform center, so that odd stars rotate correctly - // LP #462157 - this->star->setCenter(this->center); - this->star->set_shape(); - this->star->updateRepr(SP_OBJECT_WRITE_EXT); - this->star->doWriteTransform(this->star->getRepr(), this->star->transform, NULL, true); - - desktop->canvas->endForcedFullRedraws(); - - sp_desktop_selection(desktop)->set(this->star); - DocumentUndo::done(sp_desktop_document(desktop), SP_VERB_CONTEXT_STAR, - _("Create star")); - - this->star = NULL; - } -} - -void StarTool::cancel() { - sp_desktop_selection(desktop)->clear(); - sp_canvas_item_ungrab(SP_CANVAS_ITEM(desktop->acetate), 0); - - if (this->star != NULL) { - this->star->deleteObject(); - this->star = NULL; - } - - this->within_tolerance = false; - this->xp = 0; - this->yp = 0; - this->item_to_select = NULL; - - desktop->canvas->endForcedFullRedraws(); - - DocumentUndo::cancel(sp_desktop_document(desktop)); -} - -} -} -} - -/* - Local Variables: - mode:c++ - c-file-style:"stroustrup" - c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) - indent-tabs-mode:nil - fill-column:99 - End: -*/ -// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/star-context.h b/src/star-context.h deleted file mode 100644 index 3d792206a..000000000 --- a/src/star-context.h +++ /dev/null @@ -1,74 +0,0 @@ -#ifndef __SP_STAR_CONTEXT_H__ -#define __SP_STAR_CONTEXT_H__ - -/* - * Star drawing context - * - * Authors: - * Mitsuru Oka - * Lauris Kaplinski - * - * Copyright (C) 1999-2002 Lauris Kaplinski - * Copyright (C) 2001-2002 Mitsuru Oka - * - * Released under GNU GPL, read the file 'COPYING' for more information - */ - -#include -#include -#include <2geom/point.h> -#include "event-context.h" - -#include "sp-star.h" - -namespace Inkscape { -namespace UI { -namespace Tools { - -class StarTool : public ToolBase { -public: - StarTool(); - virtual ~StarTool(); - - static const std::string prefsPath; - - virtual void setup(); - virtual void finish(); - virtual void set(const Inkscape::Preferences::Entry& val); - virtual bool root_handler(GdkEvent* event); - - virtual const std::string& getPrefsPath(); - -private: - SPStar* star; - - Geom::Point center; - - /* Number of corners */ - gint magnitude; - - /* Outer/inner radius ratio */ - gdouble proportion; - - /* flat sides or not? */ - bool isflatsided; - - /* rounded corners ratio */ - gdouble rounded; - - // randomization - gdouble randomized; - - sigc::connection sel_changed_connection; - - void drag(Geom::Point p, guint state); - void finishItem(); - void cancel(); - void selection_changed(Inkscape::Selection* selection); -}; - -} -} -} - -#endif diff --git a/src/text-context.cpp b/src/text-context.cpp deleted file mode 100644 index cc27e5589..000000000 --- a/src/text-context.cpp +++ /dev/null @@ -1,1765 +0,0 @@ -/* - * TextTool - * - * Authors: - * Lauris Kaplinski - * bulia byak - * Jon A. Cruz - * Abhishek Sharma - * - * Copyright (C) 1999-2005 authors - * Copyright (C) 2001 Ximian, Inc. - * - * Released under GNU GPL, read the file 'COPYING' for more information - */ - -#ifdef HAVE_CONFIG_H -# include -#endif - -#include -#include -#include -#include -#include -#include -#include - -#include "context-fns.h" -#include "desktop-handles.h" -#include "desktop-style.h" -#include "desktop.h" -#include "document.h" -#include "document-undo.h" -#include "macros.h" -#include "message-context.h" -#include "message-stack.h" -#include "pixmaps/cursor-text-insert.xpm" -#include "pixmaps/cursor-text.xpm" -#include "preferences.h" -#include "rubberband.h" -#include "selection-chemistry.h" -#include "selection.h" -#include "shape-editor.h" -#include "sp-flowtext.h" -#include "sp-namedview.h" -#include "sp-text.h" -#include "style.h" -#include "text-context.h" -#include "text-editing.h" -#include "ui/control-manager.h" -#include "verbs.h" -#include "xml/node-event-vector.h" -#include "xml/repr.h" -#include -#include "tool-factory.h" - -using Inkscape::ControlManager; -using Inkscape::DocumentUndo; - -namespace Inkscape { -namespace UI { -namespace Tools { - -static void sp_text_context_selection_changed(Inkscape::Selection *selection, TextTool *tc); -static void sp_text_context_selection_modified(Inkscape::Selection *selection, guint flags, TextTool *tc); -static bool sp_text_context_style_set(SPCSSAttr const *css, TextTool *tc); -static int sp_text_context_style_query(SPStyle *style, int property, TextTool *tc); - -static void sp_text_context_validate_cursor_iterators(TextTool *tc); -static void sp_text_context_update_cursor(TextTool *tc, bool scroll_to_see = true); -static void sp_text_context_update_text_selection(TextTool *tc); -static gint sp_text_context_timeout(TextTool *tc); -static void sp_text_context_forget_text(TextTool *tc); - -static gint sptc_focus_in(GtkWidget *widget, GdkEventFocus *event, TextTool *tc); -static gint sptc_focus_out(GtkWidget *widget, GdkEventFocus *event, TextTool *tc); -static void sptc_commit(GtkIMContext *imc, gchar *string, TextTool *tc); - -namespace { - ToolBase* createTextContext() { - return new TextTool(); - } - - bool textContextRegistered = ToolFactory::instance().registerObject("/tools/text", createTextContext); -} - -const std::string& TextTool::getPrefsPath() { - return TextTool::prefsPath; -} - -const std::string TextTool::prefsPath = "/tools/text"; - - -TextTool::TextTool() : ToolBase() { - this->preedit_string = 0; - this->unipos = 0; - - this->cursor_shape = cursor_text_xpm; - this->hot_x = 7; - this->hot_y = 7; - - this->xp = 0; - this->yp = 0; - this->tolerance = 0; - this->within_tolerance = false; - - this->imc = NULL; - - this->text = NULL; - this->pdoc = Geom::Point(0, 0); - - this->unimode = false; - - this->cursor = NULL; - this->indicator = NULL; - this->frame = NULL; - this->grabbed = NULL; - this->timeout = 0; - this->show = FALSE; - this->phase = 0; - this->nascent_object = 0; - this->over_text = 0; - this->dragging = 0; - this->creating = 0; -} - -TextTool::~TextTool() { - delete this->shape_editor; - this->shape_editor = NULL; - - if (this->grabbed) { - sp_canvas_item_ungrab(this->grabbed, GDK_CURRENT_TIME); - this->grabbed = NULL; - } - - Inkscape::Rubberband::get(this->desktop)->stop(); -} - -void TextTool::setup() { - GtkSettings* settings = gtk_settings_get_default(); - gint timeout = 0; - g_object_get( settings, "gtk-cursor-blink-time", &timeout, NULL ); - - if (timeout < 0) { - timeout = 200; - } else { - timeout /= 2; - } - - this->cursor = ControlManager::getManager().createControlLine(sp_desktop_controls(desktop), Geom::Point(100, 0), Geom::Point(100, 100)); - this->cursor->setRgba32(0x000000ff); - sp_canvas_item_hide(this->cursor); - - this->indicator = sp_canvas_item_new(sp_desktop_controls(desktop), SP_TYPE_CTRLRECT, NULL); - SP_CTRLRECT(this->indicator)->setRectangle(Geom::Rect(Geom::Point(0, 0), Geom::Point(100, 100))); - SP_CTRLRECT(this->indicator)->setColor(0x0000ff7f, false, 0); - sp_canvas_item_hide(this->indicator); - - this->frame = sp_canvas_item_new(sp_desktop_controls(desktop), SP_TYPE_CTRLRECT, NULL); - SP_CTRLRECT(this->frame)->setRectangle(Geom::Rect(Geom::Point(0, 0), Geom::Point(100, 100))); - SP_CTRLRECT(this->frame)->setColor(0x0000ff7f, false, 0); - sp_canvas_item_hide(this->frame); - - this->timeout = g_timeout_add(timeout, (GSourceFunc) sp_text_context_timeout, this); - - this->imc = gtk_im_multicontext_new(); - if (this->imc) { - GtkWidget *canvas = GTK_WIDGET(sp_desktop_canvas(desktop)); - - /* im preedit handling is very broken in inkscape for - * multi-byte characters. See bug 1086769. - * We need to let the IM handle the preediting, and - * just take in the characters when they're finished being - * entered. - */ - gtk_im_context_set_use_preedit(this->imc, FALSE); - gtk_im_context_set_client_window(this->imc, - gtk_widget_get_window (canvas)); - - g_signal_connect(G_OBJECT(canvas), "focus_in_event", G_CALLBACK(sptc_focus_in), this); - g_signal_connect(G_OBJECT(canvas), "focus_out_event", G_CALLBACK(sptc_focus_out), this); - g_signal_connect(G_OBJECT(this->imc), "commit", G_CALLBACK(sptc_commit), this); - - if (gtk_widget_has_focus(canvas)) { - sptc_focus_in(canvas, NULL, this); - } - } - - ToolBase::setup(); - - this->shape_editor = new ShapeEditor(this->desktop); - - SPItem *item = sp_desktop_selection(this->desktop)->singleItem(); - if (item && SP_IS_FLOWTEXT(item) && SP_FLOWTEXT(item)->has_internal_frame()) { - this->shape_editor->set_item(item, SH_KNOTHOLDER); - } - - this->sel_changed_connection = sp_desktop_selection(desktop)->connectChanged( - sigc::bind(sigc::ptr_fun(&sp_text_context_selection_changed), this) - ); - this->sel_modified_connection = sp_desktop_selection(desktop)->connectModified( - sigc::bind(sigc::ptr_fun(&sp_text_context_selection_modified), this) - ); - this->style_set_connection = desktop->connectSetStyle( - sigc::bind(sigc::ptr_fun(&sp_text_context_style_set), this) - ); - this->style_query_connection = desktop->connectQueryStyle( - sigc::bind(sigc::ptr_fun(&sp_text_context_style_query), this) - ); - - sp_text_context_selection_changed(sp_desktop_selection(desktop), this); - - Inkscape::Preferences *prefs = Inkscape::Preferences::get(); - if (prefs->getBool("/tools/text/selcue")) { - this->enableSelectionCue(); - } - if (prefs->getBool("/tools/text/gradientdrag")) { - this->enableGrDrag(); - } -} - -void TextTool::finish() { - if (this->desktop) { - sp_signal_disconnect_by_data(sp_desktop_canvas(this->desktop), this); - } - - this->enableGrDrag(false); - - this->style_set_connection.disconnect(); - this->style_query_connection.disconnect(); - this->sel_changed_connection.disconnect(); - this->sel_modified_connection.disconnect(); - - sp_text_context_forget_text(SP_TEXT_CONTEXT(this)); - - if (this->imc) { - g_object_unref(G_OBJECT(this->imc)); - this->imc = NULL; - } - - if (this->timeout) { - g_source_remove(this->timeout); - this->timeout = 0; - } - - if (this->cursor) { - sp_canvas_item_destroy(this->cursor); - this->cursor = NULL; - } - - if (this->indicator) { - sp_canvas_item_destroy(this->indicator); - this->indicator = NULL; - } - - if (this->frame) { - sp_canvas_item_destroy(this->frame); - this->frame = NULL; - } - - for (std::vector::iterator it = this->text_selection_quads.begin() ; - it != this->text_selection_quads.end() ; ++it) { - sp_canvas_item_hide(*it); - sp_canvas_item_destroy(*it); - } - - this->text_selection_quads.clear(); -} - -bool TextTool::item_handler(SPItem* item, GdkEvent* event) { - SPItem *item_ungrouped; - - gint ret = FALSE; - - sp_text_context_validate_cursor_iterators(this); - Inkscape::Text::Layout::iterator old_start = this->text_sel_start; - - switch (event->type) { - case GDK_BUTTON_PRESS: - if (event->button.button == 1 && !this->space_panning) { - // find out clicked item, disregarding groups - item_ungrouped = desktop->getItemAtPoint(Geom::Point(event->button.x, event->button.y), TRUE); - if (SP_IS_TEXT(item_ungrouped) || SP_IS_FLOWTEXT(item_ungrouped)) { - sp_desktop_selection(desktop)->set(item_ungrouped); - if (this->text) { - // find out click point in document coordinates - Geom::Point p = desktop->w2d(Geom::Point(event->button.x, event->button.y)); - // set the cursor closest to that point - if (event->button.state & GDK_SHIFT_MASK) { - this->text_sel_start = old_start; - this->text_sel_end = sp_te_get_position_by_coords(this->text, p); - } else { - this->text_sel_start = this->text_sel_end = sp_te_get_position_by_coords(this->text, p); - } - // update display - sp_text_context_update_cursor(this); - sp_text_context_update_text_selection(this); - this->dragging = 1; - } - ret = TRUE; - } - } - break; - case GDK_2BUTTON_PRESS: - if (event->button.button == 1 && this->text) { - Inkscape::Text::Layout const *layout = te_get_layout(this->text); - if (layout) { - if (!layout->isStartOfWord(this->text_sel_start)) - this->text_sel_start.prevStartOfWord(); - if (!layout->isEndOfWord(this->text_sel_end)) - this->text_sel_end.nextEndOfWord(); - sp_text_context_update_cursor(this); - sp_text_context_update_text_selection(this); - this->dragging = 2; - ret = TRUE; - } - } - break; - case GDK_3BUTTON_PRESS: - if (event->button.button == 1 && this->text) { - this->text_sel_start.thisStartOfLine(); - this->text_sel_end.thisEndOfLine(); - sp_text_context_update_cursor(this); - sp_text_context_update_text_selection(this); - this->dragging = 3; - ret = TRUE; - } - break; - case GDK_BUTTON_RELEASE: - if (event->button.button == 1 && this->dragging && !this->space_panning) { - this->dragging = 0; - sp_event_context_discard_delayed_snap_event(this); - ret = TRUE; - } - break; - case GDK_MOTION_NOTIFY: - if ((event->motion.state & GDK_BUTTON1_MASK) && this->dragging && !this->space_panning) { - Inkscape::Text::Layout const *layout = te_get_layout(this->text); - if (!layout) break; - // find out click point in document coordinates - Geom::Point p = desktop->w2d(Geom::Point(event->button.x, event->button.y)); - // set the cursor closest to that point - Inkscape::Text::Layout::iterator new_end = sp_te_get_position_by_coords(this->text, p); - if (this->dragging == 2) { - // double-click dragging: go by word - if (new_end < this->text_sel_start) { - if (!layout->isStartOfWord(new_end)) - new_end.prevStartOfWord(); - } else - if (!layout->isEndOfWord(new_end)) - new_end.nextEndOfWord(); - } else if (this->dragging == 3) { - // triple-click dragging: go by line - if (new_end < this->text_sel_start) - new_end.thisStartOfLine(); - else - new_end.thisEndOfLine(); - } - // update display - if (this->text_sel_end != new_end) { - this->text_sel_end = new_end; - sp_text_context_update_cursor(this); - sp_text_context_update_text_selection(this); - } - gobble_motion_events(GDK_BUTTON1_MASK); - ret = TRUE; - break; - } - // find out item under mouse, disregarding groups - item_ungrouped = desktop->getItemAtPoint(Geom::Point(event->button.x, event->button.y), TRUE); - if (SP_IS_TEXT(item_ungrouped) || SP_IS_FLOWTEXT(item_ungrouped)) { - - Inkscape::Text::Layout const *layout = te_get_layout(item_ungrouped); - if (layout->inputTruncated()) { - SP_CTRLRECT(this->indicator)->setColor(0xff0000ff, false, 0); - } else { - SP_CTRLRECT(this->indicator)->setColor(0x0000ff7f, false, 0); - } - Geom::OptRect ibbox = item_ungrouped->desktopVisualBounds(); - if (ibbox) { - SP_CTRLRECT(this->indicator)->setRectangle(*ibbox); - } - sp_canvas_item_show(this->indicator); - - this->cursor_shape = cursor_text_insert_xpm; - this->hot_x = 7; - this->hot_y = 10; - this->sp_event_context_update_cursor(); - sp_text_context_update_text_selection(this); - - if (SP_IS_TEXT (item_ungrouped)) { - desktop->event_context->defaultMessageContext()->set(Inkscape::NORMAL_MESSAGE, _("Click to edit the text, drag to select part of the text.")); - } else { - desktop->event_context->defaultMessageContext()->set(Inkscape::NORMAL_MESSAGE, _("Click to edit the flowed text, drag to select part of the text.")); - } - - this->over_text = true; - - ret = TRUE; - } - break; - default: - break; - } - - if (!ret) { - ret = ToolBase::item_handler(item, event); - } - - return ret; -} - -static void sp_text_context_setup_text(TextTool *tc) -{ - ToolBase *ec = SP_EVENT_CONTEXT(tc); - - /* Create */ - Inkscape::XML::Document *xml_doc = SP_EVENT_CONTEXT_DESKTOP(ec)->doc()->getReprDoc(); - Inkscape::XML::Node *rtext = xml_doc->createElement("svg:text"); - rtext->setAttribute("xml:space", "preserve"); // we preserve spaces in the text objects we create - - /* Set style */ - sp_desktop_apply_style_tool(SP_EVENT_CONTEXT_DESKTOP(ec), rtext, "/tools/text", true); - - sp_repr_set_svg_double(rtext, "x", tc->pdoc[Geom::X]); - sp_repr_set_svg_double(rtext, "y", tc->pdoc[Geom::Y]); - - /* Create */ - Inkscape::XML::Node *rtspan = xml_doc->createElement("svg:tspan"); - rtspan->setAttribute("sodipodi:role", "line"); // otherwise, why bother creating the tspan? - rtext->addChild(rtspan, NULL); - Inkscape::GC::release(rtspan); - - /* Create TEXT */ - Inkscape::XML::Node *rstring = xml_doc->createTextNode(""); - rtspan->addChild(rstring, NULL); - Inkscape::GC::release(rstring); - SPItem *text_item = SP_ITEM(ec->desktop->currentLayer()->appendChildRepr(rtext)); - /* fixme: Is selection::changed really immediate? */ - /* yes, it's immediate .. why does it matter? */ - sp_desktop_selection(ec->desktop)->set(text_item); - Inkscape::GC::release(rtext); - text_item->transform = SP_ITEM(ec->desktop->currentLayer())->i2doc_affine().inverse(); - - text_item->updateRepr(); - text_item->doWriteTransform(text_item->getRepr(), text_item->transform, NULL, true); - DocumentUndo::done(sp_desktop_document(ec->desktop), SP_VERB_CONTEXT_TEXT, - _("Create text")); -} - -/** - * Insert the character indicated by tc.uni to replace the current selection, - * and reset tc.uni/tc.unipos to empty string. - * - * \pre tc.uni/tc.unipos non-empty. - */ -static void insert_uni_char(TextTool *const tc) -{ - g_return_if_fail(tc->unipos - && tc->unipos < sizeof(tc->uni) - && tc->uni[tc->unipos] == '\0'); - unsigned int uv; - std::stringstream ss; - ss << std::hex << tc->uni; - ss >> uv; - tc->unipos = 0; - tc->uni[tc->unipos] = '\0'; - - if ( !g_unichar_isprint(static_cast(uv)) - && !(g_unichar_validate(static_cast(uv)) && (g_unichar_type(static_cast(uv)) == G_UNICODE_PRIVATE_USE) ) ) { - // This may be due to bad input, so it goes to statusbar. - tc->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, - _("Non-printable character")); - } else { - if (!tc->text) { // printable key; create text if none (i.e. if nascent_object) - sp_text_context_setup_text(tc); - tc->nascent_object = 0; // we don't need it anymore, having created a real - } - - gchar u[10]; - guint const len = g_unichar_to_utf8(uv, u); - u[len] = '\0'; - - tc->text_sel_start = tc->text_sel_end = sp_te_replace(tc->text, tc->text_sel_start, tc->text_sel_end, u); - sp_text_context_update_cursor(tc); - sp_text_context_update_text_selection(tc); - DocumentUndo::done(sp_desktop_document(tc->desktop), SP_VERB_DIALOG_TRANSFORM, - _("Insert Unicode character")); - } -} - -static void hex_to_printable_utf8_buf(char const *const ehex, char *utf8) -{ - unsigned int uv; - std::stringstream ss; - ss << std::hex << ehex; - ss >> uv; - if (!g_unichar_isprint((gunichar) uv)) { - uv = 0xfffd; - } - guint const len = g_unichar_to_utf8(uv, utf8); - utf8[len] = '\0'; -} - -static void show_curr_uni_char(TextTool *const tc) -{ - g_return_if_fail(tc->unipos < sizeof(tc->uni) - && tc->uni[tc->unipos] == '\0'); - if (tc->unipos) { - char utf8[10]; - hex_to_printable_utf8_buf(tc->uni, utf8); - - /* Status bar messages are in pango markup, so we need xml escaping. */ - if (utf8[1] == '\0') { - switch(utf8[0]) { - case '<': strcpy(utf8, "<"); break; - case '>': strcpy(utf8, ">"); break; - case '&': strcpy(utf8, "&"); break; - default: break; - } - } - tc->defaultMessageContext()->setF(Inkscape::NORMAL_MESSAGE, - _("Unicode (Enter to finish): %s: %s"), tc->uni, utf8); - } else { - tc->defaultMessageContext()->set(Inkscape::NORMAL_MESSAGE, _("Unicode (Enter to finish): ")); - } -} - -bool TextTool::root_handler(GdkEvent* event) { - sp_canvas_item_hide(this->indicator); - - sp_text_context_validate_cursor_iterators(this); - - Inkscape::Preferences *prefs = Inkscape::Preferences::get(); - this->tolerance = prefs->getIntLimited("/options/dragtolerance/value", 0, 0, 100); - - switch (event->type) { - case GDK_BUTTON_PRESS: - if (event->button.button == 1 && !this->space_panning) { - - if (Inkscape::have_viable_layer(desktop, desktop->messageStack()) == false) { - return TRUE; - } - - // save drag origin - this->xp = (gint) event->button.x; - this->yp = (gint) event->button.y; - this->within_tolerance = true; - - Geom::Point const button_pt(event->button.x, event->button.y); - Geom::Point button_dt(desktop->w2d(button_pt)); - - SnapManager &m = desktop->namedview->snap_manager; - m.setup(desktop); - m.freeSnapReturnByRef(button_dt, Inkscape::SNAPSOURCE_NODE_HANDLE); - m.unSetup(); - - this->p0 = button_dt; - Inkscape::Rubberband::get(desktop)->start(desktop, this->p0); - sp_canvas_item_grab(SP_CANVAS_ITEM(desktop->acetate), - GDK_KEY_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_BUTTON_PRESS_MASK | GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK, - NULL, event->button.time); - this->grabbed = SP_CANVAS_ITEM(desktop->acetate); - this->creating = 1; - - /* Processed */ - return TRUE; - } - break; - case GDK_MOTION_NOTIFY: - if (this->over_text) { - this->over_text = 0; - // update cursor and statusbar: we are not over a text object now - this->cursor_shape = cursor_text_xpm; - this->hot_x = 7; - this->hot_y = 7; - this->sp_event_context_update_cursor(); - desktop->event_context->defaultMessageContext()->clear(); - } - - if (this->creating && (event->motion.state & GDK_BUTTON1_MASK) && !this->space_panning) { - if ( this->within_tolerance - && ( abs( (gint) event->motion.x - this->xp ) < this->tolerance ) - && ( abs( (gint) event->motion.y - this->yp ) < this->tolerance ) ) { - break; // do not drag if we're within tolerance from origin - } - // Once the user has moved farther than tolerance from the original location - // (indicating they intend to draw, not click), then always process the - // motion notify coordinates as given (no snapping back to origin) - this->within_tolerance = false; - - Geom::Point const motion_pt(event->motion.x, event->motion.y); - Geom::Point p = desktop->w2d(motion_pt); - - SnapManager &m = desktop->namedview->snap_manager; - m.setup(desktop); - m.freeSnapReturnByRef(p, Inkscape::SNAPSOURCE_NODE_HANDLE); - m.unSetup(); - - Inkscape::Rubberband::get(desktop)->move(p); - gobble_motion_events(GDK_BUTTON1_MASK); - - // status text - Inkscape::Util::Quantity x_q = Inkscape::Util::Quantity(fabs((p - this->p0)[Geom::X]), "px"); - Inkscape::Util::Quantity y_q = Inkscape::Util::Quantity(fabs((p - this->p0)[Geom::Y]), "px"); - GString *xs = g_string_new(x_q.string(desktop->namedview->doc_units).c_str()); - GString *ys = g_string_new(y_q.string(desktop->namedview->doc_units).c_str()); - this->message_context->setF(Inkscape::IMMEDIATE_MESSAGE, _("Flowed text frame: %s × %s"), xs->str, ys->str); - - g_string_free(xs, FALSE); - g_string_free(ys, FALSE); - - } else if (!sp_event_context_knot_mouseover(this)) { - SnapManager &m = desktop->namedview->snap_manager; - m.setup(desktop); - - Geom::Point const motion_w(event->motion.x, event->motion.y); - Geom::Point motion_dt(desktop->w2d(motion_w)); - m.preSnap(Inkscape::SnapCandidatePoint(motion_dt, Inkscape::SNAPSOURCE_OTHER_HANDLE)); - m.unSetup(); - } - break; - case GDK_BUTTON_RELEASE: - if (event->button.button == 1 && !this->space_panning) { - sp_event_context_discard_delayed_snap_event(this); - - Geom::Point p1 = desktop->w2d(Geom::Point(event->button.x, event->button.y)); - - SnapManager &m = desktop->namedview->snap_manager; - m.setup(desktop); - m.freeSnapReturnByRef(p1, Inkscape::SNAPSOURCE_NODE_HANDLE); - m.unSetup(); - - if (this->grabbed) { - sp_canvas_item_ungrab(this->grabbed, GDK_CURRENT_TIME); - this->grabbed = NULL; - } - - Inkscape::Rubberband::get(desktop)->stop(); - - if (this->creating && this->within_tolerance) { - /* Button 1, set X & Y & new item */ - sp_desktop_selection(desktop)->clear(); - this->pdoc = desktop->dt2doc(p1); - this->show = TRUE; - this->phase = 1; - this->nascent_object = 1; // new object was just created - - /* Cursor */ - sp_canvas_item_show(this->cursor); - // Cursor height is defined by the new text object's font size; it needs to be set - // artificially here, for the text object does not exist yet: - double cursor_height = sp_desktop_get_font_size_tool(desktop); - this->cursor->setCoords(p1, p1 + Geom::Point(0, cursor_height)); - if (this->imc) { - GdkRectangle im_cursor; - Geom::Point const top_left = SP_EVENT_CONTEXT(this)->desktop->get_display_area().corner(3); - Geom::Point const cursor_size(0, cursor_height); - Geom::Point const im_position = SP_EVENT_CONTEXT(this)->desktop->d2w(p1 + cursor_size - top_left); - im_cursor.x = (int) floor(im_position[Geom::X]); - im_cursor.y = (int) floor(im_position[Geom::Y]); - im_cursor.width = 0; - im_cursor.height = (int) -floor(SP_EVENT_CONTEXT(this)->desktop->d2w(cursor_size)[Geom::Y]); - gtk_im_context_set_cursor_location(this->imc, &im_cursor); - } - this->message_context->set(Inkscape::NORMAL_MESSAGE, _("Type text; Enter to start new line.")); // FIXME:: this is a copy of a string from _update_cursor below, do not desync - - this->within_tolerance = false; - } else if (this->creating) { - double cursor_height = sp_desktop_get_font_size_tool(desktop); - if (fabs(p1[Geom::Y] - this->p0[Geom::Y]) > cursor_height) { - // otherwise even one line won't fit; most probably a slip of hand (even if bigger than tolerance) - SPItem *ft = create_flowtext_with_internal_frame (desktop, this->p0, p1); - /* Set style */ - sp_desktop_apply_style_tool(desktop, ft->getRepr(), "/tools/text", true); - sp_desktop_selection(desktop)->set(ft); - desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("Flowed text is created.")); - DocumentUndo::done(sp_desktop_document(desktop), SP_VERB_CONTEXT_TEXT, - _("Create flowed text")); - } else { - desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("The frame is too small for the current font size. Flowed text not created.")); - } - } - this->creating = false; - return TRUE; - } - break; - case GDK_KEY_PRESS: { - guint const group0_keyval = get_group0_keyval(&event->key); - - if (group0_keyval == GDK_KEY_KP_Add || - group0_keyval == GDK_KEY_KP_Subtract) { - if (!(event->key.state & GDK_MOD2_MASK)) // mod2 is NumLock; if on, type +/- keys - break; // otherwise pass on keypad +/- so they can zoom - } - - if ((this->text) || (this->nascent_object)) { - // there is an active text object in this context, or a new object was just created - - if (this->unimode || !this->imc - || (MOD__CTRL(event) && MOD__SHIFT(event)) // input methods tend to steal this for unimode, - // but we have our own so make sure they don't swallow it - || !gtk_im_context_filter_keypress(this->imc, (GdkEventKey*) event)) { - //IM did not consume the key, or we're in unimode - - if (!MOD__CTRL_ONLY(event) && this->unimode) { - /* TODO: ISO 14755 (section 3 Definitions) says that we should also - accept the first 6 characters of alphabets other than the latin - alphabet "if the Latin alphabet is not used". The below is also - reasonable (viz. hope that the user's keyboard includes latin - characters and force latin interpretation -- just as we do for our - keyboard shortcuts), but differs from the ISO 14755 - recommendation. */ - switch (group0_keyval) { - case GDK_KEY_space: - case GDK_KEY_KP_Space: { - if (this->unipos) { - insert_uni_char(this); - } - /* Stay in unimode. */ - show_curr_uni_char(this); - return TRUE; - } - - case GDK_KEY_BackSpace: { - g_return_val_if_fail(this->unipos < sizeof(this->uni), TRUE); - if (this->unipos) { - this->uni[--this->unipos] = '\0'; - } - show_curr_uni_char(this); - return TRUE; - } - - case GDK_KEY_Return: - case GDK_KEY_KP_Enter: { - if (this->unipos) { - insert_uni_char(this); - } - /* Exit unimode. */ - this->unimode = false; - this->defaultMessageContext()->clear(); - return TRUE; - } - - case GDK_KEY_Escape: { - // Cancel unimode. - this->unimode = false; - gtk_im_context_reset(this->imc); - this->defaultMessageContext()->clear(); - return TRUE; - } - - case GDK_KEY_Shift_L: - case GDK_KEY_Shift_R: - break; - - default: { - if (g_ascii_isxdigit(group0_keyval)) { - g_return_val_if_fail(this->unipos < sizeof(this->uni) - 1, TRUE); - this->uni[this->unipos++] = group0_keyval; - this->uni[this->unipos] = '\0'; - if (this->unipos == 8) { - /* This behaviour is partly to allow us to continue to - use a fixed-length buffer for tc->uni. Reason for - choosing the number 8 is that it's the length of - ``canonical form'' mentioned in the ISO 14755 spec. - An advantage over choosing 6 is that it allows using - backspace for typos & misremembering when entering a - 6-digit number. */ - insert_uni_char(this); - } - show_curr_uni_char(this); - return TRUE; - } else { - /* The intent is to ignore but consume characters that could be - typos for hex digits. Gtk seems to ignore & consume all - non-hex-digits, and we do similar here. Though note that some - shortcuts (like keypad +/- for zoom) get processed before - reaching this code. */ - return TRUE; - } - } - } - } - - Inkscape::Text::Layout::iterator old_start = this->text_sel_start; - Inkscape::Text::Layout::iterator old_end = this->text_sel_end; - bool cursor_moved = false; - int screenlines = 1; - if (this->text) { - double spacing = sp_te_get_average_linespacing(this->text); - Geom::Rect const d = desktop->get_display_area(); - screenlines = (int) floor(fabs(d.min()[Geom::Y] - d.max()[Geom::Y])/spacing) - 1; - if (screenlines <= 0) - screenlines = 1; - } - - /* Neither unimode nor IM consumed key; process text tool shortcuts */ - switch (group0_keyval) { - case GDK_KEY_x: - case GDK_KEY_X: - if (MOD__ALT_ONLY(event)) { - desktop->setToolboxFocusTo ("altx-text"); - return TRUE; - } - break; - case GDK_KEY_space: - if (MOD__CTRL_ONLY(event)) { - /* No-break space */ - if (!this->text) { // printable key; create text if none (i.e. if nascent_object) - sp_text_context_setup_text(this); - this->nascent_object = 0; // we don't need it anymore, having created a real - } - this->text_sel_start = this->text_sel_end = sp_te_replace(this->text, this->text_sel_start, this->text_sel_end, "\302\240"); - sp_text_context_update_cursor(this); - sp_text_context_update_text_selection(this); - desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("No-break space")); - DocumentUndo::done(sp_desktop_document(desktop), SP_VERB_CONTEXT_TEXT, - _("Insert no-break space")); - return TRUE; - } - break; - case GDK_KEY_U: - case GDK_KEY_u: - if (MOD__CTRL_ONLY(event) || (MOD__CTRL(event) && MOD__SHIFT(event))) { - if (this->unimode) { - this->unimode = false; - this->defaultMessageContext()->clear(); - } else { - this->unimode = true; - this->unipos = 0; - this->defaultMessageContext()->set(Inkscape::NORMAL_MESSAGE, _("Unicode (Enter to finish): ")); - } - if (this->imc) { - gtk_im_context_reset(this->imc); - } - return TRUE; - } - break; - case GDK_KEY_B: - case GDK_KEY_b: - if (MOD__CTRL_ONLY(event) && this->text) { - SPStyle const *style = sp_te_style_at_position(this->text, std::min(this->text_sel_start, this->text_sel_end)); - SPCSSAttr *css = sp_repr_css_attr_new(); - if (style->font_weight.computed == SP_CSS_FONT_WEIGHT_NORMAL - || style->font_weight.computed == SP_CSS_FONT_WEIGHT_100 - || style->font_weight.computed == SP_CSS_FONT_WEIGHT_200 - || style->font_weight.computed == SP_CSS_FONT_WEIGHT_300 - || style->font_weight.computed == SP_CSS_FONT_WEIGHT_400) - sp_repr_css_set_property(css, "font-weight", "bold"); - else - sp_repr_css_set_property(css, "font-weight", "normal"); - sp_te_apply_style(this->text, this->text_sel_start, this->text_sel_end, css); - sp_repr_css_attr_unref(css); - DocumentUndo::done(sp_desktop_document(desktop), SP_VERB_CONTEXT_TEXT, - _("Make bold")); - sp_text_context_update_cursor(this); - sp_text_context_update_text_selection(this); - return TRUE; - } - break; - case GDK_KEY_I: - case GDK_KEY_i: - if (MOD__CTRL_ONLY(event) && this->text) { - SPStyle const *style = sp_te_style_at_position(this->text, std::min(this->text_sel_start, this->text_sel_end)); - SPCSSAttr *css = sp_repr_css_attr_new(); - if (style->font_style.computed != SP_CSS_FONT_STYLE_NORMAL) - sp_repr_css_set_property(css, "font-style", "normal"); - else - sp_repr_css_set_property(css, "font-style", "italic"); - sp_te_apply_style(this->text, this->text_sel_start, this->text_sel_end, css); - sp_repr_css_attr_unref(css); - DocumentUndo::done(sp_desktop_document(desktop), SP_VERB_CONTEXT_TEXT, - _("Make italic")); - sp_text_context_update_cursor(this); - sp_text_context_update_text_selection(this); - return TRUE; - } - break; - - case GDK_KEY_A: - case GDK_KEY_a: - if (MOD__CTRL_ONLY(event) && this->text) { - Inkscape::Text::Layout const *layout = te_get_layout(this->text); - if (layout) { - this->text_sel_start = layout->begin(); - this->text_sel_end = layout->end(); - sp_text_context_update_cursor(this); - sp_text_context_update_text_selection(this); - return TRUE; - } - } - break; - - case GDK_KEY_Return: - case GDK_KEY_KP_Enter: - { - if (!this->text) { // printable key; create text if none (i.e. if nascent_object) - sp_text_context_setup_text(this); - this->nascent_object = 0; // we don't need it anymore, having created a real - } - - iterator_pair enter_pair; - bool success = sp_te_delete(this->text, this->text_sel_start, this->text_sel_end, enter_pair); - (void)success; // TODO cleanup - this->text_sel_start = this->text_sel_end = enter_pair.first; - - this->text_sel_start = this->text_sel_end = sp_te_insert_line(this->text, this->text_sel_start); - - sp_text_context_update_cursor(this); - sp_text_context_update_text_selection(this); - DocumentUndo::done(sp_desktop_document(desktop), SP_VERB_CONTEXT_TEXT, - _("New line")); - return TRUE; - } - case GDK_KEY_BackSpace: - if (this->text) { // if nascent_object, do nothing, but return TRUE; same for all other delete and move keys - - bool noSelection = false; - - if (MOD__CTRL(event)) { - this->text_sel_start = this->text_sel_end; - } - - if (this->text_sel_start == this->text_sel_end) { - if (MOD__CTRL(event)) { - this->text_sel_start.prevStartOfWord(); - } else { - this->text_sel_start.prevCursorPosition(); - } - noSelection = true; - } - - iterator_pair bspace_pair; - bool success = sp_te_delete(this->text, this->text_sel_start, this->text_sel_end, bspace_pair); - - if (noSelection) { - if (success) { - this->text_sel_start = this->text_sel_end = bspace_pair.first; - } else { // nothing deleted - this->text_sel_start = this->text_sel_end = bspace_pair.second; - } - } else { - if (success) { - this->text_sel_start = this->text_sel_end = bspace_pair.first; - } else { // nothing deleted - this->text_sel_start = bspace_pair.first; - this->text_sel_end = bspace_pair.second; - } - } - - sp_text_context_update_cursor(this); - sp_text_context_update_text_selection(this); - DocumentUndo::done(sp_desktop_document(desktop), SP_VERB_CONTEXT_TEXT, - _("Backspace")); - } - return TRUE; - case GDK_KEY_Delete: - case GDK_KEY_KP_Delete: - if (this->text) { - bool noSelection = false; - - if (MOD__CTRL(event)) { - this->text_sel_start = this->text_sel_end; - } - - if (this->text_sel_start == this->text_sel_end) { - if (MOD__CTRL(event)) { - this->text_sel_end.nextEndOfWord(); - } else { - this->text_sel_end.nextCursorPosition(); - } - noSelection = true; - } - - iterator_pair del_pair; - bool success = sp_te_delete(this->text, this->text_sel_start, this->text_sel_end, del_pair); - - if (noSelection) { - this->text_sel_start = this->text_sel_end = del_pair.first; - } else { - if (success) { - this->text_sel_start = this->text_sel_end = del_pair.first; - } else { // nothing deleted - this->text_sel_start = del_pair.first; - this->text_sel_end = del_pair.second; - } - } - - - sp_text_context_update_cursor(this); - sp_text_context_update_text_selection(this); - DocumentUndo::done(sp_desktop_document(desktop), SP_VERB_CONTEXT_TEXT, - _("Delete")); - } - return TRUE; - case GDK_KEY_Left: - case GDK_KEY_KP_Left: - case GDK_KEY_KP_4: - if (this->text) { - if (MOD__ALT(event)) { - gint mul = 1 + gobble_key_events( - get_group0_keyval(&event->key), 0); // with any mask - if (MOD__SHIFT(event)) - sp_te_adjust_kerning_screen(this->text, this->text_sel_start, this->text_sel_end, desktop, Geom::Point(mul*-10, 0)); - else - sp_te_adjust_kerning_screen(this->text, this->text_sel_start, this->text_sel_end, desktop, Geom::Point(mul*-1, 0)); - sp_text_context_update_cursor(this); - sp_text_context_update_text_selection(this); - DocumentUndo::maybeDone(sp_desktop_document(desktop), "kern:left", SP_VERB_CONTEXT_TEXT, - _("Kern to the left")); - } else { - if (MOD__CTRL(event)) - this->text_sel_end.cursorLeftWithControl(); - else - this->text_sel_end.cursorLeft(); - cursor_moved = true; - break; - } - } - return TRUE; - case GDK_KEY_Right: - case GDK_KEY_KP_Right: - case GDK_KEY_KP_6: - if (this->text) { - if (MOD__ALT(event)) { - gint mul = 1 + gobble_key_events( - get_group0_keyval(&event->key), 0); // with any mask - if (MOD__SHIFT(event)) - sp_te_adjust_kerning_screen(this->text, this->text_sel_start, this->text_sel_end, desktop, Geom::Point(mul*10, 0)); - else - sp_te_adjust_kerning_screen(this->text, this->text_sel_start, this->text_sel_end, desktop, Geom::Point(mul*1, 0)); - sp_text_context_update_cursor(this); - sp_text_context_update_text_selection(this); - DocumentUndo::maybeDone(sp_desktop_document(desktop), "kern:right", SP_VERB_CONTEXT_TEXT, - _("Kern to the right")); - } else { - if (MOD__CTRL(event)) - this->text_sel_end.cursorRightWithControl(); - else - this->text_sel_end.cursorRight(); - cursor_moved = true; - break; - } - } - return TRUE; - case GDK_KEY_Up: - case GDK_KEY_KP_Up: - case GDK_KEY_KP_8: - if (this->text) { - if (MOD__ALT(event)) { - gint mul = 1 + gobble_key_events( - get_group0_keyval(&event->key), 0); // with any mask - if (MOD__SHIFT(event)) - sp_te_adjust_kerning_screen(this->text, this->text_sel_start, this->text_sel_end, desktop, Geom::Point(0, mul*-10)); - else - sp_te_adjust_kerning_screen(this->text, this->text_sel_start, this->text_sel_end, desktop, Geom::Point(0, mul*-1)); - sp_text_context_update_cursor(this); - sp_text_context_update_text_selection(this); - DocumentUndo::maybeDone(sp_desktop_document(desktop), "kern:up", SP_VERB_CONTEXT_TEXT, - _("Kern up")); - } else { - if (MOD__CTRL(event)) - this->text_sel_end.cursorUpWithControl(); - else - this->text_sel_end.cursorUp(); - cursor_moved = true; - break; - } - } - return TRUE; - case GDK_KEY_Down: - case GDK_KEY_KP_Down: - case GDK_KEY_KP_2: - if (this->text) { - if (MOD__ALT(event)) { - gint mul = 1 + gobble_key_events( - get_group0_keyval(&event->key), 0); // with any mask - if (MOD__SHIFT(event)) - sp_te_adjust_kerning_screen(this->text, this->text_sel_start, this->text_sel_end, desktop, Geom::Point(0, mul*10)); - else - sp_te_adjust_kerning_screen(this->text, this->text_sel_start, this->text_sel_end, desktop, Geom::Point(0, mul*1)); - sp_text_context_update_cursor(this); - sp_text_context_update_text_selection(this); - DocumentUndo::maybeDone(sp_desktop_document(desktop), "kern:down", SP_VERB_CONTEXT_TEXT, - _("Kern down")); - } else { - if (MOD__CTRL(event)) - this->text_sel_end.cursorDownWithControl(); - else - this->text_sel_end.cursorDown(); - cursor_moved = true; - break; - } - } - return TRUE; - case GDK_KEY_Home: - case GDK_KEY_KP_Home: - if (this->text) { - if (MOD__CTRL(event)) - this->text_sel_end.thisStartOfShape(); - else - this->text_sel_end.thisStartOfLine(); - cursor_moved = true; - break; - } - return TRUE; - case GDK_KEY_End: - case GDK_KEY_KP_End: - if (this->text) { - if (MOD__CTRL(event)) - this->text_sel_end.nextStartOfShape(); - else - this->text_sel_end.thisEndOfLine(); - cursor_moved = true; - break; - } - return TRUE; - case GDK_KEY_Page_Down: - case GDK_KEY_KP_Page_Down: - if (this->text) { - this->text_sel_end.cursorDown(screenlines); - cursor_moved = true; - break; - } - return TRUE; - case GDK_KEY_Page_Up: - case GDK_KEY_KP_Page_Up: - if (this->text) { - this->text_sel_end.cursorUp(screenlines); - cursor_moved = true; - break; - } - return TRUE; - case GDK_KEY_Escape: - if (this->creating) { - this->creating = 0; - if (this->grabbed) { - sp_canvas_item_ungrab(this->grabbed, GDK_CURRENT_TIME); - this->grabbed = NULL; - } - Inkscape::Rubberband::get(desktop)->stop(); - } else { - sp_desktop_selection(desktop)->clear(); - } - this->nascent_object = FALSE; - return TRUE; - case GDK_KEY_bracketleft: - if (this->text) { - if (MOD__ALT(event) || MOD__CTRL(event)) { - if (MOD__ALT(event)) { - if (MOD__SHIFT(event)) { - // FIXME: alt+shift+[] does not work, don't know why - sp_te_adjust_rotation_screen(this->text, this->text_sel_start, this->text_sel_end, desktop, -10); - } else { - sp_te_adjust_rotation_screen(this->text, this->text_sel_start, this->text_sel_end, desktop, -1); - } - } else { - sp_te_adjust_rotation(this->text, this->text_sel_start, this->text_sel_end, desktop, -90); - } - DocumentUndo::maybeDone(sp_desktop_document(desktop), "textrot:ccw", SP_VERB_CONTEXT_TEXT, - _("Rotate counterclockwise")); - sp_text_context_update_cursor(this); - sp_text_context_update_text_selection(this); - return TRUE; - } - } - break; - case GDK_KEY_bracketright: - if (this->text) { - if (MOD__ALT(event) || MOD__CTRL(event)) { - if (MOD__ALT(event)) { - if (MOD__SHIFT(event)) { - // FIXME: alt+shift+[] does not work, don't know why - sp_te_adjust_rotation_screen(this->text, this->text_sel_start, this->text_sel_end, desktop, 10); - } else { - sp_te_adjust_rotation_screen(this->text, this->text_sel_start, this->text_sel_end, desktop, 1); - } - } else { - sp_te_adjust_rotation(this->text, this->text_sel_start, this->text_sel_end, desktop, 90); - } - DocumentUndo::maybeDone(sp_desktop_document(desktop), "textrot:cw", SP_VERB_CONTEXT_TEXT, - _("Rotate clockwise")); - sp_text_context_update_cursor(this); - sp_text_context_update_text_selection(this); - return TRUE; - } - } - break; - case GDK_KEY_less: - case GDK_KEY_comma: - if (this->text) { - if (MOD__ALT(event)) { - if (MOD__CTRL(event)) { - if (MOD__SHIFT(event)) - sp_te_adjust_linespacing_screen(this->text, this->text_sel_start, this->text_sel_end, desktop, -10); - else - sp_te_adjust_linespacing_screen(this->text, this->text_sel_start, this->text_sel_end, desktop, -1); - DocumentUndo::maybeDone(sp_desktop_document(desktop), "linespacing:dec", SP_VERB_CONTEXT_TEXT, - _("Contract line spacing")); - } else { - if (MOD__SHIFT(event)) - sp_te_adjust_tspan_letterspacing_screen(this->text, this->text_sel_start, this->text_sel_end, desktop, -10); - else - sp_te_adjust_tspan_letterspacing_screen(this->text, this->text_sel_start, this->text_sel_end, desktop, -1); - DocumentUndo::maybeDone(sp_desktop_document(desktop), "letterspacing:dec", SP_VERB_CONTEXT_TEXT, - _("Contract letter spacing")); - } - sp_text_context_update_cursor(this); - sp_text_context_update_text_selection(this); - return TRUE; - } - } - break; - case GDK_KEY_greater: - case GDK_KEY_period: - if (this->text) { - if (MOD__ALT(event)) { - if (MOD__CTRL(event)) { - if (MOD__SHIFT(event)) - sp_te_adjust_linespacing_screen(this->text, this->text_sel_start, this->text_sel_end, desktop, 10); - else - sp_te_adjust_linespacing_screen(this->text, this->text_sel_start, this->text_sel_end, desktop, 1); - DocumentUndo::maybeDone(sp_desktop_document(desktop), "linespacing:inc", SP_VERB_CONTEXT_TEXT, - _("Expand line spacing")); - } else { - if (MOD__SHIFT(event)) - sp_te_adjust_tspan_letterspacing_screen(this->text, this->text_sel_start, this->text_sel_end, desktop, 10); - else - sp_te_adjust_tspan_letterspacing_screen(this->text, this->text_sel_start, this->text_sel_end, desktop, 1); - DocumentUndo::maybeDone(sp_desktop_document(desktop), "letterspacing:inc", SP_VERB_CONTEXT_TEXT, - _("Expand letter spacing"));\ - } - sp_text_context_update_cursor(this); - sp_text_context_update_text_selection(this); - return TRUE; - } - } - break; - default: - break; - } - - if (cursor_moved) { - if (!MOD__SHIFT(event)) - this->text_sel_start = this->text_sel_end; - if (old_start != this->text_sel_start || old_end != this->text_sel_end) { - sp_text_context_update_cursor(this); - sp_text_context_update_text_selection(this); - } - return TRUE; - } - - } else return TRUE; // return the "I took care of it" value if it was consumed by the IM - } else { // do nothing if there's no object to type in - the key will be sent to parent context, - // except up/down that are swallowed to prevent the zoom field from activation - if ((group0_keyval == GDK_KEY_Up || - group0_keyval == GDK_KEY_Down || - group0_keyval == GDK_KEY_KP_Up || - group0_keyval == GDK_KEY_KP_Down ) - && !MOD__CTRL_ONLY(event)) { - return TRUE; - } else if (group0_keyval == GDK_KEY_Escape) { // cancel rubberband - if (this->creating) { - this->creating = 0; - if (this->grabbed) { - sp_canvas_item_ungrab(this->grabbed, GDK_CURRENT_TIME); - this->grabbed = NULL; - } - Inkscape::Rubberband::get(desktop)->stop(); - } - } else if ((group0_keyval == GDK_KEY_x || group0_keyval == GDK_KEY_X) && MOD__ALT_ONLY(event)) { - desktop->setToolboxFocusTo ("altx-text"); - return TRUE; - } - } - break; - } - - case GDK_KEY_RELEASE: - if (!this->unimode && this->imc && gtk_im_context_filter_keypress(this->imc, (GdkEventKey*) event)) { - return TRUE; - } - break; - default: - break; - } - - // if nobody consumed it so far -// if ((SP_EVENT_CONTEXT_CLASS(sp_text_context_parent_class))->root_handler) { // and there's a handler in parent context, -// return (SP_EVENT_CONTEXT_CLASS(sp_text_context_parent_class))->root_handler(event_context, event); // send event to parent -// } else { -// return FALSE; // return "I did nothing" value so that global shortcuts can be activated -// } - return ToolBase::root_handler(event); - -} - -/** - Attempts to paste system clipboard into the currently edited text, returns true on success - */ -bool sp_text_paste_inline(ToolBase *ec) -{ - if (!SP_IS_TEXT_CONTEXT(ec)) - return false; - - TextTool *tc = SP_TEXT_CONTEXT(ec); - - if ((tc->text) || (tc->nascent_object)) { - // there is an active text object in this context, or a new object was just created - - Glib::RefPtr refClipboard = Gtk::Clipboard::get(); - Glib::ustring const clip_text = refClipboard->wait_for_text(); - - if (!clip_text.empty()) { - // Fix for 244940 - // The XML standard defines the following as valid characters - // (Extensible Markup Language (XML) 1.0 (Fourth Edition) paragraph 2.2) - // char ::= #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD] | [#x10000-#x10FFFF] - // Since what comes in off the paste buffer will go right into XML, clean - // the text here. - Glib::ustring text(clip_text); - Glib::ustring::iterator itr = text.begin(); - gunichar paste_string_uchar; - - while(itr != text.end()) - { - paste_string_uchar = *itr; - - // Make sure we don't have a control character. We should really check - // for the whole range above... Add the rest of the invalid cases from - // above if we find additional issues - if(paste_string_uchar >= 0x00000020 || - paste_string_uchar == 0x00000009 || - paste_string_uchar == 0x0000000A || - paste_string_uchar == 0x0000000D) { - itr++; - } else { - itr = text.erase(itr); - } - } - - if (!tc->text) { // create text if none (i.e. if nascent_object) - sp_text_context_setup_text(tc); - tc->nascent_object = 0; // we don't need it anymore, having created a real - } - - // using indices is slow in ustrings. Whatever. - Glib::ustring::size_type begin = 0; - for ( ; ; ) { - Glib::ustring::size_type end = text.find('\n', begin); - if (end == Glib::ustring::npos) { - if (begin != text.length()) - tc->text_sel_start = tc->text_sel_end = sp_te_replace(tc->text, tc->text_sel_start, tc->text_sel_end, text.substr(begin).c_str()); - break; - } - tc->text_sel_start = tc->text_sel_end = sp_te_replace(tc->text, tc->text_sel_start, tc->text_sel_end, text.substr(begin, end - begin).c_str()); - tc->text_sel_start = tc->text_sel_end = sp_te_insert_line(tc->text, tc->text_sel_start); - begin = end + 1; - } - DocumentUndo::done(sp_desktop_document(ec->desktop), SP_VERB_CONTEXT_TEXT, - _("Paste text")); - - return true; - } - } // FIXME: else create and select a new object under cursor! - - return false; -} - -/** - Gets the raw characters that comprise the currently selected text, converting line - breaks into lf characters. -*/ -Glib::ustring sp_text_get_selected_text(ToolBase const *ec) -{ - if (!SP_IS_TEXT_CONTEXT(ec)) - return ""; - TextTool const *tc = SP_TEXT_CONTEXT(ec); - if (tc->text == NULL) - return ""; - - return sp_te_get_string_multiline(tc->text, tc->text_sel_start, tc->text_sel_end); -} - -SPCSSAttr *sp_text_get_style_at_cursor(ToolBase const *ec) -{ - if (!SP_IS_TEXT_CONTEXT(ec)) - return NULL; - TextTool const *tc = SP_TEXT_CONTEXT(ec); - if (tc->text == NULL) - return NULL; - - SPObject const *obj = sp_te_object_at_position(tc->text, tc->text_sel_end); - - if (obj) { - return take_style_from_item(const_cast(obj)); - } - - return NULL; -} - -/** - Deletes the currently selected characters. Returns false if there is no - text selection currently. -*/ -bool sp_text_delete_selection(ToolBase *ec) -{ - if (!SP_IS_TEXT_CONTEXT(ec)) - return false; - TextTool *tc = SP_TEXT_CONTEXT(ec); - if (tc->text == NULL) - return false; - - if (tc->text_sel_start == tc->text_sel_end) - return false; - - iterator_pair pair; - bool success = sp_te_delete(tc->text, tc->text_sel_start, tc->text_sel_end, pair); - - - if (success) { - tc->text_sel_start = tc->text_sel_end = pair.first; - } else { // nothing deleted - tc->text_sel_start = pair.first; - tc->text_sel_end = pair.second; - } - - sp_text_context_update_cursor(tc); - sp_text_context_update_text_selection(tc); - - return true; -} - -/** - * \param selection Should not be NULL. - */ -static void -sp_text_context_selection_changed(Inkscape::Selection *selection, TextTool *tc) -{ - g_assert(selection != NULL); - - ToolBase *ec = SP_EVENT_CONTEXT(tc); - - ec->shape_editor->unset_item(SH_KNOTHOLDER); - SPItem *item = selection->singleItem(); - if (item && SP_IS_FLOWTEXT(item) && SP_FLOWTEXT(item)->has_internal_frame()) { - ec->shape_editor->set_item(item, SH_KNOTHOLDER); - } - - if (tc->text && (item != tc->text)) { - sp_text_context_forget_text(tc); - } - tc->text = NULL; - - if (SP_IS_TEXT(item) || SP_IS_FLOWTEXT(item)) { - tc->text = item; - Inkscape::Text::Layout const *layout = te_get_layout(tc->text); - if (layout) - tc->text_sel_start = tc->text_sel_end = layout->end(); - } else { - tc->text = NULL; - } - - // we update cursor without scrolling, because this position may not be final; - // item_handler moves cusros to the point of click immediately - sp_text_context_update_cursor(tc, false); - sp_text_context_update_text_selection(tc); -} - -static void -sp_text_context_selection_modified(Inkscape::Selection */*selection*/, guint /*flags*/, TextTool *tc) -{ - sp_text_context_update_cursor(tc); - sp_text_context_update_text_selection(tc); -} - -static bool sp_text_context_style_set(SPCSSAttr const *css, TextTool *tc) -{ - if (tc->text == NULL) - return false; - if (tc->text_sel_start == tc->text_sel_end) - return false; // will get picked up by the parent and applied to the whole text object - - sp_te_apply_style(tc->text, tc->text_sel_start, tc->text_sel_end, css); - DocumentUndo::done(sp_desktop_document(tc->desktop), SP_VERB_CONTEXT_TEXT, - _("Set text style")); - sp_text_context_update_cursor(tc); - sp_text_context_update_text_selection(tc); - - return true; -} - -static int -sp_text_context_style_query(SPStyle *style, int property, TextTool *tc) -{ - if (tc->text == NULL) { - return QUERY_STYLE_NOTHING; - } - const Inkscape::Text::Layout *layout = te_get_layout(tc->text); - if (layout == NULL) { - return QUERY_STYLE_NOTHING; - } - sp_text_context_validate_cursor_iterators(tc); - - GSList *styles_list = NULL; - - Inkscape::Text::Layout::iterator begin_it, end_it; - if (tc->text_sel_start < tc->text_sel_end) { - begin_it = tc->text_sel_start; - end_it = tc->text_sel_end; - } else { - begin_it = tc->text_sel_end; - end_it = tc->text_sel_start; - } - if (begin_it == end_it) { - if (!begin_it.prevCharacter()) { - end_it.nextCharacter(); - } - } - for (Inkscape::Text::Layout::iterator it = begin_it ; it < end_it ; it.nextStartOfSpan()) { - SPObject const *pos_obj = 0; - void *rawptr = 0; - layout->getSourceOfCharacter(it, &rawptr); - if (!rawptr || !SP_IS_OBJECT(rawptr)) { - continue; - } - pos_obj = SP_OBJECT(rawptr); - while (SP_IS_STRING(pos_obj) && pos_obj->parent) { - pos_obj = pos_obj->parent; // SPStrings don't have style - } - styles_list = g_slist_prepend(styles_list, (gpointer)pos_obj); - } - - int result = sp_desktop_query_style_from_list (styles_list, style, property); - - g_slist_free(styles_list); - return result; -} - -static void sp_text_context_validate_cursor_iterators(TextTool *tc) -{ - if (tc->text == NULL) - return; - Inkscape::Text::Layout const *layout = te_get_layout(tc->text); - if (layout) { // undo can change the text length without us knowing it - layout->validateIterator(&tc->text_sel_start); - layout->validateIterator(&tc->text_sel_end); - } -} - -static void sp_text_context_update_cursor(TextTool *tc, bool scroll_to_see) -{ - // due to interruptible display, tc may already be destroyed during a display update before - // the cursor update (can't do both atomically, alas) - if (!tc->desktop) return; - - if (tc->text) { - Geom::Point p0, p1; - sp_te_get_cursor_coords(tc->text, tc->text_sel_end, p0, p1); - Geom::Point const d0 = p0 * tc->text->i2dt_affine(); - Geom::Point const d1 = p1 * tc->text->i2dt_affine(); - - // scroll to show cursor - if (scroll_to_see) { - Geom::Point const center = SP_EVENT_CONTEXT(tc)->desktop->get_display_area().midpoint(); - if (Geom::L2(d0 - center) > Geom::L2(d1 - center)) - // unlike mouse moves, here we must scroll all the way at first shot, so we override the autoscrollspeed - SP_EVENT_CONTEXT(tc)->desktop->scroll_to_point(d0, 1.0); - else - SP_EVENT_CONTEXT(tc)->desktop->scroll_to_point(d1, 1.0); - } - - sp_canvas_item_show(tc->cursor); - tc->cursor->setCoords(d0, d1); - - /* fixme: ... need another transformation to get canvas widget coordinate space? */ - if (tc->imc) { - GdkRectangle im_cursor = { 0, 0, 1, 1 }; - Geom::Point const top_left = SP_EVENT_CONTEXT(tc)->desktop->get_display_area().corner(3); - Geom::Point const im_d0 = SP_EVENT_CONTEXT(tc)->desktop->d2w(d0 - top_left); - Geom::Point const im_d1 = SP_EVENT_CONTEXT(tc)->desktop->d2w(d1 - top_left); - im_cursor.x = (int) floor(im_d0[Geom::X]); - im_cursor.y = (int) floor(im_d1[Geom::Y]); - im_cursor.width = (int) floor(im_d1[Geom::X]) - im_cursor.x; - im_cursor.height = (int) floor(im_d0[Geom::Y]) - im_cursor.y; - gtk_im_context_set_cursor_location(tc->imc, &im_cursor); - } - - tc->show = TRUE; - tc->phase = 1; - - Inkscape::Text::Layout const *layout = te_get_layout(tc->text); - int const nChars = layout->iteratorToCharIndex(layout->end()); - char const *trunc = ""; - bool truncated = false; - if (layout->inputTruncated()) { - truncated = true; - trunc = _(" [truncated]"); - } - if (SP_IS_FLOWTEXT(tc->text)) { - SPItem *frame = SP_FLOWTEXT(tc->text)->get_frame (NULL); // first frame only - if (frame) { - if (truncated) { - SP_CTRLRECT(tc->frame)->setColor(0xff0000ff, false, 0); - } else { - SP_CTRLRECT(tc->frame)->setColor(0x0000ff7f, false, 0); - } - sp_canvas_item_show(tc->frame); - Geom::OptRect frame_bbox = frame->desktopVisualBounds(); - if (frame_bbox) { - SP_CTRLRECT(tc->frame)->setRectangle(*frame_bbox); - } - } - - SP_EVENT_CONTEXT(tc)->message_context->setF(Inkscape::NORMAL_MESSAGE, ngettext("Type or edit flowed text (%d character%s); Enter to start new paragraph.", "Type or edit flowed text (%d characters%s); Enter to start new paragraph.", nChars), nChars, trunc); - } else { - SP_EVENT_CONTEXT(tc)->message_context->setF(Inkscape::NORMAL_MESSAGE, ngettext("Type or edit text (%d character%s); Enter to start new line.", "Type or edit text (%d characters%s); Enter to start new line.", nChars), nChars, trunc); - } - - } else { - sp_canvas_item_hide(tc->cursor); - sp_canvas_item_hide(tc->frame); - tc->show = FALSE; - if (!tc->nascent_object) { - SP_EVENT_CONTEXT(tc)->message_context->set(Inkscape::NORMAL_MESSAGE, _("Click to select or create text, drag to create flowed text; then type.")); // FIXME: this is a copy of string from tools-switch, do not desync - } - } - - SP_EVENT_CONTEXT(tc)->desktop->emitToolSubselectionChanged((gpointer)tc); -} - -static void sp_text_context_update_text_selection(TextTool *tc) -{ - // due to interruptible display, tc may already be destroyed during a display update before - // the selection update (can't do both atomically, alas) - if (!tc->desktop) return; - - for (std::vector::iterator it = tc->text_selection_quads.begin() ; it != tc->text_selection_quads.end() ; it++) { - sp_canvas_item_hide(*it); - sp_canvas_item_destroy(*it); - } - tc->text_selection_quads.clear(); - - std::vector quads; - if (tc->text != NULL) - quads = sp_te_create_selection_quads(tc->text, tc->text_sel_start, tc->text_sel_end, (tc->text)->i2dt_affine()); - for (unsigned i = 0 ; i < quads.size() ; i += 4) { - SPCanvasItem *quad_canvasitem; - quad_canvasitem = sp_canvas_item_new(sp_desktop_controls(tc->desktop), SP_TYPE_CTRLQUADR, NULL); - // FIXME: make the color settable in prefs - // for now, use semitrasparent blue, as cairo cannot do inversion :( - sp_ctrlquadr_set_rgba32(SP_CTRLQUADR(quad_canvasitem), 0x00777777); - sp_ctrlquadr_set_coords(SP_CTRLQUADR(quad_canvasitem), quads[i], quads[i+1], quads[i+2], quads[i+3]); - sp_canvas_item_show(quad_canvasitem); - tc->text_selection_quads.push_back(quad_canvasitem); - } -} - -static gint sp_text_context_timeout(TextTool *tc) -{ - if (tc->show) { - sp_canvas_item_show(tc->cursor); - if (tc->phase) { - tc->phase = 0; - tc->cursor->setRgba32(0x000000ff); - } else { - tc->phase = 1; - tc->cursor->setRgba32(0xffffffff); - } - } - - return TRUE; -} - -static void sp_text_context_forget_text(TextTool *tc) -{ - if (! tc->text) return; - SPItem *ti = tc->text; - (void)ti; - /* We have to set it to zero, - * or selection changed signal messes everything up */ - tc->text = NULL; - -/* FIXME: this automatic deletion when nothing is inputted crashes the XML edittor and also crashes when duplicating an empty flowtext. - So don't create an empty flowtext in the first place? Create it when first character is typed. - */ -/* - if ((SP_IS_TEXT(ti) || SP_IS_FLOWTEXT(ti)) && sp_te_input_is_empty(ti)) { - Inkscape::XML::Node *text_repr = ti->getRepr(); - // the repr may already have been unparented - // if we were called e.g. as the result of - // an undo or the element being removed from - // the XML editor - if ( text_repr && text_repr->parent() ) { - sp_repr_unparent(text_repr); - SPDocumentUndo::done(sp_desktop_document(tc->desktop), SP_VERB_CONTEXT_TEXT, - _("Remove empty text")); - } - } -*/ -} - -gint sptc_focus_in(GtkWidget */*widget*/, GdkEventFocus */*event*/, TextTool *tc) -{ - gtk_im_context_focus_in(tc->imc); - return FALSE; -} - -gint sptc_focus_out(GtkWidget */*widget*/, GdkEventFocus */*event*/, TextTool *tc) -{ - gtk_im_context_focus_out(tc->imc); - return FALSE; -} - -static void sptc_commit(GtkIMContext */*imc*/, gchar *string, TextTool *tc) -{ - if (!tc->text) { - sp_text_context_setup_text(tc); - tc->nascent_object = 0; // we don't need it anymore, having created a real - } - - tc->text_sel_start = tc->text_sel_end = sp_te_replace(tc->text, tc->text_sel_start, tc->text_sel_end, string); - sp_text_context_update_cursor(tc); - sp_text_context_update_text_selection(tc); - - DocumentUndo::done(tc->text->document, SP_VERB_CONTEXT_TEXT, - _("Type text")); -} - -void sp_text_context_place_cursor (TextTool *tc, SPObject *text, Inkscape::Text::Layout::iterator where) -{ - SP_EVENT_CONTEXT_DESKTOP (tc)->selection->set (text); - tc->text_sel_start = tc->text_sel_end = where; - sp_text_context_update_cursor(tc); - sp_text_context_update_text_selection(tc); -} - -void sp_text_context_place_cursor_at (TextTool *tc, SPObject *text, Geom::Point const p) -{ - SP_EVENT_CONTEXT_DESKTOP (tc)->selection->set (text); - sp_text_context_place_cursor (tc, text, sp_te_get_position_by_coords(tc->text, p)); -} - -Inkscape::Text::Layout::iterator *sp_text_context_get_cursor_position(TextTool *tc, SPObject *text) -{ - if (text != tc->text) - return NULL; - return &(tc->text_sel_end); -} - -} -} -} - -/* - Local Variables: - mode:c++ - c-file-style:"stroustrup" - c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) - indent-tabs-mode:nil - fill-column:99 - End: -*/ -// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/text-context.h b/src/text-context.h deleted file mode 100644 index 1b618a1f0..000000000 --- a/src/text-context.h +++ /dev/null @@ -1,101 +0,0 @@ -#ifndef __SP_TEXT_CONTEXT_H__ -#define __SP_TEXT_CONTEXT_H__ - -/* - * TextTool - * - * Authors: - * Lauris Kaplinski - * bulia byak - * - * Copyright (C) 1999-2005 authors - * Copyright (C) 2001 Ximian, Inc. - * - * Released under GNU GPL, read the file 'COPYING' for more information - */ - -/* #include */ -#include -#include -#include - -#include "event-context.h" -#include <2geom/point.h> -#include "libnrtype/Layout-TNG.h" - -#define SP_TEXT_CONTEXT(obj) (dynamic_cast((Inkscape::UI::Tools::ToolBase*)obj)) -#define SP_IS_TEXT_CONTEXT(obj) (dynamic_cast((const Inkscape::UI::Tools::ToolBase*)obj) != NULL) - -struct SPCtrlLine; - -namespace Inkscape { -namespace UI { -namespace Tools { - -class TextTool : public ToolBase { -public: - TextTool(); - virtual ~TextTool(); - - sigc::connection sel_changed_connection; - sigc::connection sel_modified_connection; - sigc::connection style_set_connection; - sigc::connection style_query_connection; - - GtkIMContext *imc; - - SPItem *text; // the text we're editing, or NULL if none selected - - /* Text item position in root coordinates */ - Geom::Point pdoc; - /* Insertion point position */ - Inkscape::Text::Layout::iterator text_sel_start; - Inkscape::Text::Layout::iterator text_sel_end; - - gchar uni[9]; - bool unimode; - guint unipos; - - SPCtrlLine *cursor; - SPCanvasItem *indicator; - SPCanvasItem *frame; // hiliting the first frame of flowtext; FIXME: make this a list to accommodate arbitrarily many chained shapes - std::vector text_selection_quads; - gint timeout; - guint show : 1; - guint phase : 1; - guint nascent_object : 1; // true if we're clicked on canvas to put cursor, but no text typed yet so ->text is still NULL - - guint over_text : 1; // true if cursor is over a text object - - guint dragging : 2; // dragging selection over text - - guint creating : 1; // dragging rubberband to create flowtext - SPCanvasItem *grabbed; // we grab while we are creating, to get events even if the mouse goes out of the window - Geom::Point p0; // initial point if the flowtext rect - - /* Preedit String */ - gchar* preedit_string; - - static const std::string prefsPath; - - virtual void setup(); - virtual void finish(); - virtual bool root_handler(GdkEvent* event); - virtual bool item_handler(SPItem* item, GdkEvent* event); - - virtual const std::string& getPrefsPath(); -}; - -bool sp_text_paste_inline(ToolBase *ec); -Glib::ustring sp_text_get_selected_text(ToolBase const *ec); -SPCSSAttr *sp_text_get_style_at_cursor(ToolBase const *ec); -bool sp_text_delete_selection(ToolBase *ec); -void sp_text_context_place_cursor (TextTool *tc, SPObject *text, Inkscape::Text::Layout::iterator where); -void sp_text_context_place_cursor_at (TextTool *tc, SPObject *text, Geom::Point const p); -Inkscape::Text::Layout::iterator *sp_text_context_get_cursor_position(TextTool *tc, SPObject *text); - -} -} -} - -#endif diff --git a/src/tools-switch.cpp b/src/tools-switch.cpp index beff0e5f3..fe9d32f43 100644 --- a/src/tools-switch.cpp +++ b/src/tools-switch.cpp @@ -25,36 +25,36 @@ #include -#include "select-context.h" -#include "ui/tool/node-tool.h" -#include "tweak-context.h" -#include "spray-context.h" +#include "ui/tools/select-tool.h" +#include "ui/tools/node-tool.h" +#include "ui/tools/tweak-tool.h" +#include "ui/tools/spray-tool.h" #include "sp-path.h" -#include "rect-context.h" +#include "ui/tools/rect-tool.h" #include "sp-rect.h" -#include "box3d-context.h" +#include "ui/tools/box3d-tool.h" #include "box3d.h" -#include "arc-context.h" +#include "ui/tools/arc-tool.h" #include "sp-ellipse.h" -#include "star-context.h" +#include "ui/tools/tweak-tool.h" #include "sp-star.h" -#include "spiral-context.h" +#include "ui/tools/spiral-tool.h" #include "sp-spiral.h" -#include "dyna-draw-context.h" -#include "eraser-context.h" -#include "pen-context.h" -#include "pencil-context.h" -#include "lpe-tool-context.h" -#include "text-context.h" +#include "ui/tools/calligraphic-tool.h" +#include "ui/tools/eraser-tool.h" +#include "ui/tools/pen-tool.h" +#include "ui/tools/pencil-tool.h" +#include "ui/tools/lpe-tool.h" +#include "ui/tools/text-tool.h" #include "sp-text.h" #include "sp-flowtext.h" -#include "gradient-context.h" -#include "mesh-context.h" -#include "zoom-context.h" -#include "measure-context.h" -#include "dropper-context.h" -#include "connector-context.h" -#include "flood-context.h" +#include "ui/tools/gradient-tool.h" +#include "ui/tools/mesh-tool.h" +#include "ui/tools/zoom-tool.h" +#include "ui/tools/measure-tool.h" +#include "ui/tools/dropper-tool.h" +#include "ui/tools/connector-tool.h" +#include "ui/tools/flood-tool.h" #include "sp-offset.h" #include "message-context.h" diff --git a/src/tweak-context.cpp b/src/tweak-context.cpp deleted file mode 100644 index 5129e152e..000000000 --- a/src/tweak-context.cpp +++ /dev/null @@ -1,1524 +0,0 @@ -/* - * tweaking paths without node editing - * - * Authors: - * bulia byak - * Jon A. Cruz - * Abhishek Sharma - * - * Copyright (C) 2007 authors - * - * Released under GNU GPL, read the file 'COPYING' for more information - */ - -#include "config.h" - -#include -#include -#include - -#include - -#include "svg/svg.h" - -#include -#include "macros.h" -#include "document.h" -#include "document-undo.h" -#include "selection.h" -#include "desktop.h" -#include "desktop-events.h" -#include "desktop-handles.h" -#include "desktop-style.h" -#include "message-context.h" -#include "pixmaps/cursor-tweak-move.xpm" -#include "pixmaps/cursor-tweak-move-in.xpm" -#include "pixmaps/cursor-tweak-move-out.xpm" -#include "pixmaps/cursor-tweak-move-jitter.xpm" -#include "pixmaps/cursor-tweak-scale-up.xpm" -#include "pixmaps/cursor-tweak-scale-down.xpm" -#include "pixmaps/cursor-tweak-rotate-clockwise.xpm" -#include "pixmaps/cursor-tweak-rotate-counterclockwise.xpm" -#include "pixmaps/cursor-tweak-more.xpm" -#include "pixmaps/cursor-tweak-less.xpm" -#include "pixmaps/cursor-thin.xpm" -#include "pixmaps/cursor-thicken.xpm" -#include "pixmaps/cursor-attract.xpm" -#include "pixmaps/cursor-repel.xpm" -#include "pixmaps/cursor-push.xpm" -#include "pixmaps/cursor-roughen.xpm" -#include "pixmaps/cursor-color.xpm" -#include -#include "xml/repr.h" -#include "context-fns.h" -#include "sp-item.h" -#include "inkscape.h" -#include "color.h" -#include "svg/svg-color.h" -#include "splivarot.h" -#include "sp-item-group.h" -#include "sp-shape.h" -#include "sp-path.h" -#include "path-chemistry.h" -#include "sp-gradient.h" -#include "sp-stop.h" -#include "sp-gradient-reference.h" -#include "sp-linear-gradient.h" -#include "sp-radial-gradient.h" -#include "gradient-chemistry.h" -#include "sp-text.h" -#include "sp-flowtext.h" -#include "display/sp-canvas.h" -#include "display/canvas-bpath.h" -#include "display/canvas-arena.h" -#include "display/curve.h" -#include "livarot/Shape.h" -#include <2geom/transforms.h> -#include <2geom/circle.h> -#include "preferences.h" -#include "style.h" -#include "box3d.h" -#include "sp-item-transform.h" -#include "filter-chemistry.h" -#include "filters/gaussian-blur.h" -#include "verbs.h" - -#include "tweak-context.h" - -using Inkscape::DocumentUndo; - -#define DDC_RED_RGBA 0xff0000ff - -#define DYNA_MIN_WIDTH 1.0e-6 - -#include "tool-factory.h" - -namespace Inkscape { -namespace UI { -namespace Tools { - -namespace { - ToolBase* createTweakContext() { - return new TweakTool(); - } - - bool tweakContextRegistered = ToolFactory::instance().registerObject("/tools/tweak", createTweakContext); -} - -const std::string& TweakTool::getPrefsPath() { - return TweakTool::prefsPath; -} - -const std::string TweakTool::prefsPath = "/tools/tweak"; - -TweakTool::TweakTool() : ToolBase() { - this->mode = 0; - this->dilate_area = 0; - this->usetilt = 0; - this->usepressure = 0; - this->is_drawing = false; - this->fidelity = 0; - - this->cursor_shape = cursor_push_xpm; - this->hot_x = 4; - this->hot_y = 4; - - /* attributes */ - this->dragging = FALSE; - - this->width = 0.2; - this->force = 0.2; - this->pressure = TC_DEFAULT_PRESSURE; - - this->is_dilating = false; - this->has_dilated = false; - - this->do_h = true; - this->do_s = true; - this->do_l = true; - this->do_o = false; -} - -TweakTool::~TweakTool() { - this->enableGrDrag(false); - - this->style_set_connection.disconnect(); - - if (this->dilate_area) { - sp_canvas_item_destroy(this->dilate_area); - this->dilate_area = NULL; - } -} - -static bool is_transform_mode (gint mode) -{ - return (mode == TWEAK_MODE_MOVE || - mode == TWEAK_MODE_MOVE_IN_OUT || - mode == TWEAK_MODE_MOVE_JITTER || - mode == TWEAK_MODE_SCALE || - mode == TWEAK_MODE_ROTATE || - mode == TWEAK_MODE_MORELESS); -} - -static bool is_color_mode (gint mode) -{ - return (mode == TWEAK_MODE_COLORPAINT || mode == TWEAK_MODE_COLORJITTER || mode == TWEAK_MODE_BLUR); -} - -void TweakTool::update_cursor (bool with_shift) { - guint num = 0; - gchar *sel_message = NULL; - - if (!desktop->selection->isEmpty()) { - num = g_slist_length(const_cast(desktop->selection->itemList())); - sel_message = g_strdup_printf(ngettext("%i object selected","%i objects selected",num), num); - } else { - sel_message = g_strdup_printf("%s", _("Nothing selected")); - } - - switch (this->mode) { - case TWEAK_MODE_MOVE: - this->message_context->setF(Inkscape::NORMAL_MESSAGE, _("%s. Drag to move."), sel_message); - this->cursor_shape = cursor_tweak_move_xpm; - break; - case TWEAK_MODE_MOVE_IN_OUT: - this->message_context->setF(Inkscape::NORMAL_MESSAGE, _("%s. Drag or click to move in; with Shift to move out."), sel_message); - if (with_shift) { - this->cursor_shape = cursor_tweak_move_out_xpm; - } else { - this->cursor_shape = cursor_tweak_move_in_xpm; - } - break; - case TWEAK_MODE_MOVE_JITTER: - this->message_context->setF(Inkscape::NORMAL_MESSAGE, _("%s. Drag or click to move randomly."), sel_message); - this->cursor_shape = cursor_tweak_move_jitter_xpm; - break; - case TWEAK_MODE_SCALE: - this->message_context->setF(Inkscape::NORMAL_MESSAGE, _("%s. Drag or click to scale down; with Shift to scale up."), sel_message); - if (with_shift) { - this->cursor_shape = cursor_tweak_scale_up_xpm; - } else { - this->cursor_shape = cursor_tweak_scale_down_xpm; - } - break; - case TWEAK_MODE_ROTATE: - this->message_context->setF(Inkscape::NORMAL_MESSAGE, _("%s. Drag or click to rotate clockwise; with Shift, counterclockwise."), sel_message); - if (with_shift) { - this->cursor_shape = cursor_tweak_rotate_counterclockwise_xpm; - } else { - this->cursor_shape = cursor_tweak_rotate_clockwise_xpm; - } - break; - case TWEAK_MODE_MORELESS: - this->message_context->setF(Inkscape::NORMAL_MESSAGE, _("%s. Drag or click to duplicate; with Shift, delete."), sel_message); - if (with_shift) { - this->cursor_shape = cursor_tweak_less_xpm; - } else { - this->cursor_shape = cursor_tweak_more_xpm; - } - break; - case TWEAK_MODE_PUSH: - this->message_context->setF(Inkscape::NORMAL_MESSAGE, _("%s. Drag to push paths."), sel_message); - this->cursor_shape = cursor_push_xpm; - break; - case TWEAK_MODE_SHRINK_GROW: - this->message_context->setF(Inkscape::NORMAL_MESSAGE, _("%s. Drag or click to inset paths; with Shift to outset."), sel_message); - if (with_shift) { - this->cursor_shape = cursor_thicken_xpm; - } else { - this->cursor_shape = cursor_thin_xpm; - } - break; - case TWEAK_MODE_ATTRACT_REPEL: - this->message_context->setF(Inkscape::NORMAL_MESSAGE, _("%s. Drag or click to attract paths; with Shift to repel."), sel_message); - if (with_shift) { - this->cursor_shape = cursor_repel_xpm; - } else { - this->cursor_shape = cursor_attract_xpm; - } - break; - case TWEAK_MODE_ROUGHEN: - this->message_context->setF(Inkscape::NORMAL_MESSAGE, _("%s. Drag or click to roughen paths."), sel_message); - this->cursor_shape = cursor_roughen_xpm; - break; - case TWEAK_MODE_COLORPAINT: - this->message_context->setF(Inkscape::NORMAL_MESSAGE, _("%s. Drag or click to paint objects with color."), sel_message); - this->cursor_shape = cursor_color_xpm; - break; - case TWEAK_MODE_COLORJITTER: - this->message_context->setF(Inkscape::NORMAL_MESSAGE, _("%s. Drag or click to randomize colors."), sel_message); - this->cursor_shape = cursor_color_xpm; - break; - case TWEAK_MODE_BLUR: - this->message_context->setF(Inkscape::NORMAL_MESSAGE, _("%s. Drag or click to increase blur; with Shift to decrease."), sel_message); - this->cursor_shape = cursor_color_xpm; - break; - } - - this->sp_event_context_update_cursor(); - g_free(sel_message); -} - -bool TweakTool::set_style(const SPCSSAttr* css) { - if (this->mode == TWEAK_MODE_COLORPAINT) { // intercept color setting only in this mode - // we cannot store properties with uris - css = sp_css_attr_unset_uris(const_cast(css)); - Inkscape::Preferences *prefs = Inkscape::Preferences::get(); - prefs->setStyle("/tools/tweak/style", const_cast(css)); - return true; - } - - return false; -} - -void TweakTool::setup() { - ToolBase::setup(); - - { - /* TODO: have a look at sp_dyna_draw_context_setup where the same is done.. generalize? at least make it an arcto! */ - Geom::PathVector path; - Geom::Circle(0, 0, 1).getPath(path); - - SPCurve *c = new SPCurve(path); - - this->dilate_area = sp_canvas_bpath_new(sp_desktop_controls(this->desktop), c); - c->unref(); - sp_canvas_bpath_set_fill(SP_CANVAS_BPATH(this->dilate_area), 0x00000000,(SPWindRule)0); - sp_canvas_bpath_set_stroke(SP_CANVAS_BPATH(this->dilate_area), 0xff9900ff, 1.0, SP_STROKE_LINEJOIN_MITER, SP_STROKE_LINECAP_BUTT); - sp_canvas_item_hide(this->dilate_area); - } - - this->is_drawing = false; - - sp_event_context_read(this, "width"); - sp_event_context_read(this, "mode"); - sp_event_context_read(this, "fidelity"); - sp_event_context_read(this, "force"); - sp_event_context_read(this, "usepressure"); - sp_event_context_read(this, "doh"); - sp_event_context_read(this, "dol"); - sp_event_context_read(this, "dos"); - sp_event_context_read(this, "doo"); - - this->style_set_connection = this->desktop->connectSetStyle( // catch style-setting signal in this tool - //sigc::bind(sigc::ptr_fun(&sp_tweak_context_style_set), this) - sigc::mem_fun(this, &TweakTool::set_style) - ); - - Inkscape::Preferences *prefs = Inkscape::Preferences::get(); - if (prefs->getBool("/tools/tweak/selcue")) { - this->enableSelectionCue(); - } - if (prefs->getBool("/tools/tweak/gradientdrag")) { - this->enableGrDrag(); - } -} - -void TweakTool::set(const Inkscape::Preferences::Entry& val) { - Glib::ustring path = val.getEntryName(); - - if (path == "width") { - this->width = CLAMP(val.getDouble(0.1), -1000.0, 1000.0); - } else if (path == "mode") { - this->mode = val.getInt(); - this->update_cursor(false); - } else if (path == "fidelity") { - this->fidelity = CLAMP(val.getDouble(), 0.0, 1.0); - } else if (path == "force") { - this->force = CLAMP(val.getDouble(1.0), 0, 1.0); - } else if (path == "usepressure") { - this->usepressure = val.getBool(); - } else if (path == "doh") { - this->do_h = val.getBool(); - } else if (path == "dos") { - this->do_s = val.getBool(); - } else if (path == "dol") { - this->do_l = val.getBool(); - } else if (path == "doo") { - this->do_o = val.getBool(); - } -} - -static void -sp_tweak_extinput(TweakTool *tc, GdkEvent *event) -{ - if (gdk_event_get_axis (event, GDK_AXIS_PRESSURE, &tc->pressure)) { - tc->pressure = CLAMP (tc->pressure, TC_MIN_PRESSURE, TC_MAX_PRESSURE); - } else { - tc->pressure = TC_DEFAULT_PRESSURE; - } -} - -static double -get_dilate_radius (TweakTool *tc) -{ - // 10 times the pen width: - return 500 * tc->width/SP_EVENT_CONTEXT(tc)->desktop->current_zoom(); -} - -static double -get_path_force (TweakTool *tc) -{ - double force = 8 * (tc->usepressure? tc->pressure : TC_DEFAULT_PRESSURE) - /sqrt(SP_EVENT_CONTEXT(tc)->desktop->current_zoom()); - if (force > 3) { - force += 4 * (force - 3); - } - return force * tc->force; -} - -static double -get_move_force (TweakTool *tc) -{ - double force = (tc->usepressure? tc->pressure : TC_DEFAULT_PRESSURE); - return force * tc->force; -} - -static bool -sp_tweak_dilate_recursive (Inkscape::Selection *selection, SPItem *item, Geom::Point p, Geom::Point vector, gint mode, double radius, double force, double fidelity, bool reverse) -{ - bool did = false; - - if (SP_IS_BOX3D(item) && !is_transform_mode(mode) && !is_color_mode(mode)) { - // convert 3D boxes to ordinary groups before tweaking their shapes - item = box3d_convert_to_group(SP_BOX3D(item)); - selection->add(item); - } - - if (SP_IS_TEXT(item) || SP_IS_FLOWTEXT(item)) { - GSList *items = g_slist_prepend (NULL, item); - GSList *selected = NULL; - GSList *to_select = NULL; - SPDocument *doc = item->document; - sp_item_list_to_curves (items, &selected, &to_select); - g_slist_free (items); - SPObject* newObj = doc->getObjectByRepr(static_cast(to_select->data)); - g_slist_free (to_select); - item = SP_ITEM(newObj); - selection->add(item); - } - - if (SP_IS_GROUP(item) && !SP_IS_BOX3D(item)) { - GSList *children = NULL; - for (SPObject *child = item->firstChild() ; child; child = child->getNext() ) { - if (SP_IS_ITEM(child)) { - children = g_slist_prepend(children, child); - } - } - - for (GSList *i = children; i; i = i->next) { - SPItem *child = SP_ITEM(i->data); - if (sp_tweak_dilate_recursive (selection, SP_ITEM(child), p, vector, mode, radius, force, fidelity, reverse)) - did = true; - } - - g_slist_free(children); - - } else { - if (mode == TWEAK_MODE_MOVE) { - - Geom::OptRect a = item->documentVisualBounds(); - if (a) { - double x = Geom::L2(a->midpoint() - p)/radius; - if (a->contains(p)) x = 0; - if (x < 1) { - Geom::Point move = force * 0.5 * (cos(M_PI * x) + 1) * vector; - sp_item_move_rel(item, Geom::Translate(move[Geom::X], -move[Geom::Y])); - did = true; - } - } - - } else if (mode == TWEAK_MODE_MOVE_IN_OUT) { - - Geom::OptRect a = item->documentVisualBounds(); - if (a) { - double x = Geom::L2(a->midpoint() - p)/radius; - if (a->contains(p)) x = 0; - if (x < 1) { - Geom::Point move = force * 0.5 * (cos(M_PI * x) + 1) * - (reverse? (a->midpoint() - p) : (p - a->midpoint())); - sp_item_move_rel(item, Geom::Translate(move[Geom::X], -move[Geom::Y])); - did = true; - } - } - - } else if (mode == TWEAK_MODE_MOVE_JITTER) { - - Geom::OptRect a = item->documentVisualBounds(); - if (a) { - double dp = g_random_double_range(0, M_PI*2); - double dr = g_random_double_range(0, radius); - double x = Geom::L2(a->midpoint() - p)/radius; - if (a->contains(p)) x = 0; - if (x < 1) { - Geom::Point move = force * 0.5 * (cos(M_PI * x) + 1) * Geom::Point(cos(dp)*dr, sin(dp)*dr); - sp_item_move_rel(item, Geom::Translate(move[Geom::X], -move[Geom::Y])); - did = true; - } - } - - } else if (mode == TWEAK_MODE_SCALE) { - - Geom::OptRect a = item->documentVisualBounds(); - if (a) { - double x = Geom::L2(a->midpoint() - p)/radius; - if (a->contains(p)) x = 0; - if (x < 1) { - double scale = 1 + (reverse? force : -force) * 0.05 * (cos(M_PI * x) + 1); - sp_item_scale_rel(item, Geom::Scale(scale, scale)); - did = true; - } - } - - } else if (mode == TWEAK_MODE_ROTATE) { - - Geom::OptRect a = item->documentVisualBounds(); - if (a) { - double x = Geom::L2(a->midpoint() - p)/radius; - if (a->contains(p)) x = 0; - if (x < 1) { - double angle = (reverse? force : -force) * 0.05 * (cos(M_PI * x) + 1) * M_PI; - sp_item_rotate_rel(item, Geom::Rotate(angle)); - did = true; - } - } - - } else if (mode == TWEAK_MODE_MORELESS) { - - Geom::OptRect a = item->documentVisualBounds(); - if (a) { - double x = Geom::L2(a->midpoint() - p)/radius; - if (a->contains(p)) x = 0; - if (x < 1) { - double prob = force * 0.5 * (cos(M_PI * x) + 1); - double chance = g_random_double_range(0, 1); - if (chance <= prob) { - if (reverse) { // delete - sp_object_ref(item, NULL); - item->deleteObject(true, true); - sp_object_unref(item, NULL); - } else { // duplicate - SPDocument *doc = item->document; - Inkscape::XML::Document* xml_doc = doc->getReprDoc(); - Inkscape::XML::Node *old_repr = item->getRepr(); - SPObject *old_obj = doc->getObjectByRepr(old_repr); - Inkscape::XML::Node *parent = old_repr->parent(); - Inkscape::XML::Node *copy = old_repr->duplicate(xml_doc); - parent->appendChild(copy); - SPObject *new_obj = doc->getObjectByRepr(copy); - if (selection->includes(old_obj)) { - selection->add(new_obj); - } - Inkscape::GC::release(copy); - } - did = true; - } - } - } - - } else if (SP_IS_PATH(item) || SP_IS_SHAPE(item)) { - - Inkscape::XML::Node *newrepr = NULL; - gint pos = 0; - Inkscape::XML::Node *parent = NULL; - char const *id = NULL; - if (!SP_IS_PATH(item)) { - newrepr = sp_selected_item_to_curved_repr(item, 0); - if (!newrepr) { - return false; - } - - // remember the position of the item - pos = item->getRepr()->position(); - // remember parent - parent = item->getRepr()->parent(); - // remember id - id = item->getRepr()->attribute("id"); - } - - // skip those paths whose bboxes are entirely out of reach with our radius - Geom::OptRect bbox = item->documentVisualBounds(); - if (bbox) { - bbox->expandBy(radius); - if (!bbox->contains(p)) { - return false; - } - } - - Path *orig = Path_for_item(item, false); - if (orig == NULL) { - return false; - } - - Path *res = new Path; - res->SetBackData(false); - - Shape *theShape = new Shape; - Shape *theRes = new Shape; - Geom::Affine i2doc(item->i2doc_affine()); - - orig->ConvertWithBackData((0.08 - (0.07 * fidelity)) / i2doc.descrim()); // default 0.059 - orig->Fill(theShape, 0); - - SPCSSAttr *css = sp_repr_css_attr(item->getRepr(), "style"); - gchar const *val = sp_repr_css_property(css, "fill-rule", NULL); - if (val && strcmp(val, "nonzero") == 0) { - theRes->ConvertToShape(theShape, fill_nonZero); - } else if (val && strcmp(val, "evenodd") == 0) { - theRes->ConvertToShape(theShape, fill_oddEven); - } else { - theRes->ConvertToShape(theShape, fill_nonZero); - } - - if (Geom::L2(vector) != 0) { - vector = 1/Geom::L2(vector) * vector; - } - - bool did_this = false; - if (mode == TWEAK_MODE_SHRINK_GROW) { - if (theShape->MakeTweak(tweak_mode_grow, theRes, - reverse? force : -force, - join_straight, 4.0, - true, p, Geom::Point(0,0), radius, &i2doc) == 0) // 0 means the shape was actually changed - did_this = true; - } else if (mode == TWEAK_MODE_ATTRACT_REPEL) { - if (theShape->MakeTweak(tweak_mode_repel, theRes, - reverse? force : -force, - join_straight, 4.0, - true, p, Geom::Point(0,0), radius, &i2doc) == 0) - did_this = true; - } else if (mode == TWEAK_MODE_PUSH) { - if (theShape->MakeTweak(tweak_mode_push, theRes, - 1.0, - join_straight, 4.0, - true, p, force*2*vector, radius, &i2doc) == 0) - did_this = true; - } else if (mode == TWEAK_MODE_ROUGHEN) { - if (theShape->MakeTweak(tweak_mode_roughen, theRes, - force, - join_straight, 4.0, - true, p, Geom::Point(0,0), radius, &i2doc) == 0) - did_this = true; - } - - // the rest only makes sense if we actually changed the path - if (did_this) { - theRes->ConvertToShape(theShape, fill_positive); - - res->Reset(); - theRes->ConvertToForme(res); - - double th_max = (0.6 - 0.59*sqrt(fidelity)) / i2doc.descrim(); - double threshold = MAX(th_max, th_max*force); - res->ConvertEvenLines(threshold); - res->Simplify(threshold / (selection->desktop()->current_zoom())); - - if (newrepr) { // converting to path, need to replace the repr - bool is_selected = selection->includes(item); - if (is_selected) { - selection->remove(item); - } - - // It's going to resurrect, so we delete without notifying listeners. - item->deleteObject(false); - - // restore id - newrepr->setAttribute("id", id); - // add the new repr to the parent - parent->appendChild(newrepr); - // move to the saved position - newrepr->setPosition(pos > 0 ? pos : 0); - - if (is_selected) - selection->add(newrepr); - } - - if (res->descr_cmd.size() > 1) { - gchar *str = res->svg_dump_path(); - if (newrepr) { - newrepr->setAttribute("d", str); - } else { - if (SP_IS_LPE_ITEM(item) && SP_LPE_ITEM(item)->hasPathEffectRecursive()) { - item->getRepr()->setAttribute("inkscape:original-d", str); - } else { - item->getRepr()->setAttribute("d", str); - } - } - g_free(str); - } else { - // TODO: if there's 0 or 1 node left, delete this path altogether - } - - if (newrepr) { - Inkscape::GC::release(newrepr); - newrepr = NULL; - } - } - - delete theShape; - delete theRes; - delete orig; - delete res; - - if (did_this) { - did = true; - } - } - - } - - return did; -} - -static void -tweak_colorpaint (float *color, guint32 goal, double force, bool do_h, bool do_s, bool do_l) -{ - float rgb_g[3]; - - if (!do_h || !do_s || !do_l) { - float hsl_g[3]; - sp_color_rgb_to_hsl_floatv (hsl_g, SP_RGBA32_R_F(goal), SP_RGBA32_G_F(goal), SP_RGBA32_B_F(goal)); - float hsl_c[3]; - sp_color_rgb_to_hsl_floatv (hsl_c, color[0], color[1], color[2]); - if (!do_h) { - hsl_g[0] = hsl_c[0]; - } - if (!do_s) { - hsl_g[1] = hsl_c[1]; - } - if (!do_l) { - hsl_g[2] = hsl_c[2]; - } - sp_color_hsl_to_rgb_floatv (rgb_g, hsl_g[0], hsl_g[1], hsl_g[2]); - } else { - rgb_g[0] = SP_RGBA32_R_F(goal); - rgb_g[1] = SP_RGBA32_G_F(goal); - rgb_g[2] = SP_RGBA32_B_F(goal); - } - - for (int i = 0; i < 3; i++) { - double d = rgb_g[i] - color[i]; - color[i] += d * force; - } -} - -static void -tweak_colorjitter (float *color, double force, bool do_h, bool do_s, bool do_l) -{ - float hsl_c[3]; - sp_color_rgb_to_hsl_floatv (hsl_c, color[0], color[1], color[2]); - - if (do_h) { - hsl_c[0] += g_random_double_range(-0.5, 0.5) * force; - if (hsl_c[0] > 1) { - hsl_c[0] -= 1; - } - if (hsl_c[0] < 0) { - hsl_c[0] += 1; - } - } - if (do_s) { - hsl_c[1] += g_random_double_range(-hsl_c[1], 1 - hsl_c[1]) * force; - } - if (do_l) { - hsl_c[2] += g_random_double_range(-hsl_c[2], 1 - hsl_c[2]) * force; - } - - sp_color_hsl_to_rgb_floatv (color, hsl_c[0], hsl_c[1], hsl_c[2]); -} - -static void -tweak_color (guint mode, float *color, guint32 goal, double force, bool do_h, bool do_s, bool do_l) -{ - if (mode == TWEAK_MODE_COLORPAINT) { - tweak_colorpaint (color, goal, force, do_h, do_s, do_l); - } else if (mode == TWEAK_MODE_COLORJITTER) { - tweak_colorjitter (color, force, do_h, do_s, do_l); - } -} - -static void -tweak_opacity (guint mode, SPIScale24 *style_opacity, double opacity_goal, double force) -{ - double opacity = SP_SCALE24_TO_FLOAT (style_opacity->value); - - if (mode == TWEAK_MODE_COLORPAINT) { - double d = opacity_goal - opacity; - opacity += d * force; - } else if (mode == TWEAK_MODE_COLORJITTER) { - opacity += g_random_double_range(-opacity, 1 - opacity) * force; - } - - style_opacity->value = SP_SCALE24_FROM_FLOAT(opacity); -} - - -static double -tweak_profile (double dist, double radius) -{ - if (radius == 0) { - return 0; - } - double x = dist / radius; - double alpha = 1; - if (x >= 1) { - return 0; - } else if (x <= 0) { - return 1; - } else { - return (0.5 * cos (M_PI * (pow(x, alpha))) + 0.5); - } -} - -static void tweak_colors_in_gradient(SPItem *item, Inkscape::PaintTarget fill_or_stroke, - guint32 const rgb_goal, Geom::Point p_w, double radius, double force, guint mode, - bool do_h, bool do_s, bool do_l, bool /*do_o*/) -{ - SPGradient *gradient = getGradient(item, fill_or_stroke); - - if (!gradient || !SP_IS_GRADIENT(gradient)) { - return; - } - - Geom::Affine i2d (item->i2doc_affine ()); - Geom::Point p = p_w * i2d.inverse(); - p *= (gradient->gradientTransform).inverse(); - // now p is in gradient's original coordinates - - double pos = 0; - double r = 0; - if (SP_IS_LINEARGRADIENT(gradient)) { - SPLinearGradient *lg = SP_LINEARGRADIENT(gradient); - - Geom::Point p1(lg->x1.computed, lg->y1.computed); - Geom::Point p2(lg->x2.computed, lg->y2.computed); - Geom::Point pdiff(p2 - p1); - double vl = Geom::L2(pdiff); - - // This is the matrix which moves and rotates the gradient line - // so it's oriented along the X axis: - Geom::Affine norm = Geom::Affine(Geom::Translate(-p1)) * Geom::Affine(Geom::Rotate(-atan2(pdiff[Geom::Y], pdiff[Geom::X]))); - - // Transform the mouse point by it to find out its projection onto the gradient line: - Geom::Point pnorm = p * norm; - - // Scale its X coordinate to match the length of the gradient line: - pos = pnorm[Geom::X] / vl; - // Calculate radius in lenfth-of-gradient-line units - r = radius / vl; - - } else if (SP_IS_RADIALGRADIENT(gradient)) { - SPRadialGradient *rg = SP_RADIALGRADIENT(gradient); - Geom::Point c (rg->cx.computed, rg->cy.computed); - pos = Geom::L2(p - c) / rg->r.computed; - r = radius / rg->r.computed; - } - - // Normalize pos to 0..1, taking into accound gradient spread: - double pos_e = pos; - if (gradient->getSpread() == SP_GRADIENT_SPREAD_PAD) { - if (pos > 1) { - pos_e = 1; - } - if (pos < 0) { - pos_e = 0; - } - } else if (gradient->getSpread() == SP_GRADIENT_SPREAD_REPEAT) { - if (pos > 1 || pos < 0) { - pos_e = pos - floor(pos); - } - } else if (gradient->getSpread() == SP_GRADIENT_SPREAD_REFLECT) { - if (pos > 1 || pos < 0) { - bool odd = ((int)(floor(pos)) % 2 == 1); - pos_e = pos - floor(pos); - if (odd) { - pos_e = 1 - pos_e; - } - } - } - - SPGradient *vector = sp_gradient_get_forked_vector_if_necessary(gradient, false); - - double offset_l = 0; - double offset_h = 0; - SPObject *child_prev = NULL; - for (SPObject *child = vector->firstChild(); child; child = child->getNext()) { - if (!SP_IS_STOP(child)) { - continue; - } - SPStop *stop = SP_STOP (child); - - offset_h = stop->offset; - - if (child_prev) { - - if (offset_h - offset_l > r && pos_e >= offset_l && pos_e <= offset_h) { - // the summit falls in this interstop, and the radius is small, - // so it only affects the ends of this interstop; - // distribute the force between the two endstops so that they - // get all the painting even if they are not touched by the brush - tweak_color (mode, stop->specified_color.v.c, rgb_goal, - force * (pos_e - offset_l) / (offset_h - offset_l), - do_h, do_s, do_l); - tweak_color (mode, SP_STOP(child_prev)->specified_color.v.c, rgb_goal, - force * (offset_h - pos_e) / (offset_h - offset_l), - do_h, do_s, do_l); - stop->updateRepr(); - child_prev->updateRepr(); - break; - } else { - // wide brush, may affect more than 2 stops, - // paint each stop by the force from the profile curve - if (offset_l <= pos_e && offset_l > pos_e - r) { - tweak_color (mode, SP_STOP(child_prev)->specified_color.v.c, rgb_goal, - force * tweak_profile (fabs (pos_e - offset_l), r), - do_h, do_s, do_l); - child_prev->updateRepr(); - } - - if (offset_h >= pos_e && offset_h < pos_e + r) { - tweak_color (mode, stop->specified_color.v.c, rgb_goal, - force * tweak_profile (fabs (pos_e - offset_h), r), - do_h, do_s, do_l); - stop->updateRepr(); - } - } - } - - offset_l = offset_h; - child_prev = child; - } -} - -static bool -sp_tweak_color_recursive (guint mode, SPItem *item, SPItem *item_at_point, - guint32 fill_goal, bool do_fill, - guint32 stroke_goal, bool do_stroke, - float opacity_goal, bool do_opacity, - bool do_blur, bool reverse, - Geom::Point p, double radius, double force, - bool do_h, bool do_s, bool do_l, bool do_o) -{ - bool did = false; - - if (SP_IS_GROUP(item)) { - for (SPObject *child = item->firstChild() ; child; child = child->getNext() ) { - if (SP_IS_ITEM(child)) { - if (sp_tweak_color_recursive (mode, SP_ITEM(child), item_at_point, - fill_goal, do_fill, - stroke_goal, do_stroke, - opacity_goal, do_opacity, - do_blur, reverse, - p, radius, force, do_h, do_s, do_l, do_o)) { - did = true; - } - } - } - - } else { - SPStyle *style = item->style; - if (!style) { - return false; - } - Geom::OptRect bbox = item->documentGeometricBounds(); - if (!bbox) { - return false; - } - - Geom::Rect brush(p - Geom::Point(radius, radius), p + Geom::Point(radius, radius)); - - Geom::Point center = bbox->midpoint(); - double this_force; - -// if item == item_at_point, use max force - if (item == item_at_point) { - this_force = force; -// else if no overlap of bbox and brush box, skip: - } else if (!bbox->intersects(brush)) { - return false; -//TODO: -// else if object > 1.5 brush: test 4/8/16 points in the brush on hitting the object, choose max - //} else if (bbox->maxExtent() > 3 * radius) { - //} -// else if object > 0.5 brush: test 4 corners of bbox and center on being in the brush, choose max -// else if still smaller, then check only the object center: - } else { - this_force = force * tweak_profile (Geom::L2 (p - center), radius); - } - - if (this_force > 0.002) { - - if (do_blur) { - Geom::OptRect bbox = item->documentGeometricBounds(); - if (!bbox) { - return did; - } - - double blur_now = 0; - Geom::Affine i2dt = item->i2dt_affine (); - if (style->filter.set && style->getFilter()) { - //cycle through filter primitives - SPObject *primitive_obj = style->getFilter()->children; - while (primitive_obj) { - if (SP_IS_FILTER_PRIMITIVE(primitive_obj)) { - SPFilterPrimitive *primitive = SP_FILTER_PRIMITIVE(primitive_obj); - //if primitive is gaussianblur - if(SP_IS_GAUSSIANBLUR(primitive)) { - SPGaussianBlur * spblur = SP_GAUSSIANBLUR(primitive); - float num = spblur->stdDeviation.getNumber(); - blur_now += num * i2dt.descrim(); // sum all blurs in the filter - } - } - primitive_obj = primitive_obj->next; - } - } - double perimeter = bbox->dimensions()[Geom::X] + bbox->dimensions()[Geom::Y]; - blur_now = blur_now / perimeter; - - double blur_new; - if (reverse) { - blur_new = blur_now - 0.06 * force; - } else { - blur_new = blur_now + 0.06 * force; - } - if (blur_new < 0.0005 && blur_new < blur_now) { - blur_new = 0; - } - if (blur_new == 0) { - remove_filter(item, false); - } else { - double radius = blur_new * perimeter; - SPFilter *filter = modify_filter_gaussian_blur_from_item(item->document, item, radius); - sp_style_set_property_url(item, "filter", filter, false); - } - return true; // do not do colors, blur is a separate mode - } - - if (do_fill) { - if (style->fill.isPaintserver()) { - tweak_colors_in_gradient(item, Inkscape::FOR_FILL, fill_goal, p, radius, this_force, mode, do_h, do_s, do_l, do_o); - did = true; - } else if (style->fill.isColor()) { - tweak_color (mode, style->fill.value.color.v.c, fill_goal, this_force, do_h, do_s, do_l); - item->updateRepr(); - did = true; - } - } - if (do_stroke) { - if (style->stroke.isPaintserver()) { - tweak_colors_in_gradient(item, Inkscape::FOR_STROKE, stroke_goal, p, radius, this_force, mode, do_h, do_s, do_l, do_o); - did = true; - } else if (style->stroke.isColor()) { - tweak_color (mode, style->stroke.value.color.v.c, stroke_goal, this_force, do_h, do_s, do_l); - item->updateRepr(); - did = true; - } - } - if (do_opacity && do_o) { - tweak_opacity (mode, &style->opacity, opacity_goal, this_force); - } - } - } - - return did; -} - - -static bool -sp_tweak_dilate (TweakTool *tc, Geom::Point event_p, Geom::Point p, Geom::Point vector, bool reverse) -{ - Inkscape::Selection *selection = sp_desktop_selection(SP_EVENT_CONTEXT(tc)->desktop); - SPDesktop *desktop = SP_EVENT_CONTEXT(tc)->desktop; - - if (selection->isEmpty()) { - return false; - } - - bool did = false; - double radius = get_dilate_radius(tc); - - SPItem *item_at_point = SP_EVENT_CONTEXT(tc)->desktop->getItemAtPoint(event_p, TRUE); - - bool do_fill = false, do_stroke = false, do_opacity = false; - guint32 fill_goal = sp_desktop_get_color_tool(desktop, "/tools/tweak", true, &do_fill); - guint32 stroke_goal = sp_desktop_get_color_tool(desktop, "/tools/tweak", false, &do_stroke); - double opacity_goal = sp_desktop_get_master_opacity_tool(desktop, "/tools/tweak", &do_opacity); - if (reverse) { -#if 0 - // HSL inversion - float hsv[3]; - float rgb[3]; - sp_color_rgb_to_hsv_floatv (hsv, - SP_RGBA32_R_F(fill_goal), - SP_RGBA32_G_F(fill_goal), - SP_RGBA32_B_F(fill_goal)); - sp_color_hsv_to_rgb_floatv (rgb, hsv[0]<.5? hsv[0]+.5 : hsv[0]-.5, 1 - hsv[1], 1 - hsv[2]); - fill_goal = SP_RGBA32_F_COMPOSE(rgb[0], rgb[1], rgb[2], 1); - sp_color_rgb_to_hsv_floatv (hsv, - SP_RGBA32_R_F(stroke_goal), - SP_RGBA32_G_F(stroke_goal), - SP_RGBA32_B_F(stroke_goal)); - sp_color_hsv_to_rgb_floatv (rgb, hsv[0]<.5? hsv[0]+.5 : hsv[0]-.5, 1 - hsv[1], 1 - hsv[2]); - stroke_goal = SP_RGBA32_F_COMPOSE(rgb[0], rgb[1], rgb[2], 1); -#else - // RGB inversion - fill_goal = SP_RGBA32_U_COMPOSE( - (255 - SP_RGBA32_R_U(fill_goal)), - (255 - SP_RGBA32_G_U(fill_goal)), - (255 - SP_RGBA32_B_U(fill_goal)), - (255 - SP_RGBA32_A_U(fill_goal))); - stroke_goal = SP_RGBA32_U_COMPOSE( - (255 - SP_RGBA32_R_U(stroke_goal)), - (255 - SP_RGBA32_G_U(stroke_goal)), - (255 - SP_RGBA32_B_U(stroke_goal)), - (255 - SP_RGBA32_A_U(stroke_goal))); -#endif - opacity_goal = 1 - opacity_goal; - } - - double path_force = get_path_force(tc); - if (radius == 0 || path_force == 0) { - return false; - } - double move_force = get_move_force(tc); - double color_force = MIN(sqrt(path_force)/20.0, 1); - - for (GSList *items = g_slist_copy(const_cast(selection->itemList())); - items != NULL; - items = items->next) { - - SPItem *item = SP_ITEM(items->data); - - if (is_color_mode (tc->mode)) { - if (do_fill || do_stroke || do_opacity) { - if (sp_tweak_color_recursive (tc->mode, item, item_at_point, - fill_goal, do_fill, - stroke_goal, do_stroke, - opacity_goal, do_opacity, - tc->mode == TWEAK_MODE_BLUR, reverse, - p, radius, color_force, tc->do_h, tc->do_s, tc->do_l, tc->do_o)) { - did = true; - } - } - } else if (is_transform_mode(tc->mode)) { - if (sp_tweak_dilate_recursive (selection, item, p, vector, tc->mode, radius, move_force, tc->fidelity, reverse)) { - did = true; - } - } else { - if (sp_tweak_dilate_recursive (selection, item, p, vector, tc->mode, radius, path_force, tc->fidelity, reverse)) { - did = true; - } - } - } - - return did; -} - -static void -sp_tweak_update_area (TweakTool *tc) -{ - double radius = get_dilate_radius(tc); - Geom::Affine const sm (Geom::Scale(radius, radius) * Geom::Translate(SP_EVENT_CONTEXT(tc)->desktop->point())); - sp_canvas_item_affine_absolute(tc->dilate_area, sm); - sp_canvas_item_show(tc->dilate_area); -} - -static void -sp_tweak_switch_mode (TweakTool *tc, gint mode, bool with_shift) -{ - SP_EVENT_CONTEXT(tc)->desktop->setToolboxSelectOneValue ("tweak_tool_mode", mode); - // need to set explicitly, because the prefs may not have changed by the previous - tc->mode = mode; - tc->update_cursor(with_shift); -} - -static void -sp_tweak_switch_mode_temporarily (TweakTool *tc, gint mode, bool with_shift) -{ - Inkscape::Preferences *prefs = Inkscape::Preferences::get(); - // Juggling about so that prefs have the old value but tc->mode and the button show new mode: - gint now_mode = prefs->getInt("/tools/tweak/mode", 0); - SP_EVENT_CONTEXT(tc)->desktop->setToolboxSelectOneValue ("tweak_tool_mode", mode); - // button has changed prefs, restore - prefs->setInt("/tools/tweak/mode", now_mode); - // changing prefs changed tc->mode, restore back :) - tc->mode = mode; - tc->update_cursor(with_shift); -} - -bool TweakTool::root_handler(GdkEvent* event) { - gint ret = FALSE; - - switch (event->type) { - case GDK_ENTER_NOTIFY: - sp_canvas_item_show(this->dilate_area); - break; - case GDK_LEAVE_NOTIFY: - sp_canvas_item_hide(this->dilate_area); - break; - case GDK_BUTTON_PRESS: - if (event->button.button == 1 && !this->space_panning) { - - if (Inkscape::have_viable_layer(desktop, this->message_context) == false) { - return TRUE; - } - - Geom::Point const button_w(event->button.x, - event->button.y); - Geom::Point const button_dt(desktop->w2d(button_w)); - this->last_push = desktop->dt2doc(button_dt); - - sp_tweak_extinput(this, event); - - desktop->canvas->forceFullRedrawAfterInterruptions(3); - this->is_drawing = true; - this->is_dilating = true; - this->has_dilated = false; - - ret = TRUE; - } - break; - case GDK_MOTION_NOTIFY: - { - Geom::Point const motion_w(event->motion.x, - event->motion.y); - Geom::Point motion_dt(desktop->w2d(motion_w)); - Geom::Point motion_doc(desktop->dt2doc(motion_dt)); - sp_tweak_extinput(this, event); - - // draw the dilating cursor - double radius = get_dilate_radius(this); - Geom::Affine const sm (Geom::Scale(radius, radius) * Geom::Translate(desktop->w2d(motion_w))); - sp_canvas_item_affine_absolute(this->dilate_area, sm); - sp_canvas_item_show(this->dilate_area); - - guint num = 0; - if (!desktop->selection->isEmpty()) { - num = g_slist_length(const_cast(desktop->selection->itemList())); - } - if (num == 0) { - this->message_context->flash(Inkscape::ERROR_MESSAGE, _("Nothing selected! Select objects to tweak.")); - } - - // dilating: - if (this->is_drawing && ( event->motion.state & GDK_BUTTON1_MASK )) { - sp_tweak_dilate (this, motion_w, motion_doc, motion_doc - this->last_push, event->button.state & GDK_SHIFT_MASK? true : false); - //this->last_push = motion_doc; - this->has_dilated = true; - // it's slow, so prevent clogging up with events - gobble_motion_events(GDK_BUTTON1_MASK); - return TRUE; - } - - } - break; - case GDK_BUTTON_RELEASE: - { - Geom::Point const motion_w(event->button.x, event->button.y); - Geom::Point const motion_dt(desktop->w2d(motion_w)); - - desktop->canvas->endForcedFullRedraws(); - this->is_drawing = false; - - if (this->is_dilating && event->button.button == 1 && !this->space_panning) { - if (!this->has_dilated) { - // if we did not rub, do a light tap - this->pressure = 0.03; - sp_tweak_dilate (this, motion_w, desktop->dt2doc(motion_dt), Geom::Point(0,0), MOD__SHIFT(event)); - } - this->is_dilating = false; - this->has_dilated = false; - switch (this->mode) { - case TWEAK_MODE_MOVE: - DocumentUndo::done(sp_desktop_document(SP_EVENT_CONTEXT(this)->desktop), - SP_VERB_CONTEXT_TWEAK, _("Move tweak")); - break; - case TWEAK_MODE_MOVE_IN_OUT: - DocumentUndo::done(sp_desktop_document(SP_EVENT_CONTEXT(this)->desktop), - SP_VERB_CONTEXT_TWEAK, _("Move in/out tweak")); - break; - case TWEAK_MODE_MOVE_JITTER: - DocumentUndo::done(sp_desktop_document(SP_EVENT_CONTEXT(this)->desktop), - SP_VERB_CONTEXT_TWEAK, _("Move jitter tweak")); - break; - case TWEAK_MODE_SCALE: - DocumentUndo::done(sp_desktop_document(SP_EVENT_CONTEXT(this)->desktop), - SP_VERB_CONTEXT_TWEAK, _("Scale tweak")); - break; - case TWEAK_MODE_ROTATE: - DocumentUndo::done(sp_desktop_document(SP_EVENT_CONTEXT(this)->desktop), - SP_VERB_CONTEXT_TWEAK, _("Rotate tweak")); - break; - case TWEAK_MODE_MORELESS: - DocumentUndo::done(sp_desktop_document(SP_EVENT_CONTEXT(this)->desktop), - SP_VERB_CONTEXT_TWEAK, _("Duplicate/delete tweak")); - break; - case TWEAK_MODE_PUSH: - DocumentUndo::done(sp_desktop_document(SP_EVENT_CONTEXT(this)->desktop), - SP_VERB_CONTEXT_TWEAK, _("Push path tweak")); - break; - case TWEAK_MODE_SHRINK_GROW: - DocumentUndo::done(sp_desktop_document(SP_EVENT_CONTEXT(this)->desktop), - SP_VERB_CONTEXT_TWEAK, _("Shrink/grow path tweak")); - break; - case TWEAK_MODE_ATTRACT_REPEL: - DocumentUndo::done(sp_desktop_document(SP_EVENT_CONTEXT(this)->desktop), - SP_VERB_CONTEXT_TWEAK, _("Attract/repel path tweak")); - break; - case TWEAK_MODE_ROUGHEN: - DocumentUndo::done(sp_desktop_document(SP_EVENT_CONTEXT(this)->desktop), - SP_VERB_CONTEXT_TWEAK, _("Roughen path tweak")); - break; - case TWEAK_MODE_COLORPAINT: - DocumentUndo::done(sp_desktop_document(SP_EVENT_CONTEXT(this)->desktop), - SP_VERB_CONTEXT_TWEAK, _("Color paint tweak")); - break; - case TWEAK_MODE_COLORJITTER: - DocumentUndo::done(sp_desktop_document(SP_EVENT_CONTEXT(this)->desktop), - SP_VERB_CONTEXT_TWEAK, _("Color jitter tweak")); - break; - case TWEAK_MODE_BLUR: - DocumentUndo::done(sp_desktop_document(SP_EVENT_CONTEXT(this)->desktop), - SP_VERB_CONTEXT_TWEAK, _("Blur tweak")); - break; - } - } - break; - } - case GDK_KEY_PRESS: - { - switch (get_group0_keyval (&event->key)) { - case GDK_KEY_m: - case GDK_KEY_M: - case GDK_KEY_0: - if (MOD__SHIFT_ONLY(event)) { - sp_tweak_switch_mode(this, TWEAK_MODE_MOVE, MOD__SHIFT(event)); - ret = TRUE; - } - break; - case GDK_KEY_i: - case GDK_KEY_I: - case GDK_KEY_1: - if (MOD__SHIFT_ONLY(event)) { - sp_tweak_switch_mode(this, TWEAK_MODE_MOVE_IN_OUT, MOD__SHIFT(event)); - ret = TRUE; - } - break; - case GDK_KEY_z: - case GDK_KEY_Z: - case GDK_KEY_2: - if (MOD__SHIFT_ONLY(event)) { - sp_tweak_switch_mode(this, TWEAK_MODE_MOVE_JITTER, MOD__SHIFT(event)); - ret = TRUE; - } - break; - case GDK_KEY_less: - case GDK_KEY_comma: - case GDK_KEY_greater: - case GDK_KEY_period: - case GDK_KEY_3: - if (MOD__SHIFT_ONLY(event)) { - sp_tweak_switch_mode(this, TWEAK_MODE_SCALE, MOD__SHIFT(event)); - ret = TRUE; - } - break; - case GDK_KEY_bracketright: - case GDK_KEY_bracketleft: - case GDK_KEY_4: - if (MOD__SHIFT_ONLY(event)) { - sp_tweak_switch_mode(this, TWEAK_MODE_ROTATE, MOD__SHIFT(event)); - ret = TRUE; - } - break; - case GDK_KEY_d: - case GDK_KEY_D: - case GDK_KEY_5: - if (MOD__SHIFT_ONLY(event)) { - sp_tweak_switch_mode(this, TWEAK_MODE_MORELESS, MOD__SHIFT(event)); - ret = TRUE; - } - break; - case GDK_KEY_p: - case GDK_KEY_P: - case GDK_KEY_6: - if (MOD__SHIFT_ONLY(event)) { - sp_tweak_switch_mode(this, TWEAK_MODE_PUSH, MOD__SHIFT(event)); - ret = TRUE; - } - break; - case GDK_KEY_s: - case GDK_KEY_S: - case GDK_KEY_7: - if (MOD__SHIFT_ONLY(event)) { - sp_tweak_switch_mode(this, TWEAK_MODE_SHRINK_GROW, MOD__SHIFT(event)); - ret = TRUE; - } - break; - case GDK_KEY_a: - case GDK_KEY_A: - case GDK_KEY_8: - if (MOD__SHIFT_ONLY(event)) { - sp_tweak_switch_mode(this, TWEAK_MODE_ATTRACT_REPEL, MOD__SHIFT(event)); - ret = TRUE; - } - break; - case GDK_KEY_r: - case GDK_KEY_R: - case GDK_KEY_9: - if (MOD__SHIFT_ONLY(event)) { - sp_tweak_switch_mode(this, TWEAK_MODE_ROUGHEN, MOD__SHIFT(event)); - ret = TRUE; - } - break; - case GDK_KEY_c: - case GDK_KEY_C: - if (MOD__SHIFT_ONLY(event)) { - sp_tweak_switch_mode(this, TWEAK_MODE_COLORPAINT, MOD__SHIFT(event)); - ret = TRUE; - } - break; - case GDK_KEY_j: - case GDK_KEY_J: - if (MOD__SHIFT_ONLY(event)) { - sp_tweak_switch_mode(this, TWEAK_MODE_COLORJITTER, MOD__SHIFT(event)); - ret = TRUE; - } - break; - case GDK_KEY_b: - case GDK_KEY_B: - if (MOD__SHIFT_ONLY(event)) { - sp_tweak_switch_mode(this, TWEAK_MODE_BLUR, MOD__SHIFT(event)); - ret = TRUE; - } - break; - - case GDK_KEY_Up: - case GDK_KEY_KP_Up: - if (!MOD__CTRL_ONLY(event)) { - this->force += 0.05; - if (this->force > 1.0) { - this->force = 1.0; - } - desktop->setToolboxAdjustmentValue ("tweak-force", this->force * 100); - ret = TRUE; - } - break; - case GDK_KEY_Down: - case GDK_KEY_KP_Down: - if (!MOD__CTRL_ONLY(event)) { - this->force -= 0.05; - if (this->force < 0.0) { - this->force = 0.0; - } - desktop->setToolboxAdjustmentValue ("tweak-force", this->force * 100); - ret = TRUE; - } - break; - case GDK_KEY_Right: - case GDK_KEY_KP_Right: - if (!MOD__CTRL_ONLY(event)) { - this->width += 0.01; - if (this->width > 1.0) { - this->width = 1.0; - } - desktop->setToolboxAdjustmentValue ("altx-tweak", this->width * 100); // the same spinbutton is for alt+x - sp_tweak_update_area(this); - ret = TRUE; - } - break; - case GDK_KEY_Left: - case GDK_KEY_KP_Left: - if (!MOD__CTRL_ONLY(event)) { - this->width -= 0.01; - if (this->width < 0.01) { - this->width = 0.01; - } - desktop->setToolboxAdjustmentValue ("altx-tweak", this->width * 100); - sp_tweak_update_area(this); - ret = TRUE; - } - break; - case GDK_KEY_Home: - case GDK_KEY_KP_Home: - this->width = 0.01; - desktop->setToolboxAdjustmentValue ("altx-tweak", this->width * 100); - sp_tweak_update_area(this); - ret = TRUE; - break; - case GDK_KEY_End: - case GDK_KEY_KP_End: - this->width = 1.0; - desktop->setToolboxAdjustmentValue ("altx-tweak", this->width * 100); - sp_tweak_update_area(this); - ret = TRUE; - break; - case GDK_KEY_x: - case GDK_KEY_X: - if (MOD__ALT_ONLY(event)) { - desktop->setToolboxFocusTo ("altx-tweak"); - ret = TRUE; - } - break; - - case GDK_KEY_Shift_L: - case GDK_KEY_Shift_R: - this->update_cursor(true); - break; - - case GDK_KEY_Control_L: - case GDK_KEY_Control_R: - sp_tweak_switch_mode_temporarily(this, TWEAK_MODE_SHRINK_GROW, MOD__SHIFT(event)); - break; - case GDK_KEY_Delete: - case GDK_KEY_KP_Delete: - case GDK_KEY_BackSpace: - ret = this->deleteSelectedDrag(MOD__CTRL_ONLY(event)); - break; - - default: - break; - } - break; - } - case GDK_KEY_RELEASE: { - Inkscape::Preferences *prefs = Inkscape::Preferences::get(); - switch (get_group0_keyval(&event->key)) { - case GDK_KEY_Shift_L: - case GDK_KEY_Shift_R: - this->update_cursor(false); - break; - case GDK_KEY_Control_L: - case GDK_KEY_Control_R: - sp_tweak_switch_mode (this, prefs->getInt("/tools/tweak/mode"), MOD__SHIFT(event)); - this->message_context->clear(); - break; - default: - sp_tweak_switch_mode (this, prefs->getInt("/tools/tweak/mode"), MOD__SHIFT(event)); - break; - } - } - default: - break; - } - - if (!ret) { - ret = ToolBase::root_handler(event); - } - - return ret; -} - -} -} -} - -/* - Local Variables: - mode:c++ - c-file-style:"stroustrup" - c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) - indent-tabs-mode:nil - fill-column:99 - End: -*/ -// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/tweak-context.h b/src/tweak-context.h deleted file mode 100644 index 80e2c75b9..000000000 --- a/src/tweak-context.h +++ /dev/null @@ -1,106 +0,0 @@ -#ifndef __SP_TWEAK_CONTEXT_H__ -#define __SP_TWEAK_CONTEXT_H__ - -/* - * tweaking paths without node editing - * - * Authors: - * bulia byak - * - * Copyright (C) 2007 authors - * - * Released under GNU GPL, read the file 'COPYING' for more information - */ - -#include "event-context.h" -#include <2geom/point.h> - -#define SAMPLING_SIZE 8 /* fixme: ?? */ - -#define TC_MIN_PRESSURE 0.0 -#define TC_MAX_PRESSURE 1.0 -#define TC_DEFAULT_PRESSURE 0.35 - -namespace Inkscape { -namespace UI { -namespace Tools { - -enum { - TWEAK_MODE_MOVE, - TWEAK_MODE_MOVE_IN_OUT, - TWEAK_MODE_MOVE_JITTER, - TWEAK_MODE_SCALE, - TWEAK_MODE_ROTATE, - TWEAK_MODE_MORELESS, - TWEAK_MODE_PUSH, - TWEAK_MODE_SHRINK_GROW, - TWEAK_MODE_ATTRACT_REPEL, - TWEAK_MODE_ROUGHEN, - TWEAK_MODE_COLORPAINT, - TWEAK_MODE_COLORJITTER, - TWEAK_MODE_BLUR -}; - -class TweakTool : public ToolBase { -public: - TweakTool(); - virtual ~TweakTool(); - - /* extended input data */ - gdouble pressure; - - /* attributes */ - guint dragging : 1; /* mouse state: mouse is dragging */ - guint usepressure : 1; - guint usetilt : 1; - - double width; - double force; - double fidelity; - - gint mode; - - bool is_drawing; - - bool is_dilating; - bool has_dilated; - Geom::Point last_push; - SPCanvasItem *dilate_area; - - bool do_h; - bool do_s; - bool do_l; - bool do_o; - - sigc::connection style_set_connection; - - static const std::string prefsPath; - - virtual void setup(); - virtual void set(const Inkscape::Preferences::Entry& val); - virtual bool root_handler(GdkEvent* event); - - virtual const std::string& getPrefsPath(); - - void update_cursor(bool with_shift); - -private: - bool set_style(const SPCSSAttr* css); -}; - -} -} -} - -#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:fileencoding=utf-8:textwidth=99 : diff --git a/src/ui/CMakeLists.txt b/src/ui/CMakeLists.txt index 24324c874..c3e406e3f 100644 --- a/src/ui/CMakeLists.txt +++ b/src/ui/CMakeLists.txt @@ -14,13 +14,38 @@ set(ui_SRC tool/manipulator.cpp tool/modifier-tracker.cpp tool/multi-path-manipulator.cpp - tool/node-tool.cpp tool/node.cpp tool/path-manipulator.cpp tool/selectable-control-point.cpp tool/selector.cpp tool/transform-handle-set.cpp + tools/arc-tool.cpp + tools/box3d-tool.cpp + tools/calligraphic-tool.cpp + tools/connector-tool.cpp + tools/dropper-tool.cpp + tools/dynamic-base.cpp + tools/eraser-tool.cpp + tools/flood-tool.cpp + tools/freehand-base.cpp + tools/gradient-tool.cpp + tools/lpe-tool.cpp + tools/measure-tool.cpp + tools/mesh-tool.cpp + tools/node-tool.cpp + tools/pencil-tool.cpp + tools/pen-tool.cpp + tools/rect-tool.cpp + tools/select-tool.cpp + tools/spiral-tool.cpp + tools/spray-tool.cpp + tools/star-tool.cpp + tools/text-tool.cpp + tools/tool-base.cpp + tools/tweak-tool.cpp + tools/zoom-tool.cpp + dialog/aboutbox.cpp dialog/align-and-distribute.cpp dialog/calligraphic-profile-rename.cpp @@ -200,7 +225,6 @@ set(ui_SRC tool/manipulator.h tool/modifier-tracker.h tool/multi-path-manipulator.h - tool/node-tool.h tool/node-types.h tool/node.h tool/path-manipulator.h @@ -209,6 +233,32 @@ set(ui_SRC tool/shape-record.h tool/transform-handle-set.h + tools/arc-tool.h + tools/box3d-tool.h + tools/calligraphic-tool.h + tools/connector-tool.h + tools/dropper-tool.h + tools/dynamic-base.h + tools/eraser-tool.h + tools/flood-tool.h + tools/freehand-base.h + tools/gradient-tool.h + tools/lpe-tool.h + tools/measure-tool.h + tools/mesh-tool.h + tools/node-tool.h + tools/pencil-tool.h + tools/pen-tool.h + tools/rect-tool.h + tools/select-tool.h + tools/spiral-tool.h + tools/spray-tool.h + tools/star-tool.h + tools/text-tool.h + tools/tool-base.h + tools/tweak-tool.h + tools/zoom-tool.h + widget/attr-widget.h widget/button.h widget/color-picker.h diff --git a/src/ui/clipboard.cpp b/src/ui/clipboard.cpp index ffb2bdf8c..8e2502545 100644 --- a/src/ui/clipboard.cpp +++ b/src/ui/clipboard.cpp @@ -44,7 +44,7 @@ #include "selection.h" #include "message-stack.h" #include "context-fns.h" -#include "dropper-context.h" // used in copy() +#include "ui/tools/dropper-tool.h" // used in copy() #include "style.h" #include "extension/db.h" // extension database #include "extension/input.h" @@ -74,7 +74,7 @@ #include "live_effects/parameter/path.h" #include "svg/svg.h" // for sp_svg_transform_write, used in _copySelection #include "svg/css-ostringstream.h" // used in copy -#include "text-context.h" +#include "ui/tools/text-tool.h" #include "text-editing.h" #include "tools-switch.h" #include "path-chemistry.h" diff --git a/src/ui/dialog/align-and-distribute.cpp b/src/ui/dialog/align-and-distribute.cpp index 4a3852d5d..6b4aeebb9 100644 --- a/src/ui/dialog/align-and-distribute.cpp +++ b/src/ui/dialog/align-and-distribute.cpp @@ -40,7 +40,7 @@ #include "text-editing.h" #include "tools-switch.h" #include "ui/icon-names.h" -#include "ui/tool/node-tool.h" +#include "ui/tools/node-tool.h" #include "ui/tool/multi-path-manipulator.h" #include "util/glib-list-iterators.h" #include "verbs.h" diff --git a/src/ui/dialog/dialog.cpp b/src/ui/dialog/dialog.cpp index 7fc9c3889..3cc3f3d88 100644 --- a/src/ui/dialog/dialog.cpp +++ b/src/ui/dialog/dialog.cpp @@ -23,7 +23,7 @@ #include #include "inkscape.h" -#include "event-context.h" +#include "ui/tools/tool-base.h" #include "desktop.h" #include "desktop-handles.h" #include "modifier-fns.h" diff --git a/src/ui/dialog/guides.cpp b/src/ui/dialog/guides.cpp index 5dfafa78d..e84f25733 100644 --- a/src/ui/dialog/guides.cpp +++ b/src/ui/dialog/guides.cpp @@ -25,7 +25,7 @@ #include "sp-guide.h" #include "sp-namedview.h" #include "desktop-handles.h" -#include "event-context.h" +#include "ui/tools/tool-base.h" #include "widgets/desktop-widget.h" #include #include "dialogs/dialog-events.h" diff --git a/src/ui/dialog/layer-properties.cpp b/src/ui/dialog/layer-properties.cpp index c726f93af..f19828b1f 100644 --- a/src/ui/dialog/layer-properties.cpp +++ b/src/ui/dialog/layer-properties.cpp @@ -31,7 +31,7 @@ #include "selection-chemistry.h" #include "ui/icon-names.h" #include "ui/widget/imagetoggler.h" -#include "event-context.h" +#include "ui/tools/tool-base.h" namespace Inkscape { namespace UI { diff --git a/src/ui/dialog/layers.cpp b/src/ui/dialog/layers.cpp index ff9265c8d..12fdf6b7d 100644 --- a/src/ui/dialog/layers.cpp +++ b/src/ui/dialog/layers.cpp @@ -41,7 +41,7 @@ #include "widgets/icon.h" #include "xml/repr.h" #include "sp-root.h" -#include "event-context.h" +#include "ui/tools/tool-base.h" #include "selection-chemistry.h" //#define DUMP_LAYERS 1 diff --git a/src/ui/dialog/spellcheck.cpp b/src/ui/dialog/spellcheck.cpp index 45106755c..00bd445bf 100644 --- a/src/ui/dialog/spellcheck.cpp +++ b/src/ui/dialog/spellcheck.cpp @@ -24,7 +24,7 @@ #include "desktop.h" #include "desktop-handles.h" #include "tools-switch.h" -#include "text-context.h" +#include "ui/tools/text-tool.h" #include "interface.h" #include "preferences.h" #include "sp-text.h" diff --git a/src/ui/dialog/xml-tree.cpp b/src/ui/dialog/xml-tree.cpp index dfddfb1d3..0e1e9f7a6 100644 --- a/src/ui/dialog/xml-tree.cpp +++ b/src/ui/dialog/xml-tree.cpp @@ -27,7 +27,7 @@ #include "dialogs/dialog-events.h" #include "document.h" #include "document-undo.h" -#include "event-context.h" +#include "ui/tools/tool-base.h" #include "helper/window.h" #include "inkscape.h" #include "interface.h" diff --git a/src/ui/tool/Makefile_insert b/src/ui/tool/Makefile_insert index 2a72dd645..f46f48b72 100644 --- a/src/ui/tool/Makefile_insert +++ b/src/ui/tool/Makefile_insert @@ -19,8 +19,6 @@ ink_common_sources += \ 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 \ diff --git a/src/ui/tool/control-point.cpp b/src/ui/tool/control-point.cpp index 3f1587492..e98c7b2a2 100644 --- a/src/ui/tool/control-point.cpp +++ b/src/ui/tool/control-point.cpp @@ -14,7 +14,7 @@ #include "desktop-handles.h" #include "display/sp-canvas.h" #include "display/snap-indicator.h" -#include "event-context.h" +#include "ui/tools/tool-base.h" #include "message-context.h" #include "preferences.h" #include "snap-preferences.h" diff --git a/src/ui/tool/manipulator.h b/src/ui/tool/manipulator.h index 2e6d80517..07e01a656 100644 --- a/src/ui/tool/manipulator.h +++ b/src/ui/tool/manipulator.h @@ -18,7 +18,7 @@ #include #include #include -#include "event-context.h" +#include "ui/tools/tool-base.h" class SPDesktop; namespace Inkscape { diff --git a/src/ui/tool/node-tool.cpp b/src/ui/tool/node-tool.cpp deleted file mode 100644 index 2eeab7c1b..000000000 --- a/src/ui/tool/node-tool.cpp +++ /dev/null @@ -1,705 +0,0 @@ -/** - * @file - * New node tool - implementation. - */ -/* Authors: - * Krzysztof KosiÅ„ski - * Abhishek Sharma - * - * Copyright (C) 2009 Authors - * Released under GNU GPL, read the file 'COPYING' for more information - */ - -#include "curve-drag-point.h" -#include -#include "desktop.h" -#include "desktop-handles.h" -#include "display/sp-canvas-group.h" -#include "display/canvas-bpath.h" -#include "display/curve.h" -#include "display/sp-canvas.h" -#include "document.h" -#include "live_effects/lpeobject.h" -#include "message-context.h" -#include "selection.h" -#include "shape-editor.h" // temporary! -#include "sp-clippath.h" -#include "sp-item-group.h" -#include "sp-mask.h" -#include "sp-object-group.h" -#include "sp-path.h" -#include "sp-text.h" -#include "ui/control-manager.h" -#include "ui/tool/node-tool.h" -#include "ui/tool/control-point-selection.h" -#include "ui/tool/event-utils.h" -#include "ui/tool/manipulator.h" -#include "ui/tool/multi-path-manipulator.h" -#include "ui/tool/path-manipulator.h" -#include "ui/tool/selector.h" -#include "ui/tool/shape-record.h" - -#include "pixmaps/cursor-node.xpm" -#include "pixmaps/cursor-node-d.xpm" -#include "selection-chemistry.h" - -#include - -/** @struct NodeTool - * - * Node tool event context. - * - * @par Architectural overview of the tool - * @par - * Here's a breakdown of what each object does. - * - Handle: shows a handle and keeps the node type constraint (smooth / symmetric) by updating - * the other handle's position when dragged. Its move() method cannot violate the constraints. - * - Node: keeps node type constraints for auto nodes and smooth nodes at ends of linear segments. - * Its move() method cannot violate constraints. Handles linear grow and dispatches spatial grow - * to MultiPathManipulator. Keeps a reference to its NodeList. - * - NodeList: exposes an iterator-based interface to nodes. It is possible to obtain an iterator - * to a node from the node. Keeps a reference to its SubpathList. - * - SubpathList: list of NodeLists that represents an editable pathvector. Keeps a reference - * to its PathManipulator. - * - PathManipulator: performs most of the single-path actions like reverse subpaths, - * delete segment, shift selection, etc. Keeps a reference to MultiPathManipulator. - * - MultiPathManipulator: performs additional operations for actions that are not per-path, - * for example node joins and segment joins. Tracks the control transforms for PMs that edit - * clipping paths and masks. It is more or less equivalent to ShapeEditor and in the future - * it might handle all shapes. Handles XML commit of actions that affect all paths or - * the node selection and removes PathManipulators that have no nodes left after e.g. node - * deletes. - * - ControlPointSelection: keeps track of node selection and a set of nodes that can potentially - * be selected. There can be more than one selection. Performs actions that require no - * knowledge about the path, only about the nodes, like dragging and transforms. It is not - * specific to nodes and can accomodate any control point derived from SelectableControlPoint. - * Transforms nodes in response to transform handle events. - * - TransformHandleSet: displays nodeset transform handles and emits transform events. The aim - * is to eventually use a common class for object and control point transforms. - * - SelectableControlPoint: base for any type of selectable point. It can belong to only one - * selection. - * - * @par Functionality that resides in weird places - * @par - * - * This list is probably incomplete. - * - Curve dragging: CurveDragPoint, controlled by PathManipulator - * - Single handle shortcuts: MultiPathManipulator::event(), ModifierTracker - * - Linear and spatial grow: Node, spatial grow routed to ControlPointSelection - * - Committing handle actions performed with the mouse: PathManipulator - * - Sculpting: ControlPointSelection - * - * @par Plans for the future - * @par - * - MultiPathManipulator should become a generic shape editor that manages all active manipulator, - * more or less like the old ShapeEditor. - * - Knotholder should be rewritten into one manipulator class per shape, using the control point - * classes. Interesting features like dragging rectangle sides could be added along the way. - * - Better handling of clip and mask editing, particularly in response to undo. - * - High level refactoring of the event context hierarchy. All aspects of tools, like toolbox - * controls, icons, event handling should be collected in one class, though each aspect - * of a tool might be in an separate class for better modularity. The long term goal is to allow - * tools to be defined in extensions or shared library plugins. - */ - -using Inkscape::ControlManager; - -#include "tool-factory.h" - -namespace Inkscape { -namespace UI { -namespace Tools { - -namespace { - ToolBase* createNodesContext() { - return new NodeTool(); - } - - bool nodesContextRegistered = ToolFactory::instance().registerObject("/tools/nodes", createNodesContext); -} - -const std::string& NodeTool::getPrefsPath() { - return NodeTool::prefsPath; -} - -const std::string NodeTool::prefsPath = "/tools/nodes"; - -SPCanvasGroup *create_control_group(SPDesktop *d); - -NodeTool::NodeTool() : ToolBase() { - this->show_handles = false; - this->single_node_transform_handles = false; - this->show_transform_handles = false; - this->cursor_drag = false; - this->live_objects = false; - this->edit_clipping_paths = false; - this->live_outline = false; - this->flashed_item = 0; - this->_transform_handle_group = 0; - this->show_path_direction = false; - this->_last_over = 0; - this->edit_masks = false; - this->show_outline = false; - this->flash_tempitem = 0; - - this->cursor_shape = cursor_node_xpm; - this->hot_x = 1; - this->hot_y = 1; - - this->_selected_nodes = 0; - this->_multipath = 0; - this->_selector = 0; - this->_path_data = 0; -} - -SPCanvasGroup *create_control_group(SPDesktop *d) -{ - return reinterpret_cast(sp_canvas_item_new( - sp_desktop_controls(d), SP_TYPE_CANVAS_GROUP, NULL)); -} - -void destroy_group(SPCanvasGroup *g) -{ - sp_canvas_item_destroy(SP_CANVAS_ITEM(g)); -} - -NodeTool::~NodeTool() { - this->enableGrDrag(false); - - if (this->flash_tempitem) { - this->desktop->remove_temporary_canvasitem(this->flash_tempitem); - } - - this->_selection_changed_connection.disconnect(); - //this->_selection_modified_connection.disconnect(); - this->_mouseover_changed_connection.disconnect(); - this->_sizeUpdatedConn.disconnect(); - - delete this->_multipath; - delete this->_selected_nodes; - delete this->_selector; - - Inkscape::UI::PathSharedData &data = *this->_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(this->_transform_handle_group); -} - -void NodeTool::setup() { - ToolBase::setup(); - - this->_path_data = new Inkscape::UI::PathSharedData(); - - Inkscape::UI::PathSharedData &data = *this->_path_data; - data.node_data.desktop = this->desktop; - - // selector has to be created here, so that its hidden control point is on the bottom - this->_selector = new Inkscape::UI::Selector(this->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(this->desktop); - data.node_data.handle_line_group = create_control_group(this->desktop); - data.dragpoint_group = create_control_group(this->desktop); - this->_transform_handle_group = create_control_group(this->desktop); - data.node_data.node_group = create_control_group(this->desktop); - data.node_data.handle_group = create_control_group(this->desktop); - - Inkscape::Selection *selection = sp_desktop_selection (this->desktop); - - this->_selection_changed_connection.disconnect(); - this->_selection_changed_connection = - selection->connectChanged(sigc::mem_fun(this, &NodeTool::selection_changed)); - - this->_mouseover_changed_connection.disconnect(); - this->_mouseover_changed_connection = - Inkscape::UI::ControlPoint::signal_mouseover_change.connect(sigc::mem_fun(this, &NodeTool::mouseover_changed)); - - this->_sizeUpdatedConn = ControlManager::getManager().connectCtrlSizeChanged( - sigc::mem_fun(this, &NodeTool::handleControlUiStyleChange) - ); - - this->_selected_nodes = new Inkscape::UI::ControlPointSelection(this->desktop, this->_transform_handle_group); - - data.node_data.selection = this->_selected_nodes; - - this->_multipath = new Inkscape::UI::MultiPathManipulator(data, this->_selection_changed_connection); - - this->_selector->signal_point.connect(sigc::mem_fun(this, &NodeTool::select_point)); - this->_selector->signal_area.connect(sigc::mem_fun(this, &NodeTool::select_area)); - - this->_multipath->signal_coords_changed.connect( - sigc::bind( - sigc::mem_fun(*this->desktop, &SPDesktop::emitToolSubselectionChanged), - (void*)NULL - ) - ); - - this->_selected_nodes->signal_point_changed.connect( - // Hide both signal parameters and bind the function parameter to 0 - // sigc::signal - // <=> - // void update_tip(GdkEvent *event) - sigc::hide(sigc::hide(sigc::bind( - sigc::mem_fun(this, &NodeTool::update_tip), - (GdkEvent*)NULL - ))) - ); - - this->cursor_drag = false; - this->show_transform_handles = true; - this->single_node_transform_handles = false; - this->flash_tempitem = NULL; - this->flashed_item = NULL; - this->_last_over = NULL; - - // read prefs before adding items to selection to prevent momentarily showing the outline - sp_event_context_read(this, "show_handles"); - sp_event_context_read(this, "show_outline"); - sp_event_context_read(this, "live_outline"); - sp_event_context_read(this, "live_objects"); - sp_event_context_read(this, "show_path_direction"); - sp_event_context_read(this, "show_transform_handles"); - sp_event_context_read(this, "single_node_transform_handles"); - sp_event_context_read(this, "edit_clipping_paths"); - sp_event_context_read(this, "edit_masks"); - - this->selection_changed(selection); - this->update_tip(NULL); - - Inkscape::Preferences *prefs = Inkscape::Preferences::get(); - - if (prefs->getBool("/tools/nodes/selcue")) { - this->enableSelectionCue(); - } - - if (prefs->getBool("/tools/nodes/gradientdrag")) { - this->enableGrDrag(); - } - - this->desktop->emitToolSubselectionChanged(NULL); // sets the coord entry fields to inactive -} - -void NodeTool::set(const Inkscape::Preferences::Entry& value) { - Glib::ustring entry_name = value.getEntryName(); - - if (entry_name == "show_handles") { - this->show_handles = value.getBool(true); - this->_multipath->showHandles(this->show_handles); - } else if (entry_name == "show_outline") { - this->show_outline = value.getBool(); - this->_multipath->showOutline(this->show_outline); - } else if (entry_name == "live_outline") { - this->live_outline = value.getBool(); - this->_multipath->setLiveOutline(this->live_outline); - } else if (entry_name == "live_objects") { - this->live_objects = value.getBool(); - this->_multipath->setLiveObjects(this->live_objects); - } else if (entry_name == "show_path_direction") { - this->show_path_direction = value.getBool(); - this->_multipath->showPathDirection(this->show_path_direction); - } else if (entry_name == "show_transform_handles") { - this->show_transform_handles = value.getBool(true); - this->_selected_nodes->showTransformHandles( - this->show_transform_handles, this->single_node_transform_handles); - } else if (entry_name == "single_node_transform_handles") { - this->single_node_transform_handles = value.getBool(); - this->_selected_nodes->showTransformHandles( - this->show_transform_handles, this->single_node_transform_handles); - } else if (entry_name == "edit_clipping_paths") { - this->edit_clipping_paths = value.getBool(); - this->selection_changed(this->desktop->selection); - } else if (entry_name == "edit_masks") { - this->edit_masks = value.getBool(); - this->selection_changed(this->desktop->selection); - } else { - ToolBase::set(value); - } -} - -/** Recursively collect ShapeRecords */ -void gather_items(NodeTool *nt, SPItem *base, SPObject *obj, Inkscape::UI::ShapeRole role, - std::set &s) -{ - using namespace Inkscape::UI; - - if (!obj) { - return; - } - - //XML Tree being used directly here while it shouldn't be. - if (SP_IS_PATH(obj) && obj->getRepr()->attribute("inkscape:original-d") != NULL) { - ShapeRecord r; - r.item = static_cast(obj); - r.edit_transform = Geom::identity(); // TODO wrong? - r.role = role; - s.insert(r); - } else if (role != SHAPE_ROLE_NORMAL && (SP_IS_GROUP(obj) || SP_IS_OBJECTGROUP(obj))) { - for (SPObject *c = obj->children; c; c = c->next) { - gather_items(nt, base, c, role, s); - } - } else if (SP_IS_ITEM(obj)) { - SPItem *item = static_cast(obj); - ShapeRecord r; - r.item = item; - // TODO add support for objectBoundingBox - r.edit_transform = base ? base->i2doc_affine() : Geom::identity(); - r.role = role; - - if (s.insert(r).second) { - // this item was encountered the first time - if (nt->edit_clipping_paths && item->clip_ref) { - gather_items(nt, item, item->clip_ref->getObject(), SHAPE_ROLE_CLIPPING_PATH, s); - } - - if (nt->edit_masks && item->mask_ref) { - gather_items(nt, item, item->mask_ref->getObject(), SHAPE_ROLE_MASK, s); - } - } - } -} - -void NodeTool::selection_changed(Inkscape::Selection *sel) { - using namespace Inkscape::UI; - - std::set shapes; - - GSList const *ilist = sel->itemList(); - - for (GSList *i = const_cast(ilist); i; i = i->next) { - SPObject *obj = static_cast(i->data); - - if (SP_IS_ITEM(obj)) { - gather_items(this, NULL, static_cast(obj), SHAPE_ROLE_NORMAL, shapes); - } - } - - // use multiple ShapeEditors for now, to allow editing many shapes at once - // needs to be rethought - for (boost::ptr_map::iterator i = this->_shape_editors.begin(); - i != this->_shape_editors.end(); ) - { - ShapeRecord s; - s.item = i->first; - - if (shapes.find(s) == shapes.end()) { - this->_shape_editors.erase(i++); - } else { - ++i; - } - } - - for (std::set::iterator i = shapes.begin(); i != shapes.end(); ++i) { - ShapeRecord const &r = *i; - - if ((SP_IS_SHAPE(r.item) || SP_IS_TEXT(r.item)) && - this->_shape_editors.find(r.item) == this->_shape_editors.end()) - { - ShapeEditor *si = new ShapeEditor(this->desktop); - si->set_item(r.item, SH_KNOTHOLDER); - this->_shape_editors.insert(const_cast(r.item), si); - } - } - - this->_multipath->setItems(shapes); - this->update_tip(NULL); - this->desktop->updateNow(); -} - -bool NodeTool::root_handler(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 - - Inkscape::Selection *selection = desktop->selection; - static Inkscape::Preferences *prefs = Inkscape::Preferences::get(); - - if (this->_multipath->event(this, event)) { - return true; - } - - if (this->_selector->event(this, event)) { - return true; - } - - if (this->_selected_nodes->event(this, event)) { - return true; - } - - switch (event->type) - { - case GDK_MOTION_NOTIFY: { - combine_motion_events(desktop->canvas, event->motion, 0); - SPItem *over_item = sp_event_context_find_item (desktop, event_point(event->button), - FALSE, TRUE); - - if (over_item != this->_last_over) { - this->_last_over = over_item; - //ink_node_tool_update_tip(nt, event); - this->update_tip(event); - } - - // create pathflash outline - if (prefs->getBool("/tools/nodes/pathflash_enabled")) { - if (over_item == this->flashed_item) { - break; - } - - if (!prefs->getBool("/tools/nodes/pathflash_selected") && selection->includes(over_item)) { - break; - } - - if (this->flash_tempitem) { - desktop->remove_temporary_canvasitem(this->flash_tempitem); - this->flash_tempitem = NULL; - this->flashed_item = NULL; - } - - if (!SP_IS_SHAPE(over_item)) { - break; // for now, handle only shapes - } - - this->flashed_item = over_item; - SPCurve *c = SP_SHAPE(over_item)->getCurveBeforeLPE(); - - if (!c) { - break; // break out when curve doesn't exist - } - - c->transform(over_item->i2dt_affine()); - 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); - - this->flash_tempitem = desktop->add_temporary_canvasitem(flash, - prefs->getInt("/tools/nodes/pathflash_timeout", 500)); - - c->unref(); - } - } break; // do not return true, because we need to pass this event to the parent context - // otherwise some features cease to work - - case GDK_KEY_PRESS: - switch (get_group0_keyval(&event->key)) - { - case GDK_KEY_Escape: // deselect everything - if (this->_selected_nodes->empty()) { - Inkscape::SelectionHelper::selectNone(desktop); - } else { - this->_selected_nodes->clear(); - } - //ink_node_tool_update_tip(nt, event); - this->update_tip(event); - return TRUE; - - case GDK_KEY_a: - case GDK_KEY_A: - if (held_control(event->key) && held_alt(event->key)) { - this->_selected_nodes->selectAll(); - // Ctrl+A is handled in selection-chemistry.cpp via verb - //ink_node_tool_update_tip(nt, event); - this->update_tip(event); - return TRUE; - } - break; - - case GDK_KEY_h: - case GDK_KEY_H: - if (held_only_control(event->key)) { - Inkscape::Preferences *prefs = Inkscape::Preferences::get(); - prefs->setBool("/tools/nodes/show_handles", !this->show_handles); - return TRUE; - } - break; - - default: - break; - } - //ink_node_tool_update_tip(nt, event); - this->update_tip(event); - break; - - case GDK_KEY_RELEASE: - //ink_node_tool_update_tip(nt, event); - this->update_tip(event); - break; - - default: - break; - } - - return ToolBase::root_handler(event); -} - -void NodeTool::update_tip(GdkEvent *event) { - using namespace Inkscape::UI; - - if (event && (event->type == GDK_KEY_PRESS || event->type == GDK_KEY_RELEASE)) { - unsigned new_state = state_after_event(event); - - if (new_state == event->key.state) { - return; - } - - if (state_held_shift(new_state)) { - if (this->_last_over) { - this->message_context->set(Inkscape::NORMAL_MESSAGE, - C_("Node tool tip", "Shift: drag to add nodes to the selection, " - "click to toggle object selection")); - } else { - this->message_context->set(Inkscape::NORMAL_MESSAGE, - C_("Node tool tip", "Shift: drag to add nodes to the selection")); - } - - return; - } - } - - unsigned sz = this->_selected_nodes->size(); - unsigned total = this->_selected_nodes->allPoints().size(); - - if (sz != 0) { - char *nodestring = g_strdup_printf( - ngettext("%u of %u node selected.", "%u of %u nodes selected.", total), - sz, total); - - if (this->_last_over) { - // TRANSLATORS: The %s below is where the "%u of %u nodes selected" sentence gets put - char *dyntip = g_strdup_printf(C_("Node tool tip", - "%s Drag to select nodes, click to edit only this object (more: Shift)"), - nodestring); - this->message_context->set(Inkscape::NORMAL_MESSAGE, dyntip); - g_free(dyntip); - } else { - char *dyntip = g_strdup_printf(C_("Node tool tip", - "%s Drag to select nodes, click clear the selection"), - nodestring); - this->message_context->set(Inkscape::NORMAL_MESSAGE, dyntip); - g_free(dyntip); - } - g_free(nodestring); - } else if (!this->_multipath->empty()) { - if (this->_last_over) { - this->message_context->set(Inkscape::NORMAL_MESSAGE, C_("Node tool tip", - "Drag to select nodes, click to edit only this object")); - } else { - this->message_context->set(Inkscape::NORMAL_MESSAGE, C_("Node tool tip", - "Drag to select nodes, click to clear the selection")); - } - } else { - if (this->_last_over) { - this->message_context->set(Inkscape::NORMAL_MESSAGE, C_("Node tool tip", - "Drag to select objects to edit, click to edit this object (more: Shift)")); - } else { - this->message_context->set(Inkscape::NORMAL_MESSAGE, C_("Node tool tip", - "Drag to select objects to edit")); - } - } -} - -void NodeTool::select_area(Geom::Rect const &sel, GdkEventButton *event) { - using namespace Inkscape::UI; - - if (this->_multipath->empty()) { - // if multipath is empty, select rubberbanded items rather than nodes - Inkscape::Selection *selection = this->desktop->selection; - GSList *items = sp_desktop_document(this->desktop)->getItemsInBox(this->desktop->dkey, sel); - selection->setList(items); - g_slist_free(items); - } else { - if (!held_shift(*event)) { - this->_selected_nodes->clear(); - } - - this->_selected_nodes->selectArea(sel); - } -} - -void NodeTool::select_point(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 = this->desktop->selection; - - SPItem *item_clicked = sp_event_context_find_item (this->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 there are nodes selected, the first click should deselect the nodes - // and the second should deselect the items - if (!state_held_shift(event->state)) { - if (this->_selected_nodes->empty()) { - selection->clear(); - } else { - this->_selected_nodes->clear(); - } - } - } else { - if (held_shift(*event)) { - selection->toggle(item_clicked); - } else { - selection->set(item_clicked); - } - - this->desktop->updateNow(); - } -} - -void NodeTool::mouseover_changed(Inkscape::UI::ControlPoint *p) { - using Inkscape::UI::CurveDragPoint; - - CurveDragPoint *cdp = dynamic_cast(p); - - if (cdp && !this->cursor_drag) { - this->cursor_shape = cursor_node_d_xpm; - this->hot_x = 1; - this->hot_y = 1; - this->sp_event_context_update_cursor(); - this->cursor_drag = true; - } else if (!cdp && this->cursor_drag) { - this->cursor_shape = cursor_node_xpm; - this->hot_x = 1; - this->hot_y = 1; - this->sp_event_context_update_cursor(); - this->cursor_drag = false; - } -} - -void NodeTool::handleControlUiStyleChange() { - this->_multipath->updateHandles(); -} - -} -} -} - -//} // 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:fileencoding=utf-8:textwidth=99 : diff --git a/src/ui/tool/node-tool.h b/src/ui/tool/node-tool.h deleted file mode 100644 index a670256bb..000000000 --- a/src/ui/tool/node-tool.h +++ /dev/null @@ -1,105 +0,0 @@ -/** @file - * @brief New node tool with support for multiple path editing - */ -/* Authors: - * Krzysztof KosiÅ„ski - * - * 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 -#include -#include "event-context.h" - -namespace Inkscape { - namespace Display { - class TemporaryItem; - } - - namespace UI { - class MultiPathManipulator; - class ControlPointSelection; - class Selector; - class ControlPoint; - - struct PathSharedData; - } -} - -#define INK_NODE_TOOL(obj) (dynamic_cast((Inkscape::UI::Tools::ToolBase*)obj)) -#define INK_IS_NODE_TOOL(obj) (dynamic_cast((const Inkscape::UI::Tools::ToolBase*)obj)) - -namespace Inkscape { -namespace UI { -namespace Tools { - -class NodeTool : public ToolBase { -public: - NodeTool(); - virtual ~NodeTool(); - - Inkscape::UI::ControlPointSelection* _selected_nodes; - Inkscape::UI::MultiPathManipulator* _multipath; - - bool edit_clipping_paths; - bool edit_masks; - - static const std::string prefsPath; - - virtual void setup(); - virtual void set(const Inkscape::Preferences::Entry& val); - virtual bool root_handler(GdkEvent* event); - - virtual const std::string& getPrefsPath(); - -private: - sigc::connection _selection_changed_connection; - sigc::connection _mouseover_changed_connection; - sigc::connection _sizeUpdatedConn; - - SPItem *flashed_item; - Inkscape::Display::TemporaryItem *flash_tempitem; - Inkscape::UI::Selector* _selector; - Inkscape::UI::PathSharedData* _path_data; - SPCanvasGroup *_transform_handle_group; - SPItem *_last_over; - boost::ptr_map _shape_editors; - - bool cursor_drag; - bool show_handles; - bool show_outline; - bool live_outline; - bool live_objects; - bool show_path_direction; - bool show_transform_handles; - bool single_node_transform_handles; - - void selection_changed(Inkscape::Selection *sel); - - void select_area(Geom::Rect const &sel, GdkEventButton *event); - void select_point(Geom::Point const &sel, GdkEventButton *event); - void mouseover_changed(Inkscape::UI::ControlPoint *p); - void update_tip(GdkEvent *event); - void handleControlUiStyleChange(); -}; - -} -} -} - -#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:fileencoding=utf-8:textwidth=99 : diff --git a/src/ui/tool/selector.cpp b/src/ui/tool/selector.cpp index d46a160bc..bdeacadc9 100644 --- a/src/ui/tool/selector.cpp +++ b/src/ui/tool/selector.cpp @@ -13,7 +13,7 @@ #include "desktop.h" #include "desktop-handles.h" #include "display/sodipodi-ctrlrect.h" -#include "event-context.h" +#include "ui/tools/tool-base.h" #include "preferences.h" #include "ui/tool/event-utils.h" #include "ui/tool/selector.h" diff --git a/src/ui/tool/transform-handle-set.cpp b/src/ui/tool/transform-handle-set.cpp index 535a138ed..f21e1661a 100644 --- a/src/ui/tool/transform-handle-set.cpp +++ b/src/ui/tool/transform-handle-set.cpp @@ -26,7 +26,7 @@ #include "ui/tool/selectable-control-point.h" #include "ui/tool/event-utils.h" #include "ui/tool/transform-handle-set.h" -#include "ui/tool/node-tool.h" +#include "ui/tools/node-tool.h" #include "ui/tool/node.h" #include "seltrans.h" diff --git a/src/ui/tools/Makefile_insert b/src/ui/tools/Makefile_insert new file mode 100644 index 000000000..cd09a3230 --- /dev/null +++ b/src/ui/tools/Makefile_insert @@ -0,0 +1,28 @@ +## Makefile.am fragment sourced by src/Makefile.am. + +ink_common_sources += \ + ui/tools/arc-tool.cpp ui/tools/arc-tool.h \ + ui/tools/box3d-tool.cpp ui/tools/box3d-tool.h \ + ui/tools/calligraphic-tool.cpp ui/tools/calligraphic-tool.h \ + ui/tools/connector-tool.cpp ui/tools/connector-tool.h \ + ui/tools/dropper-tool.cpp ui/tools/dropper-tool.h \ + ui/tools/dynamic-base.cpp ui/tools/dynamic-base.h \ + ui/tools/eraser-tool.cpp ui/tools/eraser-tool.h \ + ui/tools/flood-tool.cpp ui/tools/flood-tool.h \ + ui/tools/freehand-base.cpp ui/tools/freehand-base.h \ + ui/tools/gradient-tool.cpp ui/tools/gradient-tool.h \ + ui/tools/lpe-tool.cpp ui/tools/lpe-tool.h \ + ui/tools/measure-tool.cpp ui/tools/measure-tool.h \ + ui/tools/mesh-tool.cpp ui/tools/mesh-tool.h \ + ui/tools/node-tool.cpp ui/tools/node-tool.h \ + ui/tools/pen-tool.cpp ui/tools/pen-tool.h \ + ui/tools/pencil-tool.cpp ui/tools/pencil-tool.h \ + ui/tools/rect-tool.cpp ui/tools/rect-tool.h \ + ui/tools/select-tool.cpp ui/tools/select-tool.h \ + ui/tools/spiral-tool.cpp ui/tools/spiral-tool.h \ + ui/tools/spray-tool.cpp ui/tools/spray-tool.h \ + ui/tools/star-tool.cpp ui/tools/star-tool.h \ + ui/tools/text-tool.cpp ui/tools/text-tool.h \ + ui/tools/tool-base.cpp ui/tools/tool-base.h \ + ui/tools/tweak-tool.cpp ui/tools/tweak-tool.h \ + ui/tools/zoom-tool.cpp ui/tools/zoom-tool.h \ No newline at end of file diff --git a/src/ui/tools/arc-tool.cpp b/src/ui/tools/arc-tool.cpp new file mode 100644 index 000000000..bb7dfa21c --- /dev/null +++ b/src/ui/tools/arc-tool.cpp @@ -0,0 +1,503 @@ +/** + * @file + * Ellipse drawing context. + */ +/* Authors: + * Mitsuru Oka + * Lauris Kaplinski + * bulia byak + * Johan Engelen + * Abhishek Sharma + * Jon A. Cruz + * + * Copyright (C) 2000-2006 Authors + * Copyright (C) 2000-2001 Ximian, Inc. + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#ifdef HAVE_CONFIG_H +# include +#endif +#include + +#include "macros.h" +#include +#include "display/sp-canvas.h" +#include "sp-ellipse.h" +#include "document.h" +#include "document-undo.h" +#include "sp-namedview.h" +#include "selection.h" +#include "desktop-handles.h" +#include "snap.h" +#include "pixmaps/cursor-ellipse.xpm" +#include "xml/repr.h" +#include "xml/node-event-vector.h" +#include "preferences.h" +#include "message-context.h" +#include "desktop.h" +#include "desktop-style.h" +#include "context-fns.h" +#include "verbs.h" +#include "shape-editor.h" +#include "ui/tools/tool-base.h" + +#include "ui/tools/arc-tool.h" +#include "display/sp-canvas-item.h" + +using Inkscape::DocumentUndo; + +#include "tool-factory.h" + +namespace Inkscape { +namespace UI { +namespace Tools { + +namespace { + ToolBase* createArcContext() { + return new ArcTool(); + } + + bool arcContextRegistered = ToolFactory::instance().registerObject("/tools/shapes/arc", createArcContext); +} + +const std::string& ArcTool::getPrefsPath() { + return ArcTool::prefsPath; +} + +const std::string ArcTool::prefsPath = "/tools/shapes/arc"; + + +ArcTool::ArcTool() : ToolBase() { + this->cursor_shape = cursor_ellipse_xpm; + this->hot_x = 4; + this->hot_y = 4; + this->xp = 0; + this->yp = 0; + this->tolerance = 0; + this->within_tolerance = false; + this->item_to_select = NULL; + //this->tool_url = "/tools/shapes/arc"; + + this->arc = NULL; +} + +void ArcTool::finish() { + sp_canvas_item_ungrab(SP_CANVAS_ITEM(desktop->acetate), GDK_CURRENT_TIME); + this->finishItem(); + this->sel_changed_connection.disconnect(); + + ToolBase::finish(); +} + +ArcTool::~ArcTool() { + this->enableGrDrag(false); + + this->sel_changed_connection.disconnect(); + + delete this->shape_editor; + this->shape_editor = NULL; + + /* fixme: This is necessary because we do not grab */ + if (this->arc) { + this->finishItem(); + } +} + +/** + * Callback that processes the "changed" signal on the selection; + * destroys old and creates new knotholder. + */ +void ArcTool::selection_changed(Inkscape::Selection* selection) { + this->shape_editor->unset_item(SH_KNOTHOLDER); + this->shape_editor->set_item(selection->singleItem(), SH_KNOTHOLDER); +} + +void ArcTool::setup() { + ToolBase::setup(); + + Inkscape::Selection *selection = sp_desktop_selection(this->desktop); + + this->shape_editor = new ShapeEditor(this->desktop); + + SPItem *item = sp_desktop_selection(this->desktop)->singleItem(); + if (item) { + this->shape_editor->set_item(item, SH_KNOTHOLDER); + } + + this->sel_changed_connection.disconnect(); + this->sel_changed_connection = selection->connectChanged( + sigc::mem_fun(this, &ArcTool::selection_changed) + ); + + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + if (prefs->getBool("/tools/shapes/selcue")) { + this->enableSelectionCue(); + } + + if (prefs->getBool("/tools/shapes/gradientdrag")) { + this->enableGrDrag(); + } +} + +bool ArcTool::item_handler(SPItem* item, GdkEvent* event) { + gint ret = FALSE; + + switch (event->type) { + case GDK_BUTTON_PRESS: + if (event->button.button == 1 && !this->space_panning) { + Inkscape::setup_for_drag_start(desktop, this, event); + ret = TRUE; + } + break; + // motion and release are always on root (why?) + default: + break; + } + +// if ((SP_EVENT_CONTEXT_CLASS(sp_arc_context_parent_class))->item_handler) { +// ret = (SP_EVENT_CONTEXT_CLASS(sp_arc_context_parent_class))->item_handler(event_context, item, event); +// } + // CPPIFY: ret is overwritten... + ret = ToolBase::item_handler(item, event); + + return ret; +} + +bool ArcTool::root_handler(GdkEvent* event) { + static bool dragging; + + Inkscape::Selection *selection = sp_desktop_selection(desktop); + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + + this->tolerance = prefs->getIntLimited("/options/dragtolerance/value", 0, 0, 100); + + gint ret = FALSE; + + switch (event->type) { + case GDK_BUTTON_PRESS: + if (event->button.button == 1 && !this->space_panning) { + dragging = true; + + this->center = Inkscape::setup_for_drag_start(desktop, this, event); + + /* Snap center */ + SnapManager &m = desktop->namedview->snap_manager; + m.setup(desktop); + m.freeSnapReturnByRef(this->center, Inkscape::SNAPSOURCE_NODE_HANDLE); + + sp_canvas_item_grab(SP_CANVAS_ITEM(desktop->acetate), + GDK_KEY_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK, + NULL, event->button.time); + ret = TRUE; + m.unSetup(); + } + break; + case GDK_MOTION_NOTIFY: + if (dragging && (event->motion.state & GDK_BUTTON1_MASK) && !this->space_panning) { + if ( this->within_tolerance + && ( abs( (gint) event->motion.x - this->xp ) < this->tolerance ) + && ( abs( (gint) event->motion.y - this->yp ) < this->tolerance ) ) { + break; // do not drag if we're within tolerance from origin + } + // Once the user has moved farther than tolerance from the original location + // (indicating they intend to draw, not click), then always process the + // motion notify coordinates as given (no snapping back to origin) + this->within_tolerance = false; + + Geom::Point const motion_w(event->motion.x, event->motion.y); + Geom::Point motion_dt(desktop->w2d(motion_w)); + + this->drag(motion_dt, event->motion.state); + + gobble_motion_events(GDK_BUTTON1_MASK); + + ret = TRUE; + } else if (!sp_event_context_knot_mouseover(this)){ + SnapManager &m = desktop->namedview->snap_manager; + m.setup(desktop); + + Geom::Point const motion_w(event->motion.x, event->motion.y); + Geom::Point motion_dt(desktop->w2d(motion_w)); + m.preSnap(Inkscape::SnapCandidatePoint(motion_dt, Inkscape::SNAPSOURCE_NODE_HANDLE)); + m.unSetup(); + } + break; + case GDK_BUTTON_RELEASE: + this->xp = this->yp = 0; + if (event->button.button == 1 && !this->space_panning) { + dragging = false; + sp_event_context_discard_delayed_snap_event(this); + + if (!this->within_tolerance) { + // we've been dragging, finish the arc + this->finishItem(); + } else if (this->item_to_select) { + // no dragging, select clicked item if any + if (event->button.state & GDK_SHIFT_MASK) { + selection->toggle(this->item_to_select); + } else { + selection->set(this->item_to_select); + } + } else { + // click in an empty space + selection->clear(); + } + + this->xp = 0; + this->yp = 0; + this->item_to_select = NULL; + ret = TRUE; + } + sp_canvas_item_ungrab(SP_CANVAS_ITEM(desktop->acetate), event->button.time); + break; + + case GDK_KEY_PRESS: + switch (get_group0_keyval (&event->key)) { + case GDK_KEY_Alt_L: + case GDK_KEY_Alt_R: + case GDK_KEY_Control_L: + case GDK_KEY_Control_R: + case GDK_KEY_Shift_L: + case GDK_KEY_Shift_R: + case GDK_KEY_Meta_L: // Meta is when you press Shift+Alt (at least on my machine) + case GDK_KEY_Meta_R: + if (!dragging) { + sp_event_show_modifier_tip(this->defaultMessageContext(), event, + _("Ctrl: make circle or integer-ratio ellipse, snap arc/segment angle"), + _("Shift: draw around the starting point"), + NULL); + } + break; + + case GDK_KEY_Up: + case GDK_KEY_Down: + case GDK_KEY_KP_Up: + case GDK_KEY_KP_Down: + // prevent the zoom field from activation + if (!MOD__CTRL_ONLY(event)) + ret = TRUE; + break; + + case GDK_KEY_x: + case GDK_KEY_X: + if (MOD__ALT_ONLY(event)) { + desktop->setToolboxFocusTo ("altx-arc"); + ret = TRUE; + } + break; + + case GDK_KEY_Escape: + if (dragging) { + dragging = false; + sp_event_context_discard_delayed_snap_event(this); + // if drawing, cancel, otherwise pass it up for deselecting + this->cancel(); + ret = TRUE; + } + break; + + case GDK_KEY_space: + if (dragging) { + sp_canvas_item_ungrab(SP_CANVAS_ITEM(desktop->acetate), event->button.time); + dragging = false; + sp_event_context_discard_delayed_snap_event(this); + + if (!this->within_tolerance) { + // we've been dragging, finish the arc + this->finishItem(); + } + // do not return true, so that space would work switching to selector + } + break; + + case GDK_KEY_Delete: + case GDK_KEY_KP_Delete: + case GDK_KEY_BackSpace: + ret = this->deleteSelectedDrag(MOD__CTRL_ONLY(event)); + break; + + default: + break; + } + break; + + case GDK_KEY_RELEASE: + switch (event->key.keyval) { + case GDK_KEY_Alt_L: + case GDK_KEY_Alt_R: + case GDK_KEY_Control_L: + case GDK_KEY_Control_R: + case GDK_KEY_Shift_L: + case GDK_KEY_Shift_R: + case GDK_KEY_Meta_L: // Meta is when you press Shift+Alt + case GDK_KEY_Meta_R: + this->defaultMessageContext()->clear(); + break; + + default: + break; + } + break; + + default: + break; + } + + if (!ret) { + ret = ToolBase::root_handler(event); + } + + return ret; +} + +void ArcTool::drag(Geom::Point pt, guint state) { + if (!this->arc) { + if (Inkscape::have_viable_layer(desktop, this->message_context) == false) { + return; + } + + // Create object + Inkscape::XML::Document *xml_doc = desktop->doc()->getReprDoc(); + Inkscape::XML::Node *repr = xml_doc->createElement("svg:path"); + repr->setAttribute("sodipodi:type", "arc"); + + // Set style + sp_desktop_apply_style_tool(desktop, repr, "/tools/shapes/arc", false); + + this->arc = SP_GENERICELLIPSE(desktop->currentLayer()->appendChildRepr(repr)); + Inkscape::GC::release(repr); + this->arc->transform = SP_ITEM(desktop->currentLayer())->i2doc_affine().inverse(); + this->arc->updateRepr(); + + desktop->canvas->forceFullRedrawAfterInterruptions(5); + } + + bool ctrl_save = false; + + if ((state & GDK_MOD1_MASK) && (state & GDK_CONTROL_MASK) && !(state & GDK_SHIFT_MASK)) { + // if Alt is pressed without Shift in addition to Control, temporarily drop the CONTROL mask + // so that the ellipse is not constrained to integer ratios + ctrl_save = true; + state = state ^ GDK_CONTROL_MASK; + } + + Geom::Rect r = Inkscape::snap_rectangular_box(desktop, this->arc, pt, this->center, state); + + if (ctrl_save) { + state = state ^ GDK_CONTROL_MASK; + } + + Geom::Point dir = r.dimensions() / 2; + + if (state & GDK_MOD1_MASK) { + /* With Alt let the ellipse pass through the mouse pointer */ + Geom::Point c = r.midpoint(); + + if (!ctrl_save) { + if (fabs(dir[Geom::X]) > 1E-6 && fabs(dir[Geom::Y]) > 1E-6) { + Geom::Affine const i2d ( (this->arc)->i2dt_affine() ); + Geom::Point new_dir = pt * i2d - c; + new_dir[Geom::X] *= dir[Geom::Y] / dir[Geom::X]; + double lambda = new_dir.length() / dir[Geom::Y]; + r = Geom::Rect (c - lambda*dir, c + lambda*dir); + } + } else { + /* with Alt+Ctrl (without Shift) we generate a perfect circle + with diameter click point <--> mouse pointer */ + double l = dir.length(); + Geom::Point d (l, l); + r = Geom::Rect (c - d, c + d); + } + } + + this->arc->position_set( + r.midpoint()[Geom::X], r.midpoint()[Geom::Y], + r.dimensions()[Geom::X] / 2, r.dimensions()[Geom::Y] / 2); + + double rdimx = r.dimensions()[Geom::X]; + double rdimy = r.dimensions()[Geom::Y]; + + Inkscape::Util::Quantity rdimx_q = Inkscape::Util::Quantity(rdimx, "px"); + Inkscape::Util::Quantity rdimy_q = Inkscape::Util::Quantity(rdimy, "px"); + GString *xs = g_string_new(rdimx_q.string(desktop->namedview->doc_units).c_str()); + GString *ys = g_string_new(rdimy_q.string(desktop->namedview->doc_units).c_str()); + + if (state & GDK_CONTROL_MASK) { + int ratio_x, ratio_y; + + if (fabs (rdimx) > fabs (rdimy)) { + ratio_x = (int) rint (rdimx / rdimy); + ratio_y = 1; + } else { + ratio_x = 1; + ratio_y = (int) rint (rdimy / rdimx); + } + + this->message_context->setF(Inkscape::IMMEDIATE_MESSAGE, _("Ellipse: %s × %s (constrained to ratio %d:%d); with Shift to draw around the starting point"), xs->str, ys->str, ratio_x, ratio_y); + } else { + this->message_context->setF(Inkscape::IMMEDIATE_MESSAGE, _("Ellipse: %s × %s; with Ctrl to make square or integer-ratio ellipse; with Shift to draw around the starting point"), xs->str, ys->str); + } + + g_string_free(xs, FALSE); + g_string_free(ys, FALSE); +} + +void ArcTool::finishItem() { + this->message_context->clear(); + + if (this->arc != NULL) { + if (this->arc->rx.computed == 0 || this->arc->ry.computed == 0) { + this->cancel(); // Don't allow the creating of zero sized arc, for example when the start and and point snap to the snap grid point + return; + } + + this->arc->updateRepr(); + this->arc->doWriteTransform(this->arc->getRepr(), this->arc->transform, NULL, true); + + desktop->canvas->endForcedFullRedraws(); + + sp_desktop_selection(desktop)->set(this->arc); + + DocumentUndo::done(sp_desktop_document(desktop), SP_VERB_CONTEXT_ARC, _("Create ellipse")); + + this->arc = NULL; + } +} + +void ArcTool::cancel() { + sp_desktop_selection(desktop)->clear(); + sp_canvas_item_ungrab(SP_CANVAS_ITEM(desktop->acetate), 0); + + if (this->arc != NULL) { + this->arc->deleteObject(); + this->arc = NULL; + } + + this->within_tolerance = false; + this->xp = 0; + this->yp = 0; + this->item_to_select = NULL; + + desktop->canvas->endForcedFullRedraws(); + + DocumentUndo::cancel(sp_desktop_document(desktop)); +} + +} +} +} + + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/ui/tools/arc-tool.h b/src/ui/tools/arc-tool.h new file mode 100644 index 000000000..eaa50f2b9 --- /dev/null +++ b/src/ui/tools/arc-tool.h @@ -0,0 +1,76 @@ +#ifndef SEEN_ARC_CONTEXT_H +#define SEEN_ARC_CONTEXT_H + +/* + * Ellipse drawing context + * + * Authors: + * Mitsuru Oka + * Lauris Kaplinski + * + * Copyright (C) 2000-2002 Lauris Kaplinski + * Copyright (C) 2000-2001 Ximian, Inc. + * Copyright (C) 2002 Mitsuru Oka + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include +#include + +#include <2geom/point.h> +#include "ui/tools/tool-base.h" + +#include "sp-ellipse.h" + +#define SP_ARC_CONTEXT(obj) (dynamic_cast((Inkscape::UI::Tools::ToolBase*)obj)) +#define SP_IS_ARC_CONTEXT(obj) (dynamic_cast(const Inkscape::UI::Tools::ToolBase*(obj)) != NULL) + +namespace Inkscape { +namespace UI { +namespace Tools { + +class ArcTool : public ToolBase { +public: + ArcTool(); + virtual ~ArcTool(); + + static const std::string prefsPath; + + virtual void setup(); + virtual void finish(); + virtual bool root_handler(GdkEvent* event); + virtual bool item_handler(SPItem* item, GdkEvent* event); + + virtual const std::string& getPrefsPath(); + +private: + SPGenericEllipse *arc; + + Geom::Point center; + + sigc::connection sel_changed_connection; + + void selection_changed(Inkscape::Selection* selection); + + void drag(Geom::Point pt, guint state); + void finishItem(); + void cancel(); +}; + +} +} +} + +#endif /* !SEEN_ARC_CONTEXT_H */ + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 : diff --git a/src/ui/tools/box3d-tool.cpp b/src/ui/tools/box3d-tool.cpp new file mode 100644 index 000000000..80cc75e79 --- /dev/null +++ b/src/ui/tools/box3d-tool.cpp @@ -0,0 +1,637 @@ +/* + * 3D box drawing context + * + * Author: + * Lauris Kaplinski + * bulia byak + * Jon A. Cruz + * Abhishek Sharma + * + * Copyright (C) 2007 Maximilian Albert + * Copyright (C) 2006 Johan Engelen + * Copyright (C) 2000-2005 authors + * Copyright (C) 2000-2001 Ximian, Inc. + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include "config.h" + +#include + +#include "macros.h" +#include "display/sp-canvas.h" +#include "document.h" +#include "document-undo.h" +#include "sp-namedview.h" +#include "selection.h" +#include "selection-chemistry.h" +#include "desktop-handles.h" +#include "snap.h" +#include "display/curve.h" +#include "display/sp-canvas-item.h" +#include "desktop.h" +#include "message-context.h" +#include "pixmaps/cursor-3dbox.xpm" +#include "box3d.h" +#include "ui/tools/box3d-tool.h" +#include +#include "xml/repr.h" +#include "xml/node-event-vector.h" +#include "preferences.h" +#include "context-fns.h" +#include "desktop-style.h" +#include "transf_mat_3x4.h" +#include "perspective-line.h" +#include "persp3d.h" +#include "box3d-side.h" +#include "document-private.h" +#include "line-geometry.h" +#include "shape-editor.h" +#include "verbs.h" + +using Inkscape::DocumentUndo; + +#include "tool-factory.h" + +namespace Inkscape { +namespace UI { +namespace Tools { + +namespace { + ToolBase* createBox3dTool() { + return new Box3dTool(); + } + + bool Box3dToolRegistered = ToolFactory::instance().registerObject("/tools/shapes/3dbox", createBox3dTool); +} + +const std::string& Box3dTool::getPrefsPath() { + return Box3dTool::prefsPath; +} + +const std::string Box3dTool::prefsPath = "/tools/shapes/3dbox"; + +Box3dTool::Box3dTool() : ToolBase() { + this->cursor_shape = cursor_3dbox_xpm; + this->hot_x = 4; + this->hot_y = 4; + this->xp = 0; + this->yp = 0; + this->tolerance = 0; + this->within_tolerance = false; + this->item_to_select = NULL; + + this->box3d = NULL; + + this->ctrl_dragged = false; + this->extruded = false; + + this->_vpdrag = NULL; +} + +void Box3dTool::finish() { + sp_canvas_item_ungrab(SP_CANVAS_ITEM(desktop->acetate), GDK_CURRENT_TIME); + this->finishItem(); + this->sel_changed_connection.disconnect(); + + ToolBase::finish(); +} + + +Box3dTool::~Box3dTool() { + this->enableGrDrag(false); + + delete (this->_vpdrag); + this->_vpdrag = NULL; + + this->sel_changed_connection.disconnect(); + + delete this->shape_editor; + this->shape_editor = NULL; + + /* fixme: This is necessary because we do not grab */ + if (this->box3d) { + this->finishItem(); + } +} + +/** + * Callback that processes the "changed" signal on the selection; + * destroys old and creates new knotholder. + */ +void Box3dTool::selection_changed(Inkscape::Selection* selection) { + this->shape_editor->unset_item(SH_KNOTHOLDER); + this->shape_editor->set_item(selection->singleItem(), SH_KNOTHOLDER); + + if (selection->perspList().size() == 1) { + // selecting a single box changes the current perspective + this->desktop->doc()->setCurrentPersp3D(selection->perspList().front()); + } +} + +/* Create a default perspective in document defs if none is present (which can happen, among other + * circumstances, after 'vacuum defs' or when a pre-0.46 file is opened). + */ +static void sp_box3d_context_ensure_persp_in_defs(SPDocument *document) { + SPDefs *defs = document->getDefs(); + + bool has_persp = false; + for ( SPObject *child = defs->firstChild(); child; child = child->getNext() ) { + if (SP_IS_PERSP3D(child)) { + has_persp = true; + break; + } + } + + if (!has_persp) { + document->setCurrentPersp3D(persp3d_create_xml_element (document)); + } +} + +void Box3dTool::setup() { + ToolBase::setup(); + + this->shape_editor = new ShapeEditor(this->desktop); + + SPItem *item = sp_desktop_selection(this->desktop)->singleItem(); + if (item) { + this->shape_editor->set_item(item, SH_KNOTHOLDER); + } + + this->sel_changed_connection.disconnect(); + this->sel_changed_connection = sp_desktop_selection(this->desktop)->connectChanged( + sigc::mem_fun(this, &Box3dTool::selection_changed) + ); + + this->_vpdrag = new Box3D::VPDrag(sp_desktop_document(this->desktop)); + + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + + if (prefs->getBool("/tools/shapes/selcue")) { + this->enableSelectionCue(); + } + + if (prefs->getBool("/tools/shapes/gradientdrag")) { + this->enableGrDrag(); + } +} + +bool Box3dTool::item_handler(SPItem* item, GdkEvent* event) { + gint ret = FALSE; + + switch (event->type) { + case GDK_BUTTON_PRESS: + if ( event->button.button == 1 && !this->space_panning) { + Inkscape::setup_for_drag_start(desktop, this, event); + ret = TRUE; + } + break; + // motion and release are always on root (why?) + default: + break; + } + +// if (((ToolBaseClass *) sp_box3d_context_parent_class)->item_handler) { +// ret = ((ToolBaseClass *) sp_box3d_context_parent_class)->item_handler(event_context, item, event); +// } + // CPPIFY: ret is always overwritten... + ret = ToolBase::item_handler(item, event); + + return ret; +} + +bool Box3dTool::root_handler(GdkEvent* event) { + static bool dragging; + + SPDocument *document = sp_desktop_document (desktop); + Inkscape::Selection *selection = sp_desktop_selection (desktop); + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + int const snaps = prefs->getInt("/options/rotationsnapsperpi/value", 12); + + Persp3D *cur_persp = document->getCurrentPersp3D(); + + this->tolerance = prefs->getIntLimited("/options/dragtolerance/value", 0, 0, 100); + + gint ret = FALSE; + switch (event->type) { + case GDK_BUTTON_PRESS: + if ( event->button.button == 1 && !this->space_panning) { + Geom::Point const button_w(event->button.x, event->button.y); + Geom::Point button_dt(desktop->w2d(button_w)); + + // save drag origin + this->xp = (gint) button_w[Geom::X]; + this->yp = (gint) button_w[Geom::Y]; + this->within_tolerance = true; + + // remember clicked box3d, *not* disregarding groups (since a 3D box is a group), honoring Alt + this->item_to_select = sp_event_context_find_item (desktop, button_w, event->button.state & GDK_MOD1_MASK, event->button.state & GDK_CONTROL_MASK); + + dragging = true; + + SnapManager &m = desktop->namedview->snap_manager; + m.setup(desktop, true, this->box3d); + m.freeSnapReturnByRef(button_dt, Inkscape::SNAPSOURCE_NODE_HANDLE); + m.unSetup(); + this->center = button_dt; + + this->drag_origin = button_dt; + this->drag_ptB = button_dt; + this->drag_ptC = button_dt; + + // This can happen after saving when the last remaining perspective was purged and must be recreated. + if (!cur_persp) { + sp_box3d_context_ensure_persp_in_defs(document); + cur_persp = document->getCurrentPersp3D(); + } + + /* Projective preimages of clicked point under current perspective */ + this->drag_origin_proj = cur_persp->perspective_impl->tmat.preimage (button_dt, 0, Proj::Z); + this->drag_ptB_proj = this->drag_origin_proj; + this->drag_ptC_proj = this->drag_origin_proj; + this->drag_ptC_proj.normalize(); + this->drag_ptC_proj[Proj::Z] = 0.25; + + sp_canvas_item_grab(SP_CANVAS_ITEM(desktop->acetate), + ( GDK_KEY_PRESS_MASK | + GDK_BUTTON_RELEASE_MASK | + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | + GDK_BUTTON_PRESS_MASK ), + NULL, event->button.time); + ret = TRUE; + } + break; + + case GDK_MOTION_NOTIFY: + if (dragging && ( event->motion.state & GDK_BUTTON1_MASK ) && !this->space_panning) { + if ( this->within_tolerance + && ( abs( (gint) event->motion.x - this->xp ) < this->tolerance ) + && ( abs( (gint) event->motion.y - this->yp ) < this->tolerance ) ) { + break; // do not drag if we're within tolerance from origin + } + // Once the user has moved farther than tolerance from the original location + // (indicating they intend to draw, not click), then always process the + // motion notify coordinates as given (no snapping back to origin) + this->within_tolerance = false; + + Geom::Point const motion_w(event->motion.x, + event->motion.y); + Geom::Point motion_dt(desktop->w2d(motion_w)); + + SnapManager &m = desktop->namedview->snap_manager; + m.setup(desktop, true, this->box3d); + m.freeSnapReturnByRef(motion_dt, Inkscape::SNAPSOURCE_NODE_HANDLE); + this->ctrl_dragged = event->motion.state & GDK_CONTROL_MASK; + + if ((event->motion.state & GDK_SHIFT_MASK) && !this->extruded && this->box3d) { + // once shift is pressed, set this->extruded + this->extruded = true; + } + + if (!this->extruded) { + this->drag_ptB = motion_dt; + this->drag_ptC = motion_dt; + + this->drag_ptB_proj = cur_persp->perspective_impl->tmat.preimage (motion_dt, 0, Proj::Z); + this->drag_ptC_proj = this->drag_ptB_proj; + this->drag_ptC_proj.normalize(); + this->drag_ptC_proj[Proj::Z] = 0.25; + } else { + // Without Ctrl, motion of the extruded corner is constrained to the + // perspective line from drag_ptB to vanishing point Y. + if (!this->ctrl_dragged) { + /* snapping */ + Box3D::PerspectiveLine pline (this->drag_ptB, Proj::Z, document->getCurrentPersp3D()); + this->drag_ptC = pline.closest_to (motion_dt); + + this->drag_ptB_proj.normalize(); + this->drag_ptC_proj = cur_persp->perspective_impl->tmat.preimage (this->drag_ptC, this->drag_ptB_proj[Proj::X], Proj::X); + } else { + this->drag_ptC = motion_dt; + + this->drag_ptB_proj.normalize(); + this->drag_ptC_proj = cur_persp->perspective_impl->tmat.preimage (motion_dt, this->drag_ptB_proj[Proj::X], Proj::X); + } + + m.freeSnapReturnByRef(this->drag_ptC, Inkscape::SNAPSOURCE_NODE_HANDLE); + } + + m.unSetup(); + + this->drag(event->motion.state); + + ret = TRUE; + } else if (!sp_event_context_knot_mouseover(this)) { + SnapManager &m = desktop->namedview->snap_manager; + m.setup(desktop); + + Geom::Point const motion_w(event->motion.x, event->motion.y); + Geom::Point motion_dt(desktop->w2d(motion_w)); + m.preSnap(Inkscape::SnapCandidatePoint(motion_dt, Inkscape::SNAPSOURCE_NODE_HANDLE)); + m.unSetup(); + } + break; + + case GDK_BUTTON_RELEASE: + this->xp = this->yp = 0; + + if (event->button.button == 1 && !this->space_panning) { + dragging = false; + sp_event_context_discard_delayed_snap_event(this); + + if (!this->within_tolerance) { + // we've been dragging, finish the box + this->finishItem(); + } else if (this->item_to_select) { + // no dragging, select clicked box3d if any + if (event->button.state & GDK_SHIFT_MASK) { + selection->toggle(this->item_to_select); + } else { + selection->set(this->item_to_select); + } + } else { + // click in an empty space + selection->clear(); + } + + this->item_to_select = NULL; + ret = TRUE; + sp_canvas_item_ungrab(SP_CANVAS_ITEM(desktop->acetate), + event->button.time); + } + break; + + case GDK_KEY_PRESS: + switch (get_group0_keyval (&event->key)) { + case GDK_KEY_Up: + case GDK_KEY_Down: + case GDK_KEY_KP_Up: + case GDK_KEY_KP_Down: + // prevent the zoom field from activation + if (!MOD__CTRL_ONLY(event)) + ret = TRUE; + break; + + case GDK_KEY_bracketright: + persp3d_rotate_VP (document->getCurrentPersp3D(), Proj::X, -180/snaps, MOD__ALT(event)); + DocumentUndo::done(document, SP_VERB_CONTEXT_3DBOX, + _("Change perspective (angle of PLs)")); + ret = true; + break; + + case GDK_KEY_bracketleft: + persp3d_rotate_VP (document->getCurrentPersp3D(), Proj::X, 180/snaps, MOD__ALT(event)); + DocumentUndo::done(document, SP_VERB_CONTEXT_3DBOX, + _("Change perspective (angle of PLs)")); + ret = true; + break; + + case GDK_KEY_parenright: + persp3d_rotate_VP (document->getCurrentPersp3D(), Proj::Y, -180/snaps, MOD__ALT(event)); + DocumentUndo::done(document, SP_VERB_CONTEXT_3DBOX, + _("Change perspective (angle of PLs)")); + ret = true; + break; + + case GDK_KEY_parenleft: + persp3d_rotate_VP (document->getCurrentPersp3D(), Proj::Y, 180/snaps, MOD__ALT(event)); + DocumentUndo::done(document, SP_VERB_CONTEXT_3DBOX, + _("Change perspective (angle of PLs)")); + ret = true; + break; + + case GDK_KEY_braceright: + persp3d_rotate_VP (document->getCurrentPersp3D(), Proj::Z, -180/snaps, MOD__ALT(event)); + DocumentUndo::done(document, SP_VERB_CONTEXT_3DBOX, + _("Change perspective (angle of PLs)")); + ret = true; + break; + + case GDK_KEY_braceleft: + persp3d_rotate_VP (document->getCurrentPersp3D(), Proj::Z, 180/snaps, MOD__ALT(event)); + DocumentUndo::done(document, SP_VERB_CONTEXT_3DBOX, + _("Change perspective (angle of PLs)")); + ret = true; + break; + + /* TODO: what is this??? + case GDK_O: + if (MOD__CTRL(event) && MOD__SHIFT(event)) { + Box3D::create_canvas_point(persp3d_get_VP(document()->getCurrentPersp3D(), Proj::W).affine(), + 6, 0xff00ff00); + } + ret = true; + break; + */ + + case GDK_KEY_g: + case GDK_KEY_G: + if (MOD__SHIFT_ONLY(event)) { + sp_selection_to_guides(desktop); + ret = true; + } + break; + + case GDK_KEY_p: + case GDK_KEY_P: + if (MOD__SHIFT_ONLY(event)) { + if (document->getCurrentPersp3D()) { + persp3d_print_debugging_info (document->getCurrentPersp3D()); + } + ret = true; + } + break; + + case GDK_KEY_x: + case GDK_KEY_X: + if (MOD__ALT_ONLY(event)) { + desktop->setToolboxFocusTo ("altx-box3d"); + ret = TRUE; + } + if (MOD__SHIFT_ONLY(event)) { + persp3d_toggle_VPs(selection->perspList(), Proj::X); + this->_vpdrag->updateLines(); // FIXME: Shouldn't this be done automatically? + ret = true; + } + break; + + case GDK_KEY_y: + case GDK_KEY_Y: + if (MOD__SHIFT_ONLY(event)) { + persp3d_toggle_VPs(selection->perspList(), Proj::Y); + this->_vpdrag->updateLines(); // FIXME: Shouldn't this be done automatically? + ret = true; + } + break; + + case GDK_KEY_z: + case GDK_KEY_Z: + if (MOD__SHIFT_ONLY(event)) { + persp3d_toggle_VPs(selection->perspList(), Proj::Z); + this->_vpdrag->updateLines(); // FIXME: Shouldn't this be done automatically? + ret = true; + } + break; + + case GDK_KEY_Escape: + sp_desktop_selection(desktop)->clear(); + //TODO: make dragging escapable by Esc + break; + + case GDK_KEY_space: + if (dragging) { + sp_canvas_item_ungrab(SP_CANVAS_ITEM(desktop->acetate), + event->button.time); + dragging = false; + sp_event_context_discard_delayed_snap_event(this); + if (!this->within_tolerance) { + // we've been dragging, finish the box + this->finishItem(); + } + // do not return true, so that space would work switching to selector + } + break; + + case GDK_KEY_Delete: + case GDK_KEY_KP_Delete: + case GDK_KEY_BackSpace: + ret = this->deleteSelectedDrag(MOD__CTRL_ONLY(event)); + break; + + default: + break; + } + break; + + default: + break; + } + + if (!ret) { + ret = ToolBase::root_handler(event); + } + + return ret; +} + +void Box3dTool::drag(guint state) { + if (!this->box3d) { + if (Inkscape::have_viable_layer(desktop, this->message_context) == false) { + return; + } + + // Create object + SPBox3D *box3d = SPBox3D::createBox3D((SPItem*)desktop->currentLayer()); + + // Set style + desktop->applyCurrentOrToolStyle(box3d, "/tools/shapes/3dbox", false); + + this->box3d = box3d; + + // TODO: Incorporate this in box3d-side.cpp! + for (int i = 0; i < 6; ++i) { + Box3DSide *side = Box3DSide::createBox3DSide(box3d); + + guint desc = Box3D::int_to_face(i); + + Box3D::Axis plane = (Box3D::Axis) (desc & 0x7); + plane = (Box3D::is_plane(plane) ? plane : Box3D::orth_plane_or_axis(plane)); + side->dir1 = Box3D::extract_first_axis_direction(plane); + side->dir2 = Box3D::extract_second_axis_direction(plane); + side->front_or_rear = (Box3D::FrontOrRear) (desc & 0x8); + + // Set style + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + + Glib::ustring descr = "/desktop/"; + descr += box3d_side_axes_string(side); + descr += "/style"; + + Glib::ustring cur_style = prefs->getString(descr); + + bool use_current = prefs->getBool("/tools/shapes/3dbox/usecurrent", false); + + if (use_current && !cur_style.empty()) { + // use last used style + side->setAttribute("style", cur_style.data()); + } else { + // use default style + GString *pstring = g_string_new(""); + g_string_printf (pstring, "/tools/shapes/3dbox/%s", box3d_side_axes_string(side)); + desktop->applyCurrentOrToolStyle (side, pstring->str, false); + } + + side->updateRepr(); // calls box3d_side_write() and updates, e.g., the axes string description + } + + box3d_set_z_orders(this->box3d); + this->box3d->updateRepr(); + + // TODO: It would be nice to show the VPs during dragging, but since there is no selection + // at this point (only after finishing the box), we must do this "manually" + /* this._vpdrag->updateDraggers(); */ + + desktop->canvas->forceFullRedrawAfterInterruptions(5); + } + + g_assert(this->box3d); + + this->box3d->orig_corner0 = this->drag_origin_proj; + this->box3d->orig_corner7 = this->drag_ptC_proj; + + box3d_check_for_swapped_coords(this->box3d); + + /* we need to call this from here (instead of from box3d_position_set(), for example) + because z-order setting must not interfere with display updates during undo/redo */ + box3d_set_z_orders (this->box3d); + + box3d_position_set(this->box3d); + + // status text + this->message_context->setF(Inkscape::NORMAL_MESSAGE, "%s", _("3D Box; with Shift to extrude along the Z axis")); +} + +void Box3dTool::finishItem() { + this->message_context->clear(); + this->ctrl_dragged = false; + this->extruded = false; + + if (this->box3d != NULL) { + SPDocument *doc = sp_desktop_document(this->desktop); + + if (!doc || !doc->getCurrentPersp3D()) { + return; + } + + this->box3d->orig_corner0 = this->drag_origin_proj; + this->box3d->orig_corner7 = this->drag_ptC_proj; + + this->box3d->updateRepr(); + + box3d_relabel_corners(this->box3d); + + desktop->canvas->endForcedFullRedraws(); + + sp_desktop_selection(desktop)->set(this->box3d); + DocumentUndo::done(sp_desktop_document(desktop), SP_VERB_CONTEXT_3DBOX, + _("Create 3D box")); + + this->box3d = NULL; + } +} + +} +} +} + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/ui/tools/box3d-tool.h b/src/ui/tools/box3d-tool.h new file mode 100644 index 000000000..99bf99a7a --- /dev/null +++ b/src/ui/tools/box3d-tool.h @@ -0,0 +1,95 @@ +#ifndef __SP_BOX3D_CONTEXT_H__ +#define __SP_BOX3D_CONTEXT_H__ + +/* + * 3D box drawing context + * + * Author: + * Lauris Kaplinski + * + * Copyright (C) 2000 Lauris Kaplinski + * Copyright (C) 2000-2001 Ximian, Inc. + * Copyright (C) 2002 Lauris Kaplinski + * Copyright (C) 2007 Maximilian Albert + * + * Released under GNU GPL + */ + +#include +#include +#include "ui/tools/tool-base.h" +#include "proj_pt.h" +#include "vanishing-point.h" + +#include "box3d.h" + +#define SP_BOX3D_CONTEXT(obj) (dynamic_cast((Inkscape::UI::Tools::ToolBase*)obj)) +#define SP_IS_BOX3D_CONTEXT(obj) (dynamic_cast((const Inkscape::UI::Tools::ToolBase*)obj) != NULL) + +namespace Inkscape { +namespace UI { +namespace Tools { + +class Box3dTool : public ToolBase { +public: + Box3dTool(); + virtual ~Box3dTool(); + + Box3D::VPDrag * _vpdrag; + + static const std::string prefsPath; + + virtual void setup(); + virtual void finish(); + virtual bool root_handler(GdkEvent* event); + virtual bool item_handler(SPItem* item, GdkEvent* event); + + virtual const std::string& getPrefsPath(); + +private: + SPBox3D* box3d; + Geom::Point center; + + /** + * save three corners while dragging: + * 1) the starting point (already done by the event_context) + * 2) drag_ptB --> the opposite corner of the front face (before pressing shift) + * 3) drag_ptC --> the "extruded corner" (which coincides with the mouse pointer location + * if we are ctrl-dragging but is constrained to the perspective line from drag_ptC + * to the vanishing point Y otherwise) + */ + Geom::Point drag_origin; + Geom::Point drag_ptB; + Geom::Point drag_ptC; + + Proj::Pt3 drag_origin_proj; + Proj::Pt3 drag_ptB_proj; + Proj::Pt3 drag_ptC_proj; + + bool ctrl_dragged; /* whether we are ctrl-dragging */ + bool extruded; /* whether shift-dragging already occured (i.e. the box is already extruded) */ + + sigc::connection sel_changed_connection; + + void selection_changed(Inkscape::Selection* selection); + + void drag(guint state); + void finishItem(); +}; + +} +} +} + +#endif /* __SP_BOX3D_CONTEXT_H__ */ + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/ui/tools/calligraphic-tool.cpp b/src/ui/tools/calligraphic-tool.cpp new file mode 100644 index 000000000..2c5e6561c --- /dev/null +++ b/src/ui/tools/calligraphic-tool.cpp @@ -0,0 +1,1216 @@ +/* + * Handwriting-like drawing mode + * + * Authors: + * Mitsuru Oka + * Lauris Kaplinski + * bulia byak + * MenTaLguY + * Abhishek Sharma + * Jon A. Cruz + * + * The original dynadraw code: + * Paul Haeberli + * + * Copyright (C) 1998 The Free Software Foundation + * Copyright (C) 1999-2005 authors + * Copyright (C) 2001-2002 Ximian, Inc. + * Copyright (C) 2005-2007 bulia byak + * Copyright (C) 2006 MenTaLguY + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#define noDYNA_DRAW_VERBOSE + +#include "config.h" + +#include +#include +#include +#include +#include +#include + +#include "svg/svg.h" +#include "display/canvas-bpath.h" +#include "display/cairo-utils.h" +#include <2geom/math-utils.h> +#include <2geom/pathvector.h> +#include <2geom/bezier-utils.h> +#include <2geom/circle.h> +#include "display/curve.h" +#include +#include "macros.h" +#include "document.h" +#include "document-undo.h" +#include "selection.h" +#include "desktop.h" +#include "desktop-events.h" +#include "desktop-handles.h" +#include "desktop-style.h" +#include "message-context.h" +#include "preferences.h" +#include "pixmaps/cursor-calligraphy.xpm" +#include "xml/repr.h" +#include "context-fns.h" +#include "sp-item.h" +#include "inkscape.h" +#include "color.h" +#include "splivarot.h" +#include "sp-item-group.h" +#include "sp-shape.h" +#include "sp-path.h" +#include "sp-text.h" +#include "display/sp-canvas.h" +#include "display/canvas-bpath.h" +#include "display/canvas-arena.h" +#include "livarot/Shape.h" +#include "verbs.h" + +#include "ui/tools/calligraphic-tool.h" + +using Inkscape::DocumentUndo; + +#define DDC_RED_RGBA 0xff0000ff + +#define TOLERANCE_CALLIGRAPHIC 0.1 + +#define DYNA_EPSILON 0.5e-6 +#define DYNA_EPSILON_START 0.5e-2 +#define DYNA_VEL_START 1e-5 + +#define DYNA_MIN_WIDTH 1.0e-6 + +#include "tool-factory.h" + +namespace Inkscape { +namespace UI { +namespace Tools { + +static void add_cap(SPCurve *curve, Geom::Point const &from, Geom::Point const &to, double rounding); + +namespace { + ToolBase* createCalligraphicContext() { + return new CalligraphicTool(); + } + + bool calligraphicContextRegistered = ToolFactory::instance().registerObject("/tools/calligraphic", createCalligraphicContext); +} + +const std::string& CalligraphicTool::getPrefsPath() { + return CalligraphicTool::prefsPath; +} + + +const std::string CalligraphicTool::prefsPath = "/tools/calligraphic"; + +CalligraphicTool::CalligraphicTool() : DynamicBase() { + this->cursor_shape = cursor_calligraphy_xpm; + this->hot_x = 4; + this->hot_y = 4; + + this->vel_thin = 0.1; + this->flatness = 0.9; + this->cap_rounding = 0.0; + + this->abs_width = false; + this->keep_selected = true; + + this->hatch_spacing = 0; + this->hatch_spacing_step = 0; + + this->hatch_last_nearest = Geom::Point(0,0); + this->hatch_last_pointer = Geom::Point(0,0); + this->hatch_escaped = false; + this->hatch_area = NULL; + this->hatch_item = NULL; + this->hatch_livarot_path = NULL; + + this->trace_bg = false; + this->just_started_drawing = false; +} + +CalligraphicTool::~CalligraphicTool() { + if (this->hatch_area) { + sp_canvas_item_destroy(this->hatch_area); + this->hatch_area = NULL; + } +} + +void CalligraphicTool::setup() { + DynamicBase::setup(); + + this->accumulated = new SPCurve(); + this->currentcurve = new SPCurve(); + + this->cal1 = new SPCurve(); + this->cal2 = new SPCurve(); + + this->currentshape = sp_canvas_item_new(sp_desktop_sketch(this->desktop), SP_TYPE_CANVAS_BPATH, NULL); + sp_canvas_bpath_set_fill(SP_CANVAS_BPATH(this->currentshape), DDC_RED_RGBA, SP_WIND_RULE_EVENODD); + sp_canvas_bpath_set_stroke(SP_CANVAS_BPATH(this->currentshape), 0x00000000, 1.0, SP_STROKE_LINEJOIN_MITER, SP_STROKE_LINECAP_BUTT); + + /* fixme: Cannot we cascade it to root more clearly? */ + g_signal_connect(G_OBJECT(this->currentshape), "event", G_CALLBACK(sp_desktop_root_handler), this->desktop); + + { + /* TODO: have a look at DropperTool::setup where the same is done.. generalize? */ + Geom::PathVector path; + Geom::Circle(0, 0, 1).getPath(path); + + SPCurve *c = new SPCurve(path); + + this->hatch_area = sp_canvas_bpath_new(sp_desktop_controls(this->desktop), c); + + c->unref(); + + sp_canvas_bpath_set_fill(SP_CANVAS_BPATH(this->hatch_area), 0x00000000,(SPWindRule)0); + sp_canvas_bpath_set_stroke(SP_CANVAS_BPATH(this->hatch_area), 0x0000007f, 1.0, SP_STROKE_LINEJOIN_MITER, SP_STROKE_LINECAP_BUTT); + sp_canvas_item_hide(this->hatch_area); + } + + sp_event_context_read(this, "mass"); + sp_event_context_read(this, "wiggle"); + sp_event_context_read(this, "angle"); + sp_event_context_read(this, "width"); + sp_event_context_read(this, "thinning"); + sp_event_context_read(this, "tremor"); + sp_event_context_read(this, "flatness"); + sp_event_context_read(this, "tracebackground"); + sp_event_context_read(this, "usepressure"); + sp_event_context_read(this, "usetilt"); + sp_event_context_read(this, "abs_width"); + sp_event_context_read(this, "keep_selected"); + sp_event_context_read(this, "cap_rounding"); + + this->is_drawing = false; + + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + if (prefs->getBool("/tools/calligraphic/selcue")) { + this->enableSelectionCue(); + } +} + +void CalligraphicTool::set(const Inkscape::Preferences::Entry& val) { + Glib::ustring path = val.getEntryName(); + + if (path == "tracebackground") { + this->trace_bg = val.getBool(); + } else if (path == "keep_selected") { + this->keep_selected = val.getBool(); + } else { + //pass on up to parent class to handle common attributes. + DynamicBase::set(val); + } + + //g_print("DDC: %g %g %g %g\n", ddc->mass, ddc->drag, ddc->angle, ddc->width); +} + +static double +flerp(double f0, double f1, double p) +{ + return f0 + ( f1 - f0 ) * p; +} + +///* Get normalized point */ +//Geom::Point CalligraphicTool::getNormalizedPoint(Geom::Point v) const { +// Geom::Rect drect = desktop->get_display_area(); +// +// double const max = MAX ( drect.dimensions()[Geom::X], drect.dimensions()[Geom::Y] ); +// +// return Geom::Point(( v[Geom::X] - drect.min()[Geom::X] ) / max, ( v[Geom::Y] - drect.min()[Geom::Y] ) / max); +//} +// +///* Get view point */ +//Geom::Point CalligraphicTool::getViewPoint(Geom::Point n) const { +// Geom::Rect drect = desktop->get_display_area(); +// +// double const max = MAX ( drect.dimensions()[Geom::X], drect.dimensions()[Geom::Y] ); +// +// return Geom::Point(n[Geom::X] * max + drect.min()[Geom::X], n[Geom::Y] * max + drect.min()[Geom::Y]); +//} + +void CalligraphicTool::reset(Geom::Point p) { + this->last = this->cur = this->getNormalizedPoint(p); + + this->vel = Geom::Point(0,0); + this->vel_max = 0; + this->acc = Geom::Point(0,0); + this->ang = Geom::Point(0,0); + this->del = Geom::Point(0,0); +} + +void CalligraphicTool::extinput(GdkEvent *event) { + if (gdk_event_get_axis (event, GDK_AXIS_PRESSURE, &this->pressure)) { + this->pressure = CLAMP (this->pressure, DDC_MIN_PRESSURE, DDC_MAX_PRESSURE); + } else { + this->pressure = DDC_DEFAULT_PRESSURE; + } + + if (gdk_event_get_axis (event, GDK_AXIS_XTILT, &this->xtilt)) { + this->xtilt = CLAMP (this->xtilt, DDC_MIN_TILT, DDC_MAX_TILT); + } else { + this->xtilt = DDC_DEFAULT_TILT; + } + + if (gdk_event_get_axis (event, GDK_AXIS_YTILT, &this->ytilt)) { + this->ytilt = CLAMP (this->ytilt, DDC_MIN_TILT, DDC_MAX_TILT); + } else { + this->ytilt = DDC_DEFAULT_TILT; + } +} + + +bool CalligraphicTool::apply(Geom::Point p) { + Geom::Point n = this->getNormalizedPoint(p); + + /* Calculate mass and drag */ + double const mass = flerp(1.0, 160.0, this->mass); + double const drag = flerp(0.0, 0.5, this->drag * this->drag); + + /* Calculate force and acceleration */ + Geom::Point force = n - this->cur; + + // If force is below the absolute threshold DYNA_EPSILON, + // or we haven't yet reached DYNA_VEL_START (i.e. at the beginning of stroke) + // _and_ the force is below the (higher) DYNA_EPSILON_START threshold, + // discard this move. + // This prevents flips, blobs, and jerks caused by microscopic tremor of the tablet pen, + // especially bothersome at the start of the stroke where we don't yet have the inertia to + // smooth them out. + if ( Geom::L2(force) < DYNA_EPSILON || (this->vel_max < DYNA_VEL_START && Geom::L2(force) < DYNA_EPSILON_START)) { + return FALSE; + } + + this->acc = force / mass; + + /* Calculate new velocity */ + this->vel += this->acc; + + if (Geom::L2(this->vel) > this->vel_max) + this->vel_max = Geom::L2(this->vel); + + /* Calculate angle of drawing tool */ + + double a1; + if (this->usetilt) { + // 1a. calculate nib angle from input device tilt: + gdouble length = std::sqrt(this->xtilt*this->xtilt + this->ytilt*this->ytilt);; + + if (length > 0) { + Geom::Point ang1 = Geom::Point(this->ytilt/length, this->xtilt/length); + a1 = atan2(ang1); + } + else + a1 = 0.0; + } + else { + // 1b. fixed dc->angle (absolutely flat nib): + double const radians = ( (this->angle - 90) / 180.0 ) * M_PI; + Geom::Point ang1 = Geom::Point(-sin(radians), cos(radians)); + a1 = atan2(ang1); + } + + // 2. perpendicular to dc->vel (absolutely non-flat nib): + gdouble const mag_vel = Geom::L2(this->vel); + if ( mag_vel < DYNA_EPSILON ) { + return FALSE; + } + Geom::Point ang2 = Geom::rot90(this->vel) / mag_vel; + + // 3. Average them using flatness parameter: + // calculate angles + double a2 = atan2(ang2); + // flip a2 to force it to be in the same half-circle as a1 + bool flipped = false; + if (fabs (a2-a1) > 0.5*M_PI) { + a2 += M_PI; + flipped = true; + } + // normalize a2 + if (a2 > M_PI) + a2 -= 2*M_PI; + if (a2 < -M_PI) + a2 += 2*M_PI; + // find the flatness-weighted bisector angle, unflip if a2 was flipped + // FIXME: when dc->vel is oscillating around the fixed angle, the new_ang flips back and forth. How to avoid this? + double new_ang = a1 + (1 - this->flatness) * (a2 - a1) - (flipped? M_PI : 0); + + // Try to detect a sudden flip when the new angle differs too much from the previous for the + // current velocity; in that case discard this move + double angle_delta = Geom::L2(Geom::Point (cos (new_ang), sin (new_ang)) - this->ang); + if ( angle_delta / Geom::L2(this->vel) > 4000 ) { + return FALSE; + } + + // convert to point + this->ang = Geom::Point (cos (new_ang), sin (new_ang)); + +// g_print ("force %g acc %g vel_max %g vel %g a1 %g a2 %g new_ang %g\n", Geom::L2(force), Geom::L2(dc->acc), dc->vel_max, Geom::L2(dc->vel), a1, a2, new_ang); + + /* Apply drag */ + this->vel *= 1.0 - drag; + + /* Update position */ + this->last = this->cur; + this->cur += this->vel; + + return TRUE; +} + +void CalligraphicTool::brush() { + g_assert( this->npoints >= 0 && this->npoints < SAMPLING_SIZE ); + + // How much velocity thins strokestyle + double vel_thin = flerp (0, 160, this->vel_thin); + + // Influence of pressure on thickness + double pressure_thick = (this->usepressure ? this->pressure : 1.0); + + // get the real brush point, not the same as pointer (affected by hatch tracking and/or mass + // drag) + Geom::Point brush = this->getViewPoint(this->cur); + Geom::Point brush_w = SP_EVENT_CONTEXT(this)->desktop->d2w(brush); + + double trace_thick = 1; + if (this->trace_bg) { + // pick single pixel + double R, G, B, A; + Geom::IntRect area = Geom::IntRect::from_xywh(brush_w.floor(), Geom::IntPoint(1, 1)); + cairo_surface_t *s = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 1, 1); + sp_canvas_arena_render_surface(SP_CANVAS_ARENA(sp_desktop_drawing(SP_EVENT_CONTEXT(this)->desktop)), s, area); + ink_cairo_surface_average_color_premul(s, R, G, B, A); + cairo_surface_destroy(s); + double max = MAX (MAX (R, G), B); + double min = MIN (MIN (R, G), B); + double L = A * (max + min)/2 + (1 - A); // blend with white bg + trace_thick = 1 - L; + //g_print ("L %g thick %g\n", L, trace_thick); + } + + double width = (pressure_thick * trace_thick - vel_thin * Geom::L2(this->vel)) * this->width; + + double tremble_left = 0, tremble_right = 0; + if (this->tremor > 0) { + // obtain two normally distributed random variables, using polar Box-Muller transform + double x1, x2, w, y1, y2; + do { + x1 = 2.0 * g_random_double_range(0,1) - 1.0; + x2 = 2.0 * g_random_double_range(0,1) - 1.0; + w = x1 * x1 + x2 * x2; + } while ( w >= 1.0 ); + w = sqrt( (-2.0 * log( w ) ) / w ); + y1 = x1 * w; + y2 = x2 * w; + + // deflect both left and right edges randomly and independently, so that: + // (1) dc->tremor=1 corresponds to sigma=1, decreasing dc->tremor narrows the bell curve; + // (2) deflection depends on width, but is upped for small widths for better visual uniformity across widths; + // (3) deflection somewhat depends on speed, to prevent fast strokes looking + // comparatively smooth and slow ones excessively jittery + tremble_left = (y1)*this->tremor * (0.15 + 0.8*width) * (0.35 + 14*Geom::L2(this->vel)); + tremble_right = (y2)*this->tremor * (0.15 + 0.8*width) * (0.35 + 14*Geom::L2(this->vel)); + } + + if ( width < 0.02 * this->width ) { + width = 0.02 * this->width; + } + + double dezoomify_factor = 0.05 * 1000; + if (!this->abs_width) { + dezoomify_factor /= SP_EVENT_CONTEXT(this)->desktop->current_zoom(); + } + + Geom::Point del_left = dezoomify_factor * (width + tremble_left) * this->ang; + Geom::Point del_right = dezoomify_factor * (width + tremble_right) * this->ang; + + this->point1[this->npoints] = brush + del_left; + this->point2[this->npoints] = brush - del_right; + + this->del = 0.5*(del_left + del_right); + + this->npoints++; +} + +static void +sp_ddc_update_toolbox (SPDesktop *desktop, const gchar *id, double value) +{ + desktop->setToolboxAdjustmentValue (id, value); +} + +void CalligraphicTool::cancel() { + this->dragging = false; + this->is_drawing = false; + + sp_canvas_item_ungrab(SP_CANVAS_ITEM(desktop->acetate), 0); + + /* Remove all temporary line segments */ + while (this->segments) { + sp_canvas_item_destroy(SP_CANVAS_ITEM(this->segments->data)); + this->segments = g_slist_remove(this->segments, this->segments->data); + } + + /* reset accumulated curve */ + this->accumulated->reset(); + this->clear_current(); + + if (this->repr) { + this->repr = NULL; + } +} + +bool CalligraphicTool::root_handler(GdkEvent* event) { + gint ret = FALSE; + + switch (event->type) { + case GDK_BUTTON_PRESS: + if (event->button.button == 1 && !this->space_panning) { + if (Inkscape::have_viable_layer(desktop, this->message_context) == false) { + return TRUE; + } + + this->accumulated->reset(); + + if (this->repr) { + this->repr = NULL; + } + + /* initialize first point */ + this->npoints = 0; + + sp_canvas_item_grab(SP_CANVAS_ITEM(desktop->acetate), + ( GDK_KEY_PRESS_MASK | + GDK_BUTTON_RELEASE_MASK | + GDK_POINTER_MOTION_MASK | + GDK_BUTTON_PRESS_MASK ), + NULL, + event->button.time); + + ret = TRUE; + + desktop->canvas->forceFullRedrawAfterInterruptions(3); + this->is_drawing = true; + this->just_started_drawing = true; + } + break; + case GDK_MOTION_NOTIFY: + { + Geom::Point const motion_w(event->motion.x, + event->motion.y); + Geom::Point motion_dt(desktop->w2d(motion_w)); + this->extinput(event); + + this->message_context->clear(); + + // for hatching: + double hatch_dist = 0; + Geom::Point hatch_unit_vector(0,0); + Geom::Point nearest(0,0); + Geom::Point pointer(0,0); + Geom::Affine motion_to_curve(Geom::identity()); + + if (event->motion.state & GDK_CONTROL_MASK) { // hatching - sense the item + + SPItem *selected = sp_desktop_selection(desktop)->singleItem(); + if (selected && (SP_IS_SHAPE(selected) || SP_IS_TEXT(selected))) { + // One item selected, and it's a path; + // let's try to track it as a guide + + if (selected != this->hatch_item) { + this->hatch_item = selected; + if (this->hatch_livarot_path) + delete this->hatch_livarot_path; + this->hatch_livarot_path = Path_for_item (this->hatch_item, true, true); + this->hatch_livarot_path->ConvertWithBackData(0.01); + } + + // calculate pointer point in the guide item's coords + motion_to_curve = selected->dt2i_affine() * selected->i2doc_affine(); + pointer = motion_dt * motion_to_curve; + + // calculate the nearest point on the guide path + boost::optional position = get_nearest_position_on_Path(this->hatch_livarot_path, pointer); + nearest = get_point_on_Path(this->hatch_livarot_path, position->piece, position->t); + + + // distance from pointer to nearest + hatch_dist = Geom::L2(pointer - nearest); + // unit-length vector + hatch_unit_vector = (pointer - nearest)/hatch_dist; + + this->message_context->set(Inkscape::NORMAL_MESSAGE, _("Guide path selected; start drawing along the guide with Ctrl")); + } else { + this->message_context->set(Inkscape::NORMAL_MESSAGE, _("Select a guide path to track with Ctrl")); + } + } + + if ( this->is_drawing && (event->motion.state & GDK_BUTTON1_MASK) && !this->space_panning) { + this->dragging = TRUE; + + if (event->motion.state & GDK_CONTROL_MASK && this->hatch_item) { // hatching + +#define HATCH_VECTOR_ELEMENTS 12 +#define INERTIA_ELEMENTS 24 +#define SPEED_ELEMENTS 12 +#define SPEED_MIN 0.3 +#define SPEED_NORMAL 0.35 +#define INERTIA_FORCE 0.5 + + // speed is the movement of the nearest point along the guide path, divided by + // the movement of the pointer at the same period; it is averaged for the last + // SPEED_ELEMENTS motion events. Normally, as you track the guide path, speed + // is about 1, i.e. the nearest point on the path is moved by about the same + // distance as the pointer. If the speed starts to decrease, we are losing + // contact with the guide; if it drops below SPEED_MIN, we are on our own and + // not attracted to guide anymore. Most often this happens when you have + // tracked to the end of a guide calligraphic stroke and keep moving + // further. We try to handle this situation gracefully: not stick with the + // guide forever but let go of it smoothly and without sharp jerks (non-zero + // mass recommended; with zero mass, jerks are still quite noticeable). + + double speed = 1; + if (Geom::L2(this->hatch_last_nearest) != 0) { + // the distance nearest moved since the last motion event + double nearest_moved = Geom::L2(nearest - this->hatch_last_nearest); + // the distance pointer moved since the last motion event + double pointer_moved = Geom::L2(pointer - this->hatch_last_pointer); + // store them in stacks limited to SPEED_ELEMENTS + this->hatch_nearest_past.push_front(nearest_moved); + if (this->hatch_nearest_past.size() > SPEED_ELEMENTS) + this->hatch_nearest_past.pop_back(); + this->hatch_pointer_past.push_front(pointer_moved); + if (this->hatch_pointer_past.size() > SPEED_ELEMENTS) + this->hatch_pointer_past.pop_back(); + + // If the stacks are full, + if (this->hatch_nearest_past.size() == SPEED_ELEMENTS) { + // calculate the sums of all stored movements + double nearest_sum = std::accumulate (this->hatch_nearest_past.begin(), this->hatch_nearest_past.end(), 0.0); + double pointer_sum = std::accumulate (this->hatch_pointer_past.begin(), this->hatch_pointer_past.end(), 0.0); + // and divide to get the speed + speed = nearest_sum/pointer_sum; + //g_print ("nearest sum %g pointer_sum %g speed %g\n", nearest_sum, pointer_sum, speed); + } + } + + if ( this->hatch_escaped // already escaped, do not reattach + || (speed < SPEED_MIN) // stuck; most likely reached end of traced stroke + || (this->hatch_spacing > 0 && hatch_dist > 50 * this->hatch_spacing) // went too far from the guide + ) { + // We are NOT attracted to the guide! + + //g_print ("\nlast_nearest %g %g nearest %g %g pointer %g %g pos %d %g\n", dc->last_nearest[Geom::X], dc->last_nearest[Geom::Y], nearest[Geom::X], nearest[Geom::Y], pointer[Geom::X], pointer[Geom::Y], position->piece, position->t); + + // Remember hatch_escaped so we don't get + // attracted again until the end of this stroke + this->hatch_escaped = true; + + if (this->inertia_vectors.size() >= INERTIA_ELEMENTS/2) { // move by inertia + Geom::Point moved_past_escape = motion_dt - this->inertia_vectors.front(); + Geom::Point inertia = + this->inertia_vectors.front() - this->inertia_vectors.back(); + + double dot = Geom::dot (moved_past_escape, inertia); + dot /= Geom::L2(moved_past_escape) * Geom::L2(inertia); + + if (dot > 0) { // mouse is still moving in approx the same direction + Geom::Point should_have_moved = + (inertia) * (1/Geom::L2(inertia)) * Geom::L2(moved_past_escape); + motion_dt = this->inertia_vectors.front() + + (INERTIA_FORCE * should_have_moved + (1 - INERTIA_FORCE) * moved_past_escape); + } + } + + } else { + + // Calculate angle cosine of this vector-to-guide and all past vectors + // summed, to detect if we accidentally flipped to the other side of the + // guide + Geom::Point hatch_vector_accumulated = std::accumulate + (this->hatch_vectors.begin(), this->hatch_vectors.end(), Geom::Point(0,0)); + double dot = Geom::dot (pointer - nearest, hatch_vector_accumulated); + dot /= Geom::L2(pointer - nearest) * Geom::L2(hatch_vector_accumulated); + + if (this->hatch_spacing != 0) { // spacing was already set + double target; + if (speed > SPEED_NORMAL) { + // all ok, strictly obey the spacing + target = this->hatch_spacing; + } else { + // looks like we're starting to lose speed, + // so _gradually_ let go attraction to prevent jerks + target = (this->hatch_spacing * speed + hatch_dist * (SPEED_NORMAL - speed))/SPEED_NORMAL; + } + if (!IS_NAN(dot) && dot < -0.5) {// flip + target = -target; + } + + // This is the track pointer that we will use instead of the real one + Geom::Point new_pointer = nearest + target * hatch_unit_vector; + + // some limited feedback: allow persistent pulling to slightly change + // the spacing + this->hatch_spacing += (hatch_dist - this->hatch_spacing)/3500; + + // return it to the desktop coords + motion_dt = new_pointer * motion_to_curve.inverse(); + + if (speed >= SPEED_NORMAL) { + this->inertia_vectors.push_front(motion_dt); + if (this->inertia_vectors.size() > INERTIA_ELEMENTS) + this->inertia_vectors.pop_back(); + } + + } else { + // this is the first motion event, set the dist + this->hatch_spacing = hatch_dist; + } + + // remember last points + this->hatch_last_pointer = pointer; + this->hatch_last_nearest = nearest; + + this->hatch_vectors.push_front(pointer - nearest); + if (this->hatch_vectors.size() > HATCH_VECTOR_ELEMENTS) + this->hatch_vectors.pop_back(); + } + + this->message_context->set(Inkscape::NORMAL_MESSAGE, this->hatch_escaped? _("Tracking: connection to guide path lost!") : _("Tracking a guide path")); + + } else { + this->message_context->set(Inkscape::NORMAL_MESSAGE, _("Drawing a calligraphic stroke")); + } + + if (this->just_started_drawing) { + this->just_started_drawing = false; + this->reset(motion_dt); + } + + if (!this->apply(motion_dt)) { + ret = TRUE; + break; + } + + if ( this->cur != this->last ) { + this->brush(); + g_assert( this->npoints > 0 ); + this->fit_and_split(false); + } + ret = TRUE; + } + + // Draw the hatching circle if necessary + if (event->motion.state & GDK_CONTROL_MASK) { + if (this->hatch_spacing == 0 && hatch_dist != 0) { + // Haven't set spacing yet: gray, center free, update radius live + Geom::Point c = desktop->w2d(motion_w); + Geom::Affine const sm (Geom::Scale(hatch_dist, hatch_dist) * Geom::Translate(c)); + sp_canvas_item_affine_absolute(this->hatch_area, sm); + sp_canvas_bpath_set_stroke(SP_CANVAS_BPATH(this->hatch_area), 0x7f7f7fff, 1.0, SP_STROKE_LINEJOIN_MITER, SP_STROKE_LINECAP_BUTT); + sp_canvas_item_show(this->hatch_area); + } else if (this->dragging && !this->hatch_escaped) { + // Tracking: green, center snapped, fixed radius + Geom::Point c = motion_dt; + Geom::Affine const sm (Geom::Scale(this->hatch_spacing, this->hatch_spacing) * Geom::Translate(c)); + sp_canvas_item_affine_absolute(this->hatch_area, sm); + sp_canvas_bpath_set_stroke(SP_CANVAS_BPATH(this->hatch_area), 0x00FF00ff, 1.0, SP_STROKE_LINEJOIN_MITER, SP_STROKE_LINECAP_BUTT); + sp_canvas_item_show(this->hatch_area); + } else if (this->dragging && this->hatch_escaped) { + // Tracking escaped: red, center free, fixed radius + Geom::Point c = motion_dt; + Geom::Affine const sm (Geom::Scale(this->hatch_spacing, this->hatch_spacing) * Geom::Translate(c)); + + sp_canvas_item_affine_absolute(this->hatch_area, sm); + sp_canvas_bpath_set_stroke(SP_CANVAS_BPATH(this->hatch_area), 0xFF0000ff, 1.0, SP_STROKE_LINEJOIN_MITER, SP_STROKE_LINECAP_BUTT); + sp_canvas_item_show(this->hatch_area); + } else { + // Not drawing but spacing set: gray, center snapped, fixed radius + Geom::Point c = (nearest + this->hatch_spacing * hatch_unit_vector) * motion_to_curve.inverse(); + if (!IS_NAN(c[Geom::X]) && !IS_NAN(c[Geom::Y])) { + Geom::Affine const sm (Geom::Scale(this->hatch_spacing, this->hatch_spacing) * Geom::Translate(c)); + sp_canvas_item_affine_absolute(this->hatch_area, sm); + sp_canvas_bpath_set_stroke(SP_CANVAS_BPATH(this->hatch_area), 0x7f7f7fff, 1.0, SP_STROKE_LINEJOIN_MITER, SP_STROKE_LINECAP_BUTT); + sp_canvas_item_show(this->hatch_area); + } + } + } else { + sp_canvas_item_hide(this->hatch_area); + } + } + break; + + + case GDK_BUTTON_RELEASE: + { + Geom::Point const motion_w(event->button.x, event->button.y); + Geom::Point const motion_dt(desktop->w2d(motion_w)); + + sp_canvas_item_ungrab(SP_CANVAS_ITEM(desktop->acetate), event->button.time); + desktop->canvas->endForcedFullRedraws(); + this->is_drawing = false; + + if (this->dragging && event->button.button == 1 && !this->space_panning) { + this->dragging = FALSE; + + this->apply(motion_dt); + + /* Remove all temporary line segments */ + while (this->segments) { + sp_canvas_item_destroy(SP_CANVAS_ITEM(this->segments->data)); + this->segments = g_slist_remove(this->segments, this->segments->data); + } + + /* Create object */ + this->fit_and_split(true); + if (this->accumulate()) + this->set_to_accumulated(event->button.state & GDK_SHIFT_MASK, event->button.state & GDK_MOD1_MASK); // performs document_done + else + g_warning ("Failed to create path: invalid data in dc->cal1 or dc->cal2"); + + /* reset accumulated curve */ + this->accumulated->reset(); + + this->clear_current(); + if (this->repr) { + this->repr = NULL; + } + + if (!this->hatch_pointer_past.empty()) this->hatch_pointer_past.clear(); + if (!this->hatch_nearest_past.empty()) this->hatch_nearest_past.clear(); + if (!this->inertia_vectors.empty()) this->inertia_vectors.clear(); + if (!this->hatch_vectors.empty()) this->hatch_vectors.clear(); + this->hatch_last_nearest = Geom::Point(0,0); + this->hatch_last_pointer = Geom::Point(0,0); + this->hatch_escaped = false; + this->hatch_item = NULL; + this->hatch_livarot_path = NULL; + this->just_started_drawing = false; + + if (this->hatch_spacing != 0 && !this->keep_selected) { + // we do not select the newly drawn path, so increase spacing by step + if (this->hatch_spacing_step == 0) { + this->hatch_spacing_step = this->hatch_spacing; + } + this->hatch_spacing += this->hatch_spacing_step; + } + + this->message_context->clear(); + ret = TRUE; + } + break; + } + + case GDK_KEY_PRESS: + switch (get_group0_keyval (&event->key)) { + case GDK_KEY_Up: + case GDK_KEY_KP_Up: + if (!MOD__CTRL_ONLY(event)) { + this->angle += 5.0; + if (this->angle > 90.0) + this->angle = 90.0; + sp_ddc_update_toolbox (desktop, "calligraphy-angle", this->angle); + ret = TRUE; + } + break; + case GDK_KEY_Down: + case GDK_KEY_KP_Down: + if (!MOD__CTRL_ONLY(event)) { + this->angle -= 5.0; + if (this->angle < -90.0) + this->angle = -90.0; + sp_ddc_update_toolbox (desktop, "calligraphy-angle", this->angle); + ret = TRUE; + } + break; + case GDK_KEY_Right: + case GDK_KEY_KP_Right: + if (!MOD__CTRL_ONLY(event)) { + this->width += 0.01; + if (this->width > 1.0) + this->width = 1.0; + sp_ddc_update_toolbox (desktop, "altx-calligraphy", this->width * 100); // the same spinbutton is for alt+x + ret = TRUE; + } + break; + case GDK_KEY_Left: + case GDK_KEY_KP_Left: + if (!MOD__CTRL_ONLY(event)) { + this->width -= 0.01; + if (this->width < 0.01) + this->width = 0.01; + sp_ddc_update_toolbox (desktop, "altx-calligraphy", this->width * 100); + ret = TRUE; + } + break; + case GDK_KEY_Home: + case GDK_KEY_KP_Home: + this->width = 0.01; + sp_ddc_update_toolbox (desktop, "altx-calligraphy", this->width * 100); + ret = TRUE; + break; + case GDK_KEY_End: + case GDK_KEY_KP_End: + this->width = 1.0; + sp_ddc_update_toolbox (desktop, "altx-calligraphy", this->width * 100); + ret = TRUE; + break; + case GDK_KEY_x: + case GDK_KEY_X: + if (MOD__ALT_ONLY(event)) { + desktop->setToolboxFocusTo ("altx-calligraphy"); + ret = TRUE; + } + break; + case GDK_KEY_Escape: + if (this->is_drawing) { + // if drawing, cancel, otherwise pass it up for deselecting + this->cancel(); + ret = TRUE; + } + break; + case GDK_KEY_z: + case GDK_KEY_Z: + if (MOD__CTRL_ONLY(event) && this->is_drawing) { + // if drawing, cancel, otherwise pass it up for undo + this->cancel(); + ret = TRUE; + } + break; + default: + break; + } + break; + + case GDK_KEY_RELEASE: + switch (get_group0_keyval(&event->key)) { + case GDK_KEY_Control_L: + case GDK_KEY_Control_R: + this->message_context->clear(); + this->hatch_spacing = 0; + this->hatch_spacing_step = 0; + break; + default: + break; + } + break; + + default: + break; + } + + if (!ret) { +// if ((SP_EVENT_CONTEXT_CLASS(sp_dyna_draw_context_parent_class))->root_handler) { +// ret = (SP_EVENT_CONTEXT_CLASS(sp_dyna_draw_context_parent_class))->root_handler(event_context, event); +// } + ret = DynamicBase::root_handler(event); + } + + return ret; +} + + +void CalligraphicTool::clear_current() { + /* reset bpath */ + sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(this->currentshape), NULL); + /* reset curve */ + this->currentcurve->reset(); + this->cal1->reset(); + this->cal2->reset(); + /* reset points */ + this->npoints = 0; +} + +void CalligraphicTool::set_to_accumulated(bool unionize, bool subtract) { + if (!this->accumulated->is_empty()) { + if (!this->repr) { + /* Create object */ + Inkscape::XML::Document *xml_doc = desktop->doc()->getReprDoc(); + Inkscape::XML::Node *repr = xml_doc->createElement("svg:path"); + + /* Set style */ + sp_desktop_apply_style_tool (desktop, repr, "/tools/calligraphic", false); + + this->repr = repr; + + SPItem *item=SP_ITEM(desktop->currentLayer()->appendChildRepr(this->repr)); + Inkscape::GC::release(this->repr); + item->transform = SP_ITEM(desktop->currentLayer())->i2doc_affine().inverse(); + item->updateRepr(); + } + + Geom::PathVector pathv = this->accumulated->get_pathvector() * desktop->dt2doc(); + gchar *str = sp_svg_write_path(pathv); + g_assert( str != NULL ); + this->repr->setAttribute("d", str); + g_free(str); + + if (unionize) { + sp_desktop_selection(desktop)->add(this->repr); + sp_selected_path_union_skip_undo(sp_desktop_selection(desktop), desktop); + } else if (subtract) { + sp_desktop_selection(desktop)->add(this->repr); + sp_selected_path_diff_skip_undo(sp_desktop_selection(desktop), desktop); + } else { + if (this->keep_selected) { + sp_desktop_selection(desktop)->set(this->repr); + } + } + + // Now we need to write the transform information. + // First, find out whether our repr is still linked to a valid object. In this case, + // we need to write the transform data only for this element. + // Either there was no boolean op or it failed. + SPItem *result = SP_ITEM(desktop->doc()->getObjectByRepr(this->repr)); + + if (result == NULL) { + // The boolean operation succeeded. + // Now we fetch the single item, that has been set as selected by the boolean op. + // This is its result. + result = desktop->getSelection()->singleItem(); + } + + result->doWriteTransform(result->getRepr(), result->transform, NULL, true); + } else { + if (this->repr) { + sp_repr_unparent(this->repr); + } + + this->repr = NULL; + } + + DocumentUndo::done(sp_desktop_document(desktop), SP_VERB_CONTEXT_CALLIGRAPHIC, + _("Draw calligraphic stroke")); +} + +static void +add_cap(SPCurve *curve, + Geom::Point const &from, + Geom::Point const &to, + double rounding) +{ + if (Geom::L2( to - from ) > DYNA_EPSILON) { + Geom::Point vel = rounding * Geom::rot90( to - from ) / sqrt(2.0); + double mag = Geom::L2(vel); + + Geom::Point v = mag * Geom::rot90( to - from ) / Geom::L2( to - from ); + curve->curveto(from + v, to + v, to); + } +} + +bool CalligraphicTool::accumulate() { + if ( + this->cal1->is_empty() || + this->cal2->is_empty() || + (this->cal1->get_segment_count() <= 0) || + this->cal1->first_path()->closed() + ) { + + this->cal1->reset(); + this->cal2->reset(); + + return false; // failure + } + + SPCurve *rev_cal2 = this->cal2->create_reverse(); + + if ((rev_cal2->get_segment_count() <= 0) || rev_cal2->first_path()->closed()) { + rev_cal2->unref(); + + this->cal1->reset(); + this->cal2->reset(); + + return false; // failure + } + + Geom::Curve const * dc_cal1_firstseg = this->cal1->first_segment(); + Geom::Curve const * rev_cal2_firstseg = rev_cal2->first_segment(); + Geom::Curve const * dc_cal1_lastseg = this->cal1->last_segment(); + Geom::Curve const * rev_cal2_lastseg = rev_cal2->last_segment(); + + this->accumulated->reset(); /* Is this required ?? */ + + this->accumulated->append(this->cal1, false); + + add_cap(this->accumulated, dc_cal1_lastseg->finalPoint(), rev_cal2_firstseg->initialPoint(), this->cap_rounding); + + this->accumulated->append(rev_cal2, true); + + add_cap(this->accumulated, rev_cal2_lastseg->finalPoint(), dc_cal1_firstseg->initialPoint(), this->cap_rounding); + + this->accumulated->closepath(); + + rev_cal2->unref(); + + this->cal1->reset(); + this->cal2->reset(); + + return true; // success +} + +static double square(double const x) +{ + return x * x; +} + +void CalligraphicTool::fit_and_split(bool release) { + double const tolerance_sq = square( desktop->w2d().descrim() * TOLERANCE_CALLIGRAPHIC ); + +#ifdef DYNA_DRAW_VERBOSE + g_print("[F&S:R=%c]", release?'T':'F'); +#endif + + if (!( this->npoints > 0 && this->npoints < SAMPLING_SIZE )) { + return; // just clicked + } + + if ( this->npoints == SAMPLING_SIZE - 1 || release ) { +#define BEZIER_SIZE 4 +#define BEZIER_MAX_BEZIERS 8 +#define BEZIER_MAX_LENGTH ( BEZIER_SIZE * BEZIER_MAX_BEZIERS ) + +#ifdef DYNA_DRAW_VERBOSE + g_print("[F&S:#] dc->npoints:%d, release:%s\n", + this->npoints, release ? "TRUE" : "FALSE"); +#endif + + /* Current calligraphic */ + if ( this->cal1->is_empty() || this->cal2->is_empty() ) { + /* dc->npoints > 0 */ + /* g_print("calligraphics(1|2) reset\n"); */ + this->cal1->reset(); + this->cal2->reset(); + + this->cal1->moveto(this->point1[0]); + this->cal2->moveto(this->point2[0]); + } + + Geom::Point b1[BEZIER_MAX_LENGTH]; + gint const nb1 = Geom::bezier_fit_cubic_r(b1, this->point1, this->npoints, + tolerance_sq, BEZIER_MAX_BEZIERS); + g_assert( nb1 * BEZIER_SIZE <= gint(G_N_ELEMENTS(b1)) ); + + Geom::Point b2[BEZIER_MAX_LENGTH]; + gint const nb2 = Geom::bezier_fit_cubic_r(b2, this->point2, this->npoints, + tolerance_sq, BEZIER_MAX_BEZIERS); + g_assert( nb2 * BEZIER_SIZE <= gint(G_N_ELEMENTS(b2)) ); + + if ( nb1 != -1 && nb2 != -1 ) { + /* Fit and draw and reset state */ +#ifdef DYNA_DRAW_VERBOSE + g_print("nb1:%d nb2:%d\n", nb1, nb2); +#endif + /* CanvasShape */ + if (! release) { + this->currentcurve->reset(); + this->currentcurve->moveto(b1[0]); + for (Geom::Point *bp1 = b1; bp1 < b1 + BEZIER_SIZE * nb1; bp1 += BEZIER_SIZE) { + this->currentcurve->curveto(bp1[1], bp1[2], bp1[3]); + } + this->currentcurve->lineto(b2[BEZIER_SIZE*(nb2-1) + 3]); + for (Geom::Point *bp2 = b2 + BEZIER_SIZE * ( nb2 - 1 ); bp2 >= b2; bp2 -= BEZIER_SIZE) { + this->currentcurve->curveto(bp2[2], bp2[1], bp2[0]); + } + // FIXME: dc->segments is always NULL at this point?? + if (!this->segments) { // first segment + add_cap(this->currentcurve, b2[0], b1[0], this->cap_rounding); + } + this->currentcurve->closepath(); + sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(this->currentshape), this->currentcurve); + } + + /* Current calligraphic */ + for (Geom::Point *bp1 = b1; bp1 < b1 + BEZIER_SIZE * nb1; bp1 += BEZIER_SIZE) { + this->cal1->curveto(bp1[1], bp1[2], bp1[3]); + } + for (Geom::Point *bp2 = b2; bp2 < b2 + BEZIER_SIZE * nb2; bp2 += BEZIER_SIZE) { + this->cal2->curveto(bp2[1], bp2[2], bp2[3]); + } + } else { + /* fixme: ??? */ +#ifdef DYNA_DRAW_VERBOSE + g_print("[fit_and_split] failed to fit-cubic.\n"); +#endif + this->draw_temporary_box(); + + for (gint i = 1; i < this->npoints; i++) { + this->cal1->lineto(this->point1[i]); + } + for (gint i = 1; i < this->npoints; i++) { + this->cal2->lineto(this->point2[i]); + } + } + + /* Fit and draw and copy last point */ +#ifdef DYNA_DRAW_VERBOSE + g_print("[%d]Yup\n", this->npoints); +#endif + if (!release) { + g_assert(!this->currentcurve->is_empty()); + + SPCanvasItem *cbp = sp_canvas_item_new(sp_desktop_sketch(desktop), + SP_TYPE_CANVAS_BPATH, + NULL); + SPCurve *curve = this->currentcurve->copy(); + sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH (cbp), curve); + curve->unref(); + + guint32 fillColor = sp_desktop_get_color_tool (desktop, "/tools/calligraphic", true); + //guint32 strokeColor = sp_desktop_get_color_tool (desktop, "/tools/calligraphic", false); + double opacity = sp_desktop_get_master_opacity_tool (desktop, "/tools/calligraphic"); + double fillOpacity = sp_desktop_get_opacity_tool (desktop, "/tools/calligraphic", true); + //double strokeOpacity = sp_desktop_get_opacity_tool (desktop, "/tools/calligraphic", false); + sp_canvas_bpath_set_fill(SP_CANVAS_BPATH(cbp), ((fillColor & 0xffffff00) | SP_COLOR_F_TO_U(opacity*fillOpacity)), SP_WIND_RULE_EVENODD); + //on second thougtht don't do stroke yet because we don't have stoke-width yet and because stoke appears between segments while drawing + //sp_canvas_bpath_set_stroke(SP_CANVAS_BPATH(cbp), ((strokeColor & 0xffffff00) | SP_COLOR_F_TO_U(opacity*strokeOpacity)), 1.0, SP_STROKE_LINEJOIN_MITER, SP_STROKE_LINECAP_BUTT); + sp_canvas_bpath_set_stroke(SP_CANVAS_BPATH(cbp), 0x00000000, 1.0, SP_STROKE_LINEJOIN_MITER, SP_STROKE_LINECAP_BUTT); + /* fixme: Cannot we cascade it to root more clearly? */ + g_signal_connect(G_OBJECT(cbp), "event", G_CALLBACK(sp_desktop_root_handler), desktop); + + this->segments = g_slist_prepend(this->segments, cbp); + } + + this->point1[0] = this->point1[this->npoints - 1]; + this->point2[0] = this->point2[this->npoints - 1]; + this->npoints = 1; + } else { + this->draw_temporary_box(); + } +} + +void CalligraphicTool::draw_temporary_box() { + this->currentcurve->reset(); + + this->currentcurve->moveto(this->point2[this->npoints-1]); + + for (gint i = this->npoints-2; i >= 0; i--) { + this->currentcurve->lineto(this->point2[i]); + } + + for (gint i = 0; i < this->npoints; i++) { + this->currentcurve->lineto(this->point1[i]); + } + + if (this->npoints >= 2) { + add_cap(this->currentcurve, this->point1[this->npoints-1], this->point2[this->npoints-1], this->cap_rounding); + } + + this->currentcurve->closepath(); + sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(this->currentshape), this->currentcurve); +} + +} +} +} + + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/ui/tools/calligraphic-tool.h b/src/ui/tools/calligraphic-tool.h new file mode 100644 index 000000000..926e9d126 --- /dev/null +++ b/src/ui/tools/calligraphic-tool.h @@ -0,0 +1,94 @@ +#ifndef SP_DYNA_DRAW_CONTEXT_H_SEEN +#define SP_DYNA_DRAW_CONTEXT_H_SEEN + +/* + * Handwriting-like drawing mode + * + * Authors: + * Mitsuru Oka + * Lauris Kaplinski + * + * The original dynadraw code: + * Paul Haeberli + * + * Copyright (C) 1998 The Free Software Foundation + * Copyright (C) 1999-2002 authors + * Copyright (C) 2001-2002 Ximian, Inc. + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include "ui/tools/dynamic-base.h" +#include "splivarot.h" + +#define DDC_MIN_PRESSURE 0.0 +#define DDC_MAX_PRESSURE 1.0 +#define DDC_DEFAULT_PRESSURE 1.0 + +#define DDC_MIN_TILT -1.0 +#define DDC_MAX_TILT 1.0 +#define DDC_DEFAULT_TILT 0.0 + +namespace Inkscape { +namespace UI { +namespace Tools { + +class CalligraphicTool : public DynamicBase { +public: + CalligraphicTool(); + virtual ~CalligraphicTool(); + + static const std::string prefsPath; + + virtual void setup(); + virtual void set(const Inkscape::Preferences::Entry& val); + virtual bool root_handler(GdkEvent* event); + + virtual const std::string& getPrefsPath(); + +private: + /** newly created object remain selected */ + bool keep_selected; + + double hatch_spacing; + double hatch_spacing_step; + SPItem *hatch_item; + Path *hatch_livarot_path; + std::list hatch_nearest_past; + std::list hatch_pointer_past; + std::list inertia_vectors; + Geom::Point hatch_last_nearest, hatch_last_pointer; + std::list hatch_vectors; + bool hatch_escaped; + SPCanvasItem *hatch_area; + bool just_started_drawing; + bool trace_bg; + + void clear_current(); + void set_to_accumulated(bool unionize, bool subtract); + bool accumulate(); + void fit_and_split(bool release); + void draw_temporary_box(); + void cancel(); + void brush(); + bool apply(Geom::Point p); + void extinput(GdkEvent *event); + void reset(Geom::Point p); +}; + +} +} +} + +#endif // SP_DYNA_DRAW_CONTEXT_H_SEEN + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/ui/tools/connector-tool.cpp b/src/ui/tools/connector-tool.cpp new file mode 100644 index 000000000..36e013ba8 --- /dev/null +++ b/src/ui/tools/connector-tool.cpp @@ -0,0 +1,1496 @@ +/* + * Connector creation tool + * + * Authors: + * Michael Wybrow + * Abhishek Sharma + * Jon A. Cruz + * Martin Owens + * + * Copyright (C) 2005-2008 Michael Wybrow + * Copyright (C) 2009 Monash University + * Copyright (C) 2012 Authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + * + * TODO: + * o Show a visual indicator for objects with the 'avoid' property set. + * o Allow user to change a object between a path and connector through + * the interface. + * o Create an interface for setting markers (arrow heads). + * o Better distinguish between paths and connectors to prevent problems + * in the node tool and paths accidentally being turned into connectors + * in the connector tool. Perhaps have a way to convert between. + * o Only call libavoid's updateEndPoint as required. Currently we do it + * for both endpoints, even if only one is moving. + * o Deal sanely with connectors with both endpoints attached to the + * same connection point, and drawing of connectors attaching + * overlapping shapes (currently tries to adjust connector to be + * outside both bounding boxes). + * o Fix many special cases related to connectors updating, + * e.g., copying a couple of shapes and a connector that are + * attached to each other. + * e.g., detach connector when it is moved or transformed in + * one of the other contexts. + * o Cope with shapes whose ids change when they have attached + * connectors. + * o During dragging motion, gobble up to and use the final motion event. + * Gobbling away all duplicates after the current can occasionally result + * in the path lagging behind the mouse cursor if it is no longer being + * dragged. + * o Fix up libavoid's representation after undo actions. It doesn't see + * any transform signals and hence doesn't know shapes have moved back to + * there earlier positions. + * + * ---------------------------------------------------------------------------- + * + * Notes: + * + * Much of the way connectors work for user-defined points has been + * changed so that it no longer defines special attributes to record + * the points. Instead it uses single node paths to define points + * who are then seperate objects that can be fixed on the canvas, + * grouped into objects and take full advantage of all transform, snap + * and align functionality of all other objects. + * + * I think that the style change between polyline and orthogonal + * would be much clearer with two buttons (radio behaviour -- just + * one is true). + * + * The other tools show a label change from "New:" to "Change:" + * depending on whether an object is selected. We could consider + * this but there may not be space. + * + * Likewise for the avoid/ignore shapes buttons. These should be + * inactive when a shape is not selected in the connector context. + * + */ + + + +#include +#include +#include + +#include "ui/tools/connector-tool.h" +#include "pixmaps/cursor-connector.xpm" +#include "xml/node-event-vector.h" +#include "xml/repr.h" +#include "svg/svg.h" +#include "desktop.h" +#include "desktop-style.h" +#include "desktop-handles.h" +#include "document.h" +#include "document-undo.h" +#include "message-context.h" +#include "message-stack.h" +#include "selection.h" +#include "inkscape.h" +#include "preferences.h" +#include "sp-path.h" +#include "display/sp-canvas.h" +#include "display/canvas-bpath.h" +#include "display/sodipodi-ctrl.h" +#include +#include +#include "snap.h" +#include "knot.h" +#include "sp-conn-end.h" +#include "sp-conn-end-pair.h" +#include "conn-avoid-ref.h" +#include "libavoid/vertices.h" +#include "libavoid/router.h" +#include "context-fns.h" +#include "sp-namedview.h" +#include "sp-text.h" +#include "sp-flowtext.h" +#include "display/curve.h" +#include "verbs.h" + +using Inkscape::DocumentUndo; + +#include "tool-factory.h" + +namespace Inkscape { +namespace UI { +namespace Tools { + +// Stuff borrowed from DrawContext +static void spcc_connector_set_initial_point(ConnectorTool *cc, Geom::Point const p); +static void spcc_connector_set_subsequent_point(ConnectorTool *cc, Geom::Point const p); +static void spcc_connector_finish_segment(ConnectorTool *cc, Geom::Point p); +static void spcc_reset_colors(ConnectorTool *cc); +static void spcc_connector_finish(ConnectorTool *cc); +static void spcc_concat_colors_and_flush(ConnectorTool *cc); +static void spcc_flush_white(ConnectorTool *cc, SPCurve *gc); + +// Context event handlers +static gint connector_handle_button_press(ConnectorTool *const cc, GdkEventButton const &bevent); +static gint connector_handle_motion_notify(ConnectorTool *const cc, GdkEventMotion const &mevent); +static gint connector_handle_button_release(ConnectorTool *const cc, GdkEventButton const &revent); +static gint connector_handle_key_press(ConnectorTool *const cc, guint const keyval); + +static void cc_active_shape_add_knot(ConnectorTool *cc, SPItem* item); +static void cc_set_active_shape(ConnectorTool *cc, SPItem *item); +static void cc_clear_active_knots(SPKnotList k); +static void cc_clear_active_shape(ConnectorTool *cc); +static void cc_set_active_conn(ConnectorTool *cc, SPItem *item); +static void cc_clear_active_conn(ConnectorTool *cc); +static bool conn_pt_handle_test(ConnectorTool *cc, Geom::Point& p, gchar **href); +static void cc_select_handle(SPKnot* knot); +static void cc_deselect_handle(SPKnot* knot); +static bool cc_item_is_shape(SPItem *item); +static void cc_connector_rerouting_finish(ConnectorTool *const cc, + Geom::Point *const p); + +static void shape_event_attr_deleted(Inkscape::XML::Node *repr, + Inkscape::XML::Node *child, Inkscape::XML::Node *ref, gpointer data); +static void shape_event_attr_changed(Inkscape::XML::Node *repr, gchar const *name, + gchar const *old_value, gchar const *new_value, bool is_interactive, + gpointer data); + +/*static Geom::Point connector_drag_origin_w(0, 0); +static bool connector_within_tolerance = false;*/ + +static Inkscape::XML::NodeEventVector shape_repr_events = { + NULL, /* child_added */ + NULL, /* child_added */ + shape_event_attr_changed, + NULL, /* content_changed */ + NULL /* order_changed */ +}; + +static Inkscape::XML::NodeEventVector layer_repr_events = { + NULL, /* child_added */ + shape_event_attr_deleted, + NULL, /* child_added */ + NULL, /* content_changed */ + NULL /* order_changed */ +}; + +namespace { + ToolBase* createConnectorContext() { + return new ConnectorTool(); + } + + bool connectorContextRegistered = ToolFactory::instance().registerObject("/tools/connector", createConnectorContext); +} + +const std::string& ConnectorTool::getPrefsPath() { + return ConnectorTool::prefsPath; +} + +const std::string ConnectorTool::prefsPath = "/tools/connector"; + +ConnectorTool::ConnectorTool() : ToolBase() { + this->red_curve = 0; + this->isOrthogonal = false; + this->c1 = 0; + this->red_bpath = 0; + this->green_curve = 0; + this->selection = 0; + this->cl0 = 0; + this->cl1 = 0; + this->c0 = 0; + + this->cursor_shape = cursor_connector_xpm; + this->hot_x = 1; + this->hot_y = 1; + this->xp = 0; + this->yp = 0; + + this->red_color = 0xff00007f; + + this->newconn = NULL; + this->newConnRef = NULL; + this->curvature = 0.0; + + this->sel_changed_connection = sigc::connection(); + + this->active_shape = NULL; + this->active_shape_repr = NULL; + this->active_shape_layer_repr = NULL; + + this->active_conn = NULL; + this->active_conn_repr = NULL; + + this->active_handle = NULL; + + this->selected_handle = NULL; + + this->clickeditem = NULL; + this->clickedhandle = NULL; + + for (int i = 0; i < 2; ++i) { + this->endpt_handle[i] = NULL; + this->endpt_handler_id[i] = 0; + } + + this->shref = NULL; + this->ehref = NULL; + this->npoints = 0; + this->state = SP_CONNECTOR_CONTEXT_IDLE; +} + +ConnectorTool::~ConnectorTool() { + this->sel_changed_connection.disconnect(); + + for (int i = 0; i < 2; ++i) { + if (this->endpt_handle[1]) { + g_object_unref(this->endpt_handle[i]); + this->endpt_handle[i] = NULL; + } + } + + if (this->shref) { + g_free(this->shref); + this->shref = NULL; + } + + if (this->ehref) { + g_free(this->shref); + this->shref = NULL; + } + + g_assert( this->newConnRef == NULL ); +} + +void ConnectorTool::setup() { + ToolBase::setup(); + + this->selection = sp_desktop_selection(this->desktop); + + this->sel_changed_connection.disconnect(); + this->sel_changed_connection = this->selection->connectChanged( + sigc::mem_fun(this, &ConnectorTool::selection_changed) + ); + + /* Create red bpath */ + this->red_bpath = sp_canvas_bpath_new(sp_desktop_sketch(this->desktop), NULL); + sp_canvas_bpath_set_stroke(SP_CANVAS_BPATH(this->red_bpath), this->red_color, + 1.0, SP_STROKE_LINEJOIN_MITER, SP_STROKE_LINECAP_BUTT); + sp_canvas_bpath_set_fill(SP_CANVAS_BPATH(this->red_bpath), 0x00000000, + SP_WIND_RULE_NONZERO); + /* Create red curve */ + this->red_curve = new SPCurve(); + + /* Create green curve */ + this->green_curve = new SPCurve(); + + // Notice the initial selection. + //cc_selection_changed(this->selection, (gpointer) this); + this->selection_changed(this->selection); + + this->within_tolerance = false; + + sp_event_context_read(this, "curvature"); + sp_event_context_read(this, "orthogonal"); + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + if (prefs->getBool("/tools/connector/selcue", 0)) { + this->enableSelectionCue(); + } + + // Make sure we see all enter events for canvas items, + // even if a mouse button is depressed. + this->desktop->canvas->gen_all_enter_events = true; +} + +void ConnectorTool::set(const Inkscape::Preferences::Entry& val) { + /* fixme: Proper error handling for non-numeric data. Use a locale-independent function like + * g_ascii_strtod (or a thin wrapper that does the right thing for invalid values inf/nan). */ + Glib::ustring name = val.getEntryName(); + + if (name == "curvature") { + this->curvature = val.getDoubleLimited(); // prevents NaN and +/-Inf from messing up + } else if (name == "orthogonal") { + this->isOrthogonal = val.getBool(); + } +} + +void ConnectorTool::finish() { + spcc_connector_finish(this); + this->state = SP_CONNECTOR_CONTEXT_IDLE; + + ToolBase::finish(); + + if (this->selection) { + this->selection = NULL; + } + + cc_clear_active_shape(this); + cc_clear_active_conn(this); + + // Restore the default event generating behaviour. + this->desktop->canvas->gen_all_enter_events = false; +} + +//----------------------------------------------------------------------------- + + +static void +cc_clear_active_shape(ConnectorTool *cc) +{ + if (cc->active_shape == NULL) { + return; + } + g_assert( cc->active_shape_repr ); + g_assert( cc->active_shape_layer_repr ); + + cc->active_shape = NULL; + + if (cc->active_shape_repr) { + sp_repr_remove_listener_by_data(cc->active_shape_repr, cc); + Inkscape::GC::release(cc->active_shape_repr); + cc->active_shape_repr = NULL; + + sp_repr_remove_listener_by_data(cc->active_shape_layer_repr, cc); + Inkscape::GC::release(cc->active_shape_layer_repr); + cc->active_shape_layer_repr = NULL; + } + + cc_clear_active_knots(cc->knots); +} + +static void +cc_clear_active_knots(SPKnotList k) +{ + // Hide the connection points if they exist. + if (k.size()) { + for (SPKnotList::iterator it = k.begin(); it != k.end(); ++it) { + sp_knot_hide(it->first); + } + } +} + +static void +cc_clear_active_conn(ConnectorTool *cc) +{ + if (cc->active_conn == NULL) { + return; + } + g_assert( cc->active_conn_repr ); + + cc->active_conn = NULL; + + if (cc->active_conn_repr) { + sp_repr_remove_listener_by_data(cc->active_conn_repr, cc); + Inkscape::GC::release(cc->active_conn_repr); + cc->active_conn_repr = NULL; + } + + // Hide the endpoint handles. + for (int i = 0; i < 2; ++i) { + if (cc->endpt_handle[i]) { + sp_knot_hide(cc->endpt_handle[i]); + } + } +} + + +static bool +conn_pt_handle_test(ConnectorTool *cc, Geom::Point& p, gchar **href) +{ + if (cc->active_handle && (cc->knots.find(cc->active_handle) != cc->knots.end())) + { + p = cc->active_handle->pos; + *href = g_strdup_printf("#%s", cc->active_handle->owner->getId()); + return true; + } + *href = NULL; + return false; +} + +static void +cc_select_handle(SPKnot* knot) +{ + knot->setShape(SP_KNOT_SHAPE_SQUARE); + knot->setSize(10); + knot->setAnchor(SP_ANCHOR_CENTER); + knot->setFill(0x0000ffff, 0x0000ffff, 0x0000ffff); + sp_knot_update_ctrl(knot); +} + +static void +cc_deselect_handle(SPKnot* knot) +{ + knot->setShape(SP_KNOT_SHAPE_SQUARE); + knot->setSize(8); + knot->setAnchor(SP_ANCHOR_CENTER); + knot->setFill(0xffffff00, 0xff0000ff, 0xff0000ff); + sp_knot_update_ctrl(knot); +} + +bool ConnectorTool::item_handler(SPItem* item, GdkEvent* event) { + gint ret = FALSE; + + Geom::Point p(event->button.x, event->button.y); + + switch (event->type) { + case GDK_BUTTON_RELEASE: + if (event->button.button == 1 && !this->space_panning) { + if ((this->state == SP_CONNECTOR_CONTEXT_DRAGGING) && this->within_tolerance) { + spcc_reset_colors(this); + this->state = SP_CONNECTOR_CONTEXT_IDLE; + } + + if (this->state != SP_CONNECTOR_CONTEXT_IDLE) { + // Doing something else like rerouting. + break; + } + + // find out clicked item, honoring Alt + SPItem *item = sp_event_context_find_item(desktop, p, event->button.state & GDK_MOD1_MASK, FALSE); + + if (event->button.state & GDK_SHIFT_MASK) { + this->selection->toggle(item); + } else { + this->selection->set(item); + /* When selecting a new item, do not allow showing + connection points on connectors. (yet?) + */ + + if (item != this->active_shape && !cc_item_is_connector(item)) { + cc_set_active_shape(this, item); + } + } + + ret = TRUE; + } + break; + + case GDK_ENTER_NOTIFY: + if (!this->selected_handle) { + if (cc_item_is_shape(item)) { + cc_set_active_shape(this, item); + } + + ret = TRUE; + } + break; + + default: + break; + } + + return ret; +} + +bool ConnectorTool::root_handler(GdkEvent* event) { + gint ret = FALSE; + + switch (event->type) { + case GDK_BUTTON_PRESS: + ret = connector_handle_button_press(this, event->button); + break; + + case GDK_MOTION_NOTIFY: + ret = connector_handle_motion_notify(this, event->motion); + break; + + case GDK_BUTTON_RELEASE: + ret = connector_handle_button_release(this, event->button); + break; + + case GDK_KEY_PRESS: + ret = connector_handle_key_press(this, get_group0_keyval (&event->key)); + break; + + default: + break; + } + + if (!ret) { + ret = ToolBase::root_handler(event); + } + + return ret; +} + + +static gint +connector_handle_button_press(ConnectorTool *const cc, GdkEventButton const &bevent) +{ + Geom::Point const event_w(bevent.x, bevent.y); + /* Find desktop coordinates */ + Geom::Point p = cc->desktop->w2d(event_w); + ToolBase *event_context = SP_EVENT_CONTEXT(cc); + + gint ret = FALSE; + + if ( bevent.button == 1 && !event_context->space_panning ) { + + SPDesktop *desktop = SP_EVENT_CONTEXT_DESKTOP(cc); + + if (Inkscape::have_viable_layer(desktop, cc->message_context) == false) { + return TRUE; + } + + Geom::Point const event_w(bevent.x, + bevent.y); + + cc->xp = bevent.x; + cc->yp = bevent.y; + cc->within_tolerance = true; + + Geom::Point const event_dt = cc->desktop->w2d(event_w); + + SnapManager &m = cc->desktop->namedview->snap_manager; + + switch (cc->state) { + case SP_CONNECTOR_CONTEXT_STOP: + /* This is allowed, if we just canceled curve */ + case SP_CONNECTOR_CONTEXT_IDLE: + { + if ( cc->npoints == 0 ) { + cc_clear_active_conn(cc); + + SP_EVENT_CONTEXT_DESKTOP(cc)->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("Creating new connector")); + + /* Set start anchor */ + /* Create green anchor */ + Geom::Point p = event_dt; + + // Test whether we clicked on a connection point + bool found = conn_pt_handle_test(cc, p, &cc->shref); + + if (!found) { + // This is the first point, so just snap it to the grid + // as there's no other points to go off. + m.setup(cc->desktop); + m.freeSnapReturnByRef(p, Inkscape::SNAPSOURCE_OTHER_HANDLE); + m.unSetup(); + } + spcc_connector_set_initial_point(cc, p); + + } + cc->state = SP_CONNECTOR_CONTEXT_DRAGGING; + ret = TRUE; + break; + } + case SP_CONNECTOR_CONTEXT_DRAGGING: + { + // This is the second click of a connector creation. + m.setup(cc->desktop); + m.freeSnapReturnByRef(p, Inkscape::SNAPSOURCE_OTHER_HANDLE); + m.unSetup(); + + spcc_connector_set_subsequent_point(cc, p); + spcc_connector_finish_segment(cc, p); + + conn_pt_handle_test(cc, p, &cc->ehref); + if (cc->npoints != 0) { + spcc_connector_finish(cc); + } + cc_set_active_conn(cc, cc->newconn); + cc->state = SP_CONNECTOR_CONTEXT_IDLE; + ret = TRUE; + break; + } + case SP_CONNECTOR_CONTEXT_CLOSE: + { + g_warning("Button down in CLOSE state"); + break; + } + default: + break; + } + } else if (bevent.button == 3) { + if (cc->state == SP_CONNECTOR_CONTEXT_REROUTING) { + // A context menu is going to be triggered here, + // so end the rerouting operation. + cc_connector_rerouting_finish(cc, &p); + + cc->state = SP_CONNECTOR_CONTEXT_IDLE; + + // Don't set ret to TRUE, so we drop through to the + // parent handler which will open the context menu. + } + else if (cc->npoints != 0) { + spcc_connector_finish(cc); + cc->state = SP_CONNECTOR_CONTEXT_IDLE; + ret = TRUE; + } + } + return ret; +} + +static gint +connector_handle_motion_notify(ConnectorTool *const cc, GdkEventMotion const &mevent) +{ + gint ret = FALSE; + ToolBase *event_context = SP_EVENT_CONTEXT(cc); + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + + if (event_context->space_panning || mevent.state & GDK_BUTTON2_MASK || mevent.state & GDK_BUTTON3_MASK) { + // allow middle-button scrolling + return FALSE; + } + + Geom::Point const event_w(mevent.x, mevent.y); + + if (cc->within_tolerance) { + cc->tolerance = prefs->getIntLimited("/options/dragtolerance/value", 0, 0, 100); + if ( ( abs( (gint) mevent.x - cc->xp ) < cc->tolerance ) && + ( abs( (gint) mevent.y - cc->yp ) < cc->tolerance ) ) { + return FALSE; // Do not drag if we're within tolerance from origin. + } + } + // Once the user has moved farther than tolerance from the original location + // (indicating they intend to move the object, not click), then always process + // the motion notify coordinates as given (no snapping back to origin) + cc->within_tolerance = false; + + SPDesktop *const dt = cc->desktop; + + /* Find desktop coordinates */ + Geom::Point p = dt->w2d(event_w); + + SnapManager &m = dt->namedview->snap_manager; + + switch (cc->state) { + case SP_CONNECTOR_CONTEXT_DRAGGING: + { + gobble_motion_events(mevent.state); + // This is movement during a connector creation. + if ( cc->npoints > 0 ) { + m.setup(dt); + m.freeSnapReturnByRef(p, Inkscape::SNAPSOURCE_OTHER_HANDLE); + m.unSetup(); + cc->selection->clear(); + spcc_connector_set_subsequent_point(cc, p); + ret = TRUE; + } + break; + } + case SP_CONNECTOR_CONTEXT_REROUTING: + { + gobble_motion_events(GDK_BUTTON1_MASK); + g_assert( SP_IS_PATH(cc->clickeditem)); + + m.setup(dt); + m.freeSnapReturnByRef(p, Inkscape::SNAPSOURCE_OTHER_HANDLE); + m.unSetup(); + + // Update the hidden path + Geom::Affine i2d ( (cc->clickeditem)->i2dt_affine() ); + Geom::Affine d2i = i2d.inverse(); + SPPath *path = SP_PATH(cc->clickeditem); + SPCurve *curve = path->get_curve(); + if (cc->clickedhandle == cc->endpt_handle[0]) { + Geom::Point o = cc->endpt_handle[1]->pos; + curve->stretch_endpoints(p * d2i, o * d2i); + } + else { + Geom::Point o = cc->endpt_handle[0]->pos; + curve->stretch_endpoints(o * d2i, p * d2i); + } + sp_conn_reroute_path_immediate(path); + + // Copy this to the temporary visible path + cc->red_curve = path->get_curve_for_edit(); + cc->red_curve->transform(i2d); + + sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(cc->red_bpath), cc->red_curve); + ret = TRUE; + break; + } + case SP_CONNECTOR_CONTEXT_STOP: + /* This is perfectly valid */ + break; + default: + if (!sp_event_context_knot_mouseover(cc)) { + m.setup(dt); + m.preSnap(Inkscape::SnapCandidatePoint(p, Inkscape::SNAPSOURCE_OTHER_HANDLE)); + m.unSetup(); + } + break; + } + return ret; +} + +static gint +connector_handle_button_release(ConnectorTool *const cc, GdkEventButton const &revent) +{ + gint ret = FALSE; + ToolBase *event_context = SP_EVENT_CONTEXT(cc); + if ( revent.button == 1 && !event_context->space_panning ) { + + SPDesktop *desktop = SP_EVENT_CONTEXT_DESKTOP(cc); + SPDocument *doc = sp_desktop_document(desktop); + + SnapManager &m = desktop->namedview->snap_manager; + + Geom::Point const event_w(revent.x, revent.y); + + /* Find desktop coordinates */ + Geom::Point p = cc->desktop->w2d(event_w); + + switch (cc->state) { + //case SP_CONNECTOR_CONTEXT_POINT: + case SP_CONNECTOR_CONTEXT_DRAGGING: + { + m.setup(desktop); + m.freeSnapReturnByRef(p, Inkscape::SNAPSOURCE_OTHER_HANDLE); + m.unSetup(); + + if (cc->within_tolerance) + { + spcc_connector_finish_segment(cc, p); + return TRUE; + } + // Connector has been created via a drag, end it now. + spcc_connector_set_subsequent_point(cc, p); + spcc_connector_finish_segment(cc, p); + // Test whether we clicked on a connection point + conn_pt_handle_test(cc, p, &cc->ehref); + if (cc->npoints != 0) { + spcc_connector_finish(cc); + } + cc_set_active_conn(cc, cc->newconn); + cc->state = SP_CONNECTOR_CONTEXT_IDLE; + break; + } + case SP_CONNECTOR_CONTEXT_REROUTING: + { + m.setup(desktop); + m.freeSnapReturnByRef(p, Inkscape::SNAPSOURCE_OTHER_HANDLE); + m.unSetup(); + cc_connector_rerouting_finish(cc, &p); + + doc->ensureUpToDate(); + cc->state = SP_CONNECTOR_CONTEXT_IDLE; + return TRUE; + break; + } + case SP_CONNECTOR_CONTEXT_STOP: + /* This is allowed, if we just cancelled curve */ + break; + default: + break; + } + ret = TRUE; + } + return ret; +} + +static gint +connector_handle_key_press(ConnectorTool *const cc, guint const keyval) +{ + gint ret = FALSE; + + switch (keyval) { + case GDK_KEY_Return: + case GDK_KEY_KP_Enter: + if (cc->npoints != 0) { + spcc_connector_finish(cc); + cc->state = SP_CONNECTOR_CONTEXT_IDLE; + ret = TRUE; + } + break; + case GDK_KEY_Escape: + if (cc->state == SP_CONNECTOR_CONTEXT_REROUTING) { + + SPDesktop *desktop = SP_EVENT_CONTEXT_DESKTOP(cc); + SPDocument *doc = sp_desktop_document(desktop); + + cc_connector_rerouting_finish(cc, NULL); + + DocumentUndo::undo(doc); + + cc->state = SP_CONNECTOR_CONTEXT_IDLE; + desktop->messageStack()->flash( Inkscape::NORMAL_MESSAGE, + _("Connector endpoint drag cancelled.")); + ret = TRUE; + } + else if (cc->npoints != 0) { + // if drawing, cancel, otherwise pass it up for deselecting + cc->state = SP_CONNECTOR_CONTEXT_STOP; + spcc_reset_colors(cc); + ret = TRUE; + } + break; + default: + break; + } + return ret; +} + + +static void +cc_connector_rerouting_finish(ConnectorTool *const cc, Geom::Point *const p) +{ + SPDesktop *desktop = SP_EVENT_CONTEXT_DESKTOP(cc); + SPDocument *doc = sp_desktop_document(desktop); + + // Clear the temporary path: + cc->red_curve->reset(); + sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(cc->red_bpath), NULL); + + if (p != NULL) + { + // Test whether we clicked on a connection point + gchar *shape_label; + bool found = conn_pt_handle_test(cc, *p, &shape_label); + + if (found) { + if (cc->clickedhandle == cc->endpt_handle[0]) { + cc->clickeditem->setAttribute("inkscape:connection-start", shape_label, NULL); + } + else { + cc->clickeditem->setAttribute("inkscape:connection-end", shape_label, NULL); + } + g_free(shape_label); + } + } + cc->clickeditem->setHidden(false); + sp_conn_reroute_path_immediate(SP_PATH(cc->clickeditem)); + cc->clickeditem->updateRepr(); + DocumentUndo::done(doc, SP_VERB_CONTEXT_CONNECTOR, + _("Reroute connector")); + cc_set_active_conn(cc, cc->clickeditem); +} + + +static void +spcc_reset_colors(ConnectorTool *cc) +{ + /* Red */ + cc->red_curve->reset(); + sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(cc->red_bpath), NULL); + + cc->green_curve->reset(); + cc->npoints = 0; +} + + +static void +spcc_connector_set_initial_point(ConnectorTool *const cc, Geom::Point const p) +{ + g_assert( cc->npoints == 0 ); + + cc->p[0] = p; + cc->p[1] = p; + cc->npoints = 2; + sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(cc->red_bpath), NULL); +} + + +static void +spcc_connector_set_subsequent_point(ConnectorTool *const cc, Geom::Point const p) +{ + g_assert( cc->npoints != 0 ); + + SPDesktop *dt = cc->desktop; + Geom::Point o = dt->dt2doc(cc->p[0]); + Geom::Point d = dt->dt2doc(p); + Avoid::Point src(o[Geom::X], o[Geom::Y]); + Avoid::Point dst(d[Geom::X], d[Geom::Y]); + + if (!cc->newConnRef) { + Avoid::Router *router = sp_desktop_document(dt)->router; + cc->newConnRef = new Avoid::ConnRef(router); + cc->newConnRef->setEndpoint(Avoid::VertID::src, src); + if (cc->isOrthogonal) + cc->newConnRef->setRoutingType(Avoid::ConnType_Orthogonal); + else + cc->newConnRef->setRoutingType(Avoid::ConnType_PolyLine); + } + // Set new endpoint. + cc->newConnRef->setEndpoint(Avoid::VertID::tar, dst); + // Immediately generate new routes for connector. + cc->newConnRef->makePathInvalid(); + cc->newConnRef->router()->processTransaction(); + // Recreate curve from libavoid route. + recreateCurve( cc->red_curve, cc->newConnRef, cc->curvature ); + cc->red_curve->transform(dt->doc2dt()); + sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(cc->red_bpath), cc->red_curve); +} + + +/** + * Concats red, blue and green. + * If any anchors are defined, process these, optionally removing curves from white list + * Invoke _flush_white to write result back to object. + */ +static void +spcc_concat_colors_and_flush(ConnectorTool *cc) +{ + SPCurve *c = cc->green_curve; + cc->green_curve = new SPCurve(); + + cc->red_curve->reset(); + sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(cc->red_bpath), NULL); + + if (c->is_empty()) { + c->unref(); + return; + } + + spcc_flush_white(cc, c); + + c->unref(); +} + + +/* + * Flushes white curve(s) and additional curve into object + * + * No cleaning of colored curves - this has to be done by caller + * No rereading of white data, so if you cannot rely on ::modified, do it in caller + * + */ + +static void +spcc_flush_white(ConnectorTool *cc, SPCurve *gc) +{ + SPCurve *c; + + if (gc) { + c = gc; + c->ref(); + } else { + return; + } + + /* Now we have to go back to item coordinates at last */ + c->transform(SP_EVENT_CONTEXT_DESKTOP(cc)->dt2doc()); + + SPDesktop *desktop = SP_EVENT_CONTEXT_DESKTOP(cc); + SPDocument *doc = sp_desktop_document(desktop); + Inkscape::XML::Document *xml_doc = doc->getReprDoc(); + + if ( c && !c->is_empty() ) { + /* We actually have something to write */ + + Inkscape::XML::Node *repr = xml_doc->createElement("svg:path"); + /* Set style */ + sp_desktop_apply_style_tool(desktop, repr, "/tools/connector", false); + + gchar *str = sp_svg_write_path( c->get_pathvector() ); + g_assert( str != NULL ); + repr->setAttribute("d", str); + g_free(str); + + /* Attach repr */ + cc->newconn = SP_ITEM(desktop->currentLayer()->appendChildRepr(repr)); + cc->newconn->transform = SP_ITEM(desktop->currentLayer())->i2doc_affine().inverse(); + + bool connection = false; + cc->newconn->setAttribute( "inkscape:connector-type", + cc->isOrthogonal ? "orthogonal" : "polyline", NULL ); + cc->newconn->setAttribute( "inkscape:connector-curvature", + Glib::Ascii::dtostr(cc->curvature).c_str(), NULL ); + if (cc->shref) + { + cc->newconn->setAttribute( "inkscape:connection-start", cc->shref, NULL); + connection = true; + } + + if (cc->ehref) + { + cc->newconn->setAttribute( "inkscape:connection-end", cc->ehref, NULL); + connection = true; + } + // Process pending updates. + cc->newconn->updateRepr(); + doc->ensureUpToDate(); + + if (connection) { + // Adjust endpoints to shape edge. + sp_conn_reroute_path_immediate(SP_PATH(cc->newconn)); + cc->newconn->updateRepr(); + } + + cc->newconn->doWriteTransform(cc->newconn->getRepr(), cc->newconn->transform, NULL, true); + + // Only set the selection after we are finished with creating the attributes of + // the connector. Otherwise, the selection change may alter the defaults for + // values like curvature in the connector context, preventing subsequent lookup + // of their original values. + cc->selection->set(repr); + Inkscape::GC::release(repr); + } + + c->unref(); + + DocumentUndo::done(doc, SP_VERB_CONTEXT_CONNECTOR, _("Create connector")); +} + + +static void +spcc_connector_finish_segment(ConnectorTool *const cc, Geom::Point const /*p*/) +{ + if (!cc->red_curve->is_empty()) { + cc->green_curve->append_continuous(cc->red_curve, 0.0625); + + cc->p[0] = cc->p[3]; + cc->p[1] = cc->p[4]; + cc->npoints = 2; + + cc->red_curve->reset(); + } +} + + +static void +spcc_connector_finish(ConnectorTool *const cc) +{ + SPDesktop *const desktop = cc->desktop; + desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("Finishing connector")); + + cc->red_curve->reset(); + spcc_concat_colors_and_flush(cc); + + cc->npoints = 0; + + if (cc->newConnRef) { + cc->newConnRef->removeFromGraph(); + delete cc->newConnRef; + cc->newConnRef = NULL; + } +} + + +static gboolean +cc_generic_knot_handler(SPCanvasItem *, GdkEvent *event, SPKnot *knot) +{ + g_assert (knot != NULL); + + g_object_ref(knot); + + ConnectorTool *cc = SP_CONNECTOR_CONTEXT( + knot->desktop->event_context); + + gboolean consumed = FALSE; + + gchar const *knot_tip = "Click to join at this point"; + switch (event->type) { + case GDK_ENTER_NOTIFY: + sp_knot_set_flag(knot, SP_KNOT_MOUSEOVER, TRUE); + + cc->active_handle = knot; + if (knot_tip) + { + knot->desktop->event_context->defaultMessageContext()->set( + Inkscape::NORMAL_MESSAGE, knot_tip); + } + + consumed = TRUE; + break; + case GDK_LEAVE_NOTIFY: + sp_knot_set_flag(knot, SP_KNOT_MOUSEOVER, FALSE); + + cc->active_handle = NULL; + + if (knot_tip) { + knot->desktop->event_context->defaultMessageContext()->clear(); + } + + consumed = TRUE; + break; + default: + break; + } + + g_object_unref(knot); + + return consumed; +} + + +static gboolean +endpt_handler(SPKnot */*knot*/, GdkEvent *event, ConnectorTool *cc) +{ + //g_assert( SP_IS_CONNECTOR_CONTEXT(cc) ); + + gboolean consumed = FALSE; + + switch (event->type) { + case GDK_BUTTON_PRESS: + g_assert( (cc->active_handle == cc->endpt_handle[0]) || + (cc->active_handle == cc->endpt_handle[1]) ); + if (cc->state == SP_CONNECTOR_CONTEXT_IDLE) { + cc->clickeditem = cc->active_conn; + cc->clickedhandle = cc->active_handle; + cc_clear_active_conn(cc); + cc->state = SP_CONNECTOR_CONTEXT_REROUTING; + + // Disconnect from attached shape + unsigned ind = (cc->active_handle == cc->endpt_handle[0]) ? 0 : 1; + sp_conn_end_detach(cc->clickeditem, ind); + + Geom::Point origin; + if (cc->clickedhandle == cc->endpt_handle[0]) { + origin = cc->endpt_handle[1]->pos; + } + else { + origin = cc->endpt_handle[0]->pos; + } + + // Show the red path for dragging. + cc->red_curve = SP_PATH(cc->clickeditem)->get_curve_for_edit(); + Geom::Affine i2d = (cc->clickeditem)->i2dt_affine(); + cc->red_curve->transform(i2d); + sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(cc->red_bpath), cc->red_curve); + + cc->clickeditem->setHidden(true); + + // The rest of the interaction rerouting the connector is + // handled by the context root handler. + consumed = TRUE; + } + break; + default: + break; + } + + return consumed; +} + +static void cc_active_shape_add_knot(ConnectorTool *cc, SPItem* item) +{ + SPDesktop *desktop = cc->desktop; + SPKnot *knot = sp_knot_new(desktop, 0); + + knot->owner = item; + knot->setShape(SP_KNOT_SHAPE_SQUARE); + knot->setSize(8); + knot->setAnchor(SP_ANCHOR_CENTER); + knot->setFill(0xffffff00, 0xff0000ff, 0xff0000ff); + sp_knot_update_ctrl(knot); + + // We don't want to use the standard knot handler. + g_signal_handler_disconnect(G_OBJECT(knot->item), + knot->_event_handler_id); + knot->_event_handler_id = 0; + + g_signal_connect(G_OBJECT(knot->item), "event", + G_CALLBACK(cc_generic_knot_handler), knot); + sp_knot_set_position(knot, item->avoidRef->getConnectionPointPos() * desktop->doc2dt(), 0); + sp_knot_show(knot); + cc->knots[knot] = 1; +} + +static void cc_set_active_shape(ConnectorTool *cc, SPItem *item) +{ + g_assert(item != NULL ); + + if (cc->active_shape != item) + { + // The active shape has changed + // Rebuild everything + cc->active_shape = item; + // Remove existing active shape listeners + if (cc->active_shape_repr) { + sp_repr_remove_listener_by_data(cc->active_shape_repr, cc); + Inkscape::GC::release(cc->active_shape_repr); + + sp_repr_remove_listener_by_data(cc->active_shape_layer_repr, cc); + Inkscape::GC::release(cc->active_shape_layer_repr); + } + + // Listen in case the active shape changes + cc->active_shape_repr = item->getRepr(); + if (cc->active_shape_repr) { + Inkscape::GC::anchor(cc->active_shape_repr); + sp_repr_add_listener(cc->active_shape_repr, &shape_repr_events, cc); + + cc->active_shape_layer_repr = cc->active_shape_repr->parent(); + Inkscape::GC::anchor(cc->active_shape_layer_repr); + sp_repr_add_listener(cc->active_shape_layer_repr, &layer_repr_events, cc); + } + + cc_clear_active_knots(cc->knots); + + // The idea here is to try and add a group's children to solidify + // connection handling. We react to path objects with only one node. + for (SPObject *child = item->firstChild() ; child ; child = child->getNext() ) { + if (SP_IS_PATH(child) && SP_PATH(child)->nodesInPath() == 1) { + cc_active_shape_add_knot(cc, (SPItem *) child); + } + } + cc_active_shape_add_knot(cc, item); + + } + else + { + // Ensure the item's connection_points map + // has been updated + item->document->ensureUpToDate(); + } +} + + +static void +cc_set_active_conn(ConnectorTool *cc, SPItem *item) +{ + g_assert( SP_IS_PATH(item) ); + + const SPCurve *curve = SP_PATH(item)->get_curve_reference(); + Geom::Affine i2dt = item->i2dt_affine(); + + if (cc->active_conn == item) + { + if (curve->is_empty()) + { + // Connector is invisible because it is clipped to the boundary of + // two overlpapping shapes. + sp_knot_hide(cc->endpt_handle[0]); + sp_knot_hide(cc->endpt_handle[1]); + } + else + { + // Just adjust handle positions. + Geom::Point startpt = *(curve->first_point()) * i2dt; + sp_knot_set_position(cc->endpt_handle[0], startpt, 0); + + Geom::Point endpt = *(curve->last_point()) * i2dt; + sp_knot_set_position(cc->endpt_handle[1], endpt, 0); + } + + return; + } + + cc->active_conn = item; + + // Remove existing active conn listeners + if (cc->active_conn_repr) { + sp_repr_remove_listener_by_data(cc->active_conn_repr, cc); + Inkscape::GC::release(cc->active_conn_repr); + cc->active_conn_repr = NULL; + } + + // Listen in case the active conn changes + cc->active_conn_repr = item->getRepr(); + if (cc->active_conn_repr) { + Inkscape::GC::anchor(cc->active_conn_repr); + sp_repr_add_listener(cc->active_conn_repr, &shape_repr_events, cc); + } + + for (int i = 0; i < 2; ++i) { + + // Create the handle if it doesn't exist + if ( cc->endpt_handle[i] == NULL ) { + SPKnot *knot = sp_knot_new(cc->desktop, + _("Connector endpoint: drag to reroute or connect to new shapes")); + + knot->setShape(SP_KNOT_SHAPE_SQUARE); + knot->setSize(7); + knot->setAnchor(SP_ANCHOR_CENTER); + knot->setFill(0xffffff00, 0xff0000ff, 0xff0000ff); + knot->setStroke(0x000000ff, 0x000000ff, 0x000000ff); + sp_knot_update_ctrl(knot); + + // We don't want to use the standard knot handler, + // since we don't want this knot to be draggable. + g_signal_handler_disconnect(G_OBJECT(knot->item), + knot->_event_handler_id); + knot->_event_handler_id = 0; + + g_signal_connect(G_OBJECT(knot->item), "event", + G_CALLBACK(cc_generic_knot_handler), knot); + + cc->endpt_handle[i] = knot; + } + + // Remove any existing handlers + if (cc->endpt_handler_id[i]) { + g_signal_handlers_disconnect_by_func( + G_OBJECT(cc->endpt_handle[i]->item), + (void*)G_CALLBACK(endpt_handler), (gpointer) cc ); + cc->endpt_handler_id[i] = 0; + } + + // Setup handlers for connector endpoints, this is + // is as 'after' so that cc_generic_knot_handler is + // triggered first for any endpoint. + cc->endpt_handler_id[i] = g_signal_connect_after( + G_OBJECT(cc->endpt_handle[i]->item), "event", + G_CALLBACK(endpt_handler), cc); + } + + if (curve->is_empty()) + { + // Connector is invisible because it is clipped to the boundary + // of two overlpapping shapes. So, it doesn't need endpoints. + return; + } + + Geom::Point startpt = *(curve->first_point()) * i2dt; + sp_knot_set_position(cc->endpt_handle[0], startpt, 0); + + Geom::Point endpt = *(curve->last_point()) * i2dt; + sp_knot_set_position(cc->endpt_handle[1], endpt, 0); + + sp_knot_show(cc->endpt_handle[0]); + sp_knot_show(cc->endpt_handle[1]); +} + +void cc_create_connection_point(ConnectorTool* cc) +{ + if (cc->active_shape && cc->state == SP_CONNECTOR_CONTEXT_IDLE) + { + if (cc->selected_handle) + { + cc_deselect_handle( cc->selected_handle ); + } + SPKnot *knot = sp_knot_new(cc->desktop, 0); + // We do not process events on this knot. + g_signal_handler_disconnect(G_OBJECT(knot->item), + knot->_event_handler_id); + knot->_event_handler_id = 0; + + cc_select_handle( knot ); + cc->selected_handle = knot; + sp_knot_show(cc->selected_handle); + cc->state = SP_CONNECTOR_CONTEXT_NEWCONNPOINT; + } +} + +static bool cc_item_is_shape(SPItem *item) +{ + if (SP_IS_PATH(item)) { + const SPCurve * curve = (SP_SHAPE(item))->_curve; + if ( curve && !(curve->is_closed()) ) { + // Open paths are connectors. + return false; + } + } + else if (SP_IS_TEXT(item) || SP_IS_FLOWTEXT(item)) { + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + if (prefs->getBool("/tools/connector/ignoretext", true)) { + // Don't count text as a shape we can connect connector to. + return false; + } + } + return true; +} + + +bool cc_item_is_connector(SPItem *item) +{ + if (SP_IS_PATH(item)) { + bool closed = SP_PATH(item)->get_curve_reference()->is_closed(); + if (SP_PATH(item)->connEndPair.isAutoRoutingConn() && !closed) { + // To be considered a connector, an object must be a non-closed + // path that is marked with a "inkscape:connector-type" attribute. + return true; + } + } + return false; +} + + +void cc_selection_set_avoid(bool const set_avoid) +{ + SPDesktop *desktop = inkscape_active_desktop(); + if (desktop == NULL) { + return; + } + + SPDocument *document = sp_desktop_document(desktop); + + Inkscape::Selection *selection = sp_desktop_selection(desktop); + + GSList *l = const_cast(selection->itemList()); + + int changes = 0; + + while (l) { + SPItem *item = SP_ITEM(l->data); + + char const *value = (set_avoid) ? "true" : NULL; + + if (cc_item_is_shape(item)) { + item->setAttribute("inkscape:connector-avoid", value, NULL); + item->avoidRef->handleSettingChange(); + changes++; + } + + l = l->next; + } + + if (changes == 0) { + desktop->messageStack()->flash(Inkscape::WARNING_MESSAGE, + _("Select at least one non-connector object.")); + return; + } + + char *event_desc = (set_avoid) ? + _("Make connectors avoid selected objects") : + _("Make connectors ignore selected objects"); + DocumentUndo::done(document, SP_VERB_CONTEXT_CONNECTOR, event_desc); +} + +void ConnectorTool::selection_changed(Inkscape::Selection *selection) { + SPItem *item = selection->singleItem(); + + if (this->active_conn == item) { + // Nothing to change. + return; + } + + if (item == NULL) { + cc_clear_active_conn(this); + return; + } + + if (cc_item_is_connector(item)) { + cc_set_active_conn(this, item); + } +} + +static void +shape_event_attr_deleted(Inkscape::XML::Node */*repr*/, Inkscape::XML::Node *child, + Inkscape::XML::Node */*ref*/, gpointer data) +{ + g_assert(data); + ConnectorTool *cc = SP_CONNECTOR_CONTEXT(data); + + if (child == cc->active_shape_repr) { + // The active shape has been deleted. Clear active shape. + cc_clear_active_shape(cc); + } +} + + +static void +shape_event_attr_changed(Inkscape::XML::Node *repr, gchar const *name, + gchar const */*old_value*/, gchar const */*new_value*/, + bool /*is_interactive*/, gpointer data) +{ + g_assert(data); + ConnectorTool *cc = SP_CONNECTOR_CONTEXT(data); + + // Look for changes that result in onscreen movement. + if (!strcmp(name, "d") || !strcmp(name, "x") || !strcmp(name, "y") || + !strcmp(name, "width") || !strcmp(name, "height") || + !strcmp(name, "transform")) + { + if (repr == cc->active_shape_repr) { + // Active shape has moved. Clear active shape. + cc_clear_active_shape(cc); + } + else if (repr == cc->active_conn_repr) { + // The active conn has been moved. + // Set it again, which just sets new handle positions. + cc_set_active_conn(cc, cc->active_conn); + } + } +} + +} +} +} + + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/ui/tools/connector-tool.h b/src/ui/tools/connector-tool.h new file mode 100644 index 000000000..7cd6a6dad --- /dev/null +++ b/src/ui/tools/connector-tool.h @@ -0,0 +1,135 @@ +#ifndef SEEN_CONNECTOR_CONTEXT_H +#define SEEN_CONNECTOR_CONTEXT_H + +/* + * Connector creation tool + * + * Authors: + * Michael Wybrow + * + * Copyright (C) 2005 Michael Wybrow + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include +#include +#include +#include "ui/tools/tool-base.h" +#include <2geom/point.h> +#include "libavoid/connector.h" +#include + +#define SP_CONNECTOR_CONTEXT(obj) (dynamic_cast((Inkscape::UI::Tools::ToolBase*)obj)) +//#define SP_IS_CONNECTOR_CONTEXT(obj) (dynamic_cast((const ToolBase*)obj) != NULL) + +struct SPKnot; +class SPCurve; + +namespace Inkscape +{ + class Selection; +} + +enum { + SP_CONNECTOR_CONTEXT_IDLE, + SP_CONNECTOR_CONTEXT_DRAGGING, + SP_CONNECTOR_CONTEXT_CLOSE, + SP_CONNECTOR_CONTEXT_STOP, + SP_CONNECTOR_CONTEXT_REROUTING, + SP_CONNECTOR_CONTEXT_NEWCONNPOINT +}; + +typedef std::map SPKnotList; + +namespace Inkscape { +namespace UI { +namespace Tools { + +class ConnectorTool : public ToolBase { +public: + ConnectorTool(); + virtual ~ConnectorTool(); + + Inkscape::Selection *selection; + Geom::Point p[5]; + + /** \invar npoints in {0, 2}. */ + gint npoints; + unsigned int state : 4; + + // Red curve + SPCanvasItem *red_bpath; + SPCurve *red_curve; + guint32 red_color; + + // Green curve + SPCurve *green_curve; + + // The new connector + SPItem *newconn; + Avoid::ConnRef *newConnRef; + gdouble curvature; + bool isOrthogonal; + + // The active shape + SPItem *active_shape; + Inkscape::XML::Node *active_shape_repr; + Inkscape::XML::Node *active_shape_layer_repr; + + // Same as above, but for the active connector + SPItem *active_conn; + Inkscape::XML::Node *active_conn_repr; + sigc::connection sel_changed_connection; + + // The activehandle + SPKnot *active_handle; + + // The selected handle, used in editing mode + SPKnot *selected_handle; + + SPItem *clickeditem; + SPKnot *clickedhandle; + + SPKnotList knots; + SPKnot *endpt_handle[2]; + guint endpt_handler_id[2]; + gchar *shref; + gchar *ehref; + SPCanvasItem *c0, *c1, *cl0, *cl1; + + static const std::string prefsPath; + + virtual void setup(); + virtual void finish(); + virtual void set(const Inkscape::Preferences::Entry& val); + virtual bool root_handler(GdkEvent* event); + virtual bool item_handler(SPItem* item, GdkEvent* event); + + virtual const std::string& getPrefsPath(); + +private: + void selection_changed(Inkscape::Selection *selection); +}; + +void cc_selection_set_avoid(bool const set_ignore); +void cc_create_connection_point(ConnectorTool* cc); +void cc_remove_connection_point(ConnectorTool* cc); +bool cc_item_is_connector(SPItem *item); + +} +} +} + +#endif /* !SEEN_CONNECTOR_CONTEXT_H */ + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/ui/tools/dropper-tool.cpp b/src/ui/tools/dropper-tool.cpp new file mode 100644 index 000000000..9c47b50e9 --- /dev/null +++ b/src/ui/tools/dropper-tool.cpp @@ -0,0 +1,414 @@ +/* + * Tool for picking colors from drawing + * + * Authors: + * Lauris Kaplinski + * bulia byak + * Abhishek Sharma + * + * Copyright (C) 1999-2005 Authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#ifdef HAVE_CONFIG_H +# include +#endif + +#include +#include +#include +#include <2geom/transforms.h> +#include <2geom/circle.h> + +#include "macros.h" +#include "display/canvas-bpath.h" +#include "display/canvas-arena.h" +#include "display/curve.h" +#include "display/cairo-utils.h" +#include "svg/svg-color.h" +#include "color.h" +#include "color-rgba.h" +#include "desktop-style.h" +#include "preferences.h" +#include "sp-namedview.h" +#include "sp-cursor.h" +#include "desktop.h" +#include "desktop-handles.h" +#include "selection.h" +#include "document.h" +#include "document-undo.h" + +#include "pixmaps/cursor-dropper-f.xpm" +#include "pixmaps/cursor-dropper-s.xpm" + +#include "ui/tools/dropper-tool.h" +#include "message-context.h" +#include "verbs.h" +#include "ui/tools/tool-base.h" + +using Inkscape::DocumentUndo; + +static GdkCursor *cursor_dropper_fill = NULL; +static GdkCursor *cursor_dropper_stroke = NULL; + +#include "tool-factory.h" + +namespace Inkscape { +namespace UI { +namespace Tools { + +namespace { + ToolBase* createDropperContext() { + return new DropperTool(); + } + + bool dropperContextRegistered = ToolFactory::instance().registerObject("/tools/dropper", createDropperContext); +} + +const std::string& DropperTool::getPrefsPath() { + return DropperTool::prefsPath; +} + +const std::string DropperTool::prefsPath = "/tools/dropper"; + +DropperTool::DropperTool() : ToolBase() { + this->R = 0; + this->G = 0; + this->B = 0; + this->alpha = 0; + this->dragging = false; + + this->grabbed = 0; + this->area = 0; + this->centre = Geom::Point(0, 0); + + this->cursor_shape = cursor_dropper_f_xpm; + this->hot_x = 7; + this->hot_y = 7; + + cursor_dropper_fill = sp_cursor_new_from_xpm(cursor_dropper_f_xpm , 7, 7); + cursor_dropper_stroke = sp_cursor_new_from_xpm(cursor_dropper_s_xpm , 7, 7); +} + +DropperTool::~DropperTool() { +} + +void DropperTool::setup() { + ToolBase::setup(); + + /* TODO: have a look at CalligraphicTool::setup where the same is done.. generalize? */ + Geom::PathVector path; + Geom::Circle(0, 0, 1).getPath(path); + + SPCurve *c = new SPCurve(path); + + this->area = sp_canvas_bpath_new(sp_desktop_controls(this->desktop), c); + + c->unref(); + + sp_canvas_bpath_set_fill(SP_CANVAS_BPATH(this->area), 0x00000000,(SPWindRule)0); + sp_canvas_bpath_set_stroke(SP_CANVAS_BPATH(this->area), 0x0000007f, 1.0, SP_STROKE_LINEJOIN_MITER, SP_STROKE_LINECAP_BUTT); + sp_canvas_item_hide(this->area); + + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + + if (prefs->getBool("/tools/dropper/selcue")) { + this->enableSelectionCue(); + } + + if (prefs->getBool("/tools/dropper/gradientdrag")) { + this->enableGrDrag(); + } +} + +void DropperTool::finish() { + this->enableGrDrag(false); + + if (this->grabbed) { + sp_canvas_item_ungrab(this->grabbed, GDK_CURRENT_TIME); + this->grabbed = NULL; + } + + if (this->area) { + sp_canvas_item_destroy(this->area); + this->area = NULL; + } + + if (cursor_dropper_fill) { +#if GTK_CHECK_VERSION(3,0,0) + g_object_unref(cursor_dropper_fill); +#else + gdk_cursor_unref (cursor_dropper_fill); +#endif + cursor_dropper_fill = NULL; + } + + if (cursor_dropper_stroke) { +#if GTK_CHECK_VERSION(3,0,0) + g_object_unref(cursor_dropper_stroke); +#else + gdk_cursor_unref (cursor_dropper_stroke); +#endif + cursor_dropper_fill = NULL; + } +} + +/** + * Returns the current dropper context color. + */ +guint32 DropperTool::get_color() { + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + + int pick = prefs->getInt("/tools/dropper/pick", SP_DROPPER_PICK_VISIBLE); + bool setalpha = prefs->getBool("/tools/dropper/setalpha", true); + + return SP_RGBA32_F_COMPOSE(this->R, + this->G, + this->B, + (pick == SP_DROPPER_PICK_ACTUAL && setalpha) ? this->alpha : 1.0); +} + +bool DropperTool::root_handler(GdkEvent* event) { + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + + int ret = FALSE; + + int pick = prefs->getInt("/tools/dropper/pick", SP_DROPPER_PICK_VISIBLE); + bool setalpha = prefs->getBool("/tools/dropper/setalpha", true); + + switch (event->type) { + case GDK_BUTTON_PRESS: + if (event->button.button == 1 && !this->space_panning) { + this->centre = Geom::Point(event->button.x, event->button.y); + this->dragging = true; + ret = TRUE; + } + + sp_canvas_item_grab(SP_CANVAS_ITEM(desktop->acetate), + GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK | GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK, + NULL, event->button.time); + this->grabbed = SP_CANVAS_ITEM(desktop->acetate); + + break; + case GDK_MOTION_NOTIFY: + if (event->motion.state & GDK_BUTTON2_MASK || event->motion.state & GDK_BUTTON3_MASK) { + // pass on middle and right drag + ret = FALSE; + break; + } else if (!this->space_panning) { + // otherwise, constantly calculate color no matter is any button pressed or not + + // If one time pick with stroke set the pixmap + if (prefs->getBool("/tools/dropper/onetimepick", false) && prefs->getInt("/dialogs/fillstroke/page", 0) == 1) { + //TODO Only set when not set already + GdkWindow* window = gtk_widget_get_window(GTK_WIDGET(sp_desktop_canvas(desktop))); + gdk_window_set_cursor(window, cursor_dropper_stroke); + } + + double rw = 0.0; + double R(0), G(0), B(0), A(0); + + if (this->dragging) { + // calculate average + + // radius + rw = std::min(Geom::L2(Geom::Point(event->button.x, event->button.y) - this->centre), 400.0); + + if (rw == 0) { // happens sometimes, little idea why... + break; + } + + Geom::Point const cd = desktop->w2d(this->centre); + Geom::Affine const w2dt = desktop->w2d(); + const double scale = rw * w2dt.descrim(); + Geom::Affine const sm( Geom::Scale(scale, scale) * Geom::Translate(cd) ); + sp_canvas_item_affine_absolute(this->area, sm); + sp_canvas_item_show(this->area); + + /* Get buffer */ + Geom::Rect r(this->centre, this->centre); + r.expandBy(rw); + if (!r.hasZeroArea()) { + Geom::IntRect area = r.roundOutwards(); + cairo_surface_t *s = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, area.width(), area.height()); + sp_canvas_arena_render_surface(SP_CANVAS_ARENA(sp_desktop_drawing(desktop)), s, area); + ink_cairo_surface_average_color_premul(s, R, G, B, A); + cairo_surface_destroy(s); + } + } else { + // pick single pixel + Geom::IntRect area = Geom::IntRect::from_xywh(floor(event->button.x), floor(event->button.y), 1, 1); + cairo_surface_t *s = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 1, 1); + sp_canvas_arena_render_surface(SP_CANVAS_ARENA(sp_desktop_drawing(desktop)), s, area); + ink_cairo_surface_average_color_premul(s, R, G, B, A); + cairo_surface_destroy(s); + } + + if (pick == SP_DROPPER_PICK_VISIBLE) { + // compose with page color + guint32 bg = sp_desktop_namedview(desktop)->pagecolor; + R = R + (SP_RGBA32_R_F(bg)) * (1 - A); + G = G + (SP_RGBA32_G_F(bg)) * (1 - A); + B = B + (SP_RGBA32_B_F(bg)) * (1 - A); + A = 1.0; + } else { + // un-premultiply color channels + if (A > 0) { + R /= A; + G /= A; + B /= A; + } + } + + if (fabs(A) < 1e-4) { + A = 0; // suppress exponentials, CSS does not allow that + } + + // remember color + this->R = R; + this->G = G; + this->B = B; + this->alpha = A; + + // status message + double alpha_to_set = setalpha? this->alpha : 1.0; + guint32 c32 = SP_RGBA32_F_COMPOSE(R, G, B, alpha_to_set); + + gchar c[64]; + sp_svg_write_color(c, sizeof(c), c32); + + // alpha of color under cursor, to show in the statusbar + // locale-sensitive printf is OK, since this goes to the UI, not into SVG + gchar *alpha = g_strdup_printf(_(" alpha %.3g"), alpha_to_set); + // where the color is picked, to show in the statusbar + gchar *where = this->dragging ? g_strdup_printf(_(", averaged with radius %d"), (int) rw) : g_strdup_printf("%s", _(" under cursor")); + // message, to show in the statusbar + const gchar *message = this->dragging ? _("Release mouse to set color.") : _("Click to set fill, Shift+click to set stroke; drag to average color in area; with Alt to pick inverse color; Ctrl+C to copy the color under mouse to clipboard"); + + this->defaultMessageContext()->setF( + Inkscape::NORMAL_MESSAGE, + "%s%s%s. %s", c, + (pick == SP_DROPPER_PICK_VISIBLE) ? "" : alpha, where, message); + + g_free(where); + g_free(alpha); + + ret = TRUE; + } + break; + + case GDK_BUTTON_RELEASE: + if (event->button.button == 1 && !this->space_panning) { + sp_canvas_item_hide(this->area); + this->dragging = false; + + if (this->grabbed) { + sp_canvas_item_ungrab(this->grabbed, event->button.time); + this->grabbed = NULL; + } + + double alpha_to_set = setalpha? this->alpha : 1.0; + + bool fill = !(event->button.state & GDK_SHIFT_MASK); // Stroke if Shift key held + + if (prefs->getBool("/tools/dropper/onetimepick", false)) { + // "One time" pick from Fill/Stroke dialog stroke page, always apply fill or stroke (ignore key) + fill = (prefs->getInt("/dialogs/fillstroke/page", 0) == 0) ? true : false; + } + + // do the actual color setting + sp_desktop_set_color(desktop, + (event->button.state & GDK_MOD1_MASK)? + ColorRGBA(1 - this->R, 1 - this->G, 1 - this->B, alpha_to_set) : ColorRGBA(this->R, this->G, this->B, alpha_to_set), + false, fill); + + // REJON: set aux. toolbar input to hex color! + + if (event->button.state & GDK_SHIFT_MASK) { + GdkWindow* window = gtk_widget_get_window(GTK_WIDGET(sp_desktop_canvas(desktop))); + gdk_window_set_cursor(window, cursor_dropper_stroke); + } + + if (!(sp_desktop_selection(desktop)->isEmpty())) { + DocumentUndo::done(sp_desktop_document(desktop), SP_VERB_CONTEXT_DROPPER, + _("Set picked color")); + } + + if (prefs->getBool("/tools/dropper/onetimepick", false)) { + prefs->setBool("/tools/dropper/onetimepick", false); + sp_toggle_dropper(desktop); + } + + ret = TRUE; + } + break; + + case GDK_KEY_PRESS: + switch (get_group0_keyval(&event->key)) { + case GDK_KEY_Up: + case GDK_KEY_Down: + case GDK_KEY_KP_Up: + case GDK_KEY_KP_Down: + // prevent the zoom field from activation + if (!MOD__CTRL_ONLY(event)) { + ret = TRUE; + } + break; + + case GDK_KEY_Escape: + sp_desktop_selection(desktop)->clear(); + case GDK_KEY_Shift_L: + case GDK_KEY_Shift_R: + if (!desktop->isWaitingCursor() && !prefs->getBool("/tools/dropper/onetimepick", false)) { + GdkWindow* window = gtk_widget_get_window(GTK_WIDGET(sp_desktop_canvas(desktop))); + gdk_window_set_cursor(window, cursor_dropper_stroke); + } + + break; + default: + break; + } + break; + + case GDK_KEY_RELEASE: + switch (get_group0_keyval(&event->key)) { + case GDK_KEY_Shift_L: + case GDK_KEY_Shift_R: + if (!desktop->isWaitingCursor() && !prefs->getBool("/tools/dropper/onetimepick", false)) { + GdkWindow* window = gtk_widget_get_window(GTK_WIDGET(sp_desktop_canvas(desktop))); + gdk_window_set_cursor(window, cursor_dropper_fill); + } + break; + + default: + break; + } + break; + + default: + break; + } + + if (!ret) { + ret = ToolBase::root_handler(event); + } + + return ret; +} + +} +} +} + + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 : diff --git a/src/ui/tools/dropper-tool.h b/src/ui/tools/dropper-tool.h new file mode 100644 index 000000000..dd6f25bb6 --- /dev/null +++ b/src/ui/tools/dropper-tool.h @@ -0,0 +1,73 @@ +#ifndef __SP_DROPPER_CONTEXT_H__ +#define __SP_DROPPER_CONTEXT_H__ + +/* + * Tool for picking colors from drawing + * + * Authors: + * Lauris Kaplinski + * + * Copyright (C) 1999-2002 Authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include "ui/tools/tool-base.h" + +#define SP_DROPPER_CONTEXT(obj) (dynamic_cast((Inkscape::UI::Tools::ToolBase*)obj)) +#define SP_IS_DROPPER_CONTEXT(obj) (dynamic_cast((const Inkscape::UI::Tools::ToolBase*)obj) != NULL) + +enum { + SP_DROPPER_PICK_VISIBLE, + SP_DROPPER_PICK_ACTUAL +}; + +namespace Inkscape { +namespace UI { +namespace Tools { + +class DropperTool : public ToolBase { +public: + DropperTool(); + virtual ~DropperTool(); + + static const std::string prefsPath; + + virtual const std::string& getPrefsPath(); + + guint32 get_color(); + +protected: + virtual void setup(); + virtual void finish(); + virtual bool root_handler(GdkEvent* event); + +private: + double R; + double G; + double B; + double alpha; + + bool dragging; + + SPCanvasItem* grabbed; + SPCanvasItem* area; + Geom::Point centre; +}; + +} +} +} + +#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:fileencoding=utf-8:textwidth=99 : diff --git a/src/ui/tools/dynamic-base.cpp b/src/ui/tools/dynamic-base.cpp new file mode 100644 index 000000000..cec58dce9 --- /dev/null +++ b/src/ui/tools/dynamic-base.cpp @@ -0,0 +1,162 @@ + +#include "ui/tools/dynamic-base.h" + +#include + +#include "config.h" + +#include "message-context.h" +#include "streq.h" +#include "preferences.h" +#include "display/sp-canvas-item.h" +#include "desktop.h" + +#define MIN_PRESSURE 0.0 +#define MAX_PRESSURE 1.0 +#define DEFAULT_PRESSURE 1.0 + +#define DRAG_MIN 0.0 +#define DRAG_DEFAULT 1.0 +#define DRAG_MAX 1.0 + +namespace Inkscape { +namespace UI { +namespace Tools { + +DynamicBase::DynamicBase() : + ToolBase(), + accumulated(NULL), + segments(NULL), + currentshape(NULL), + currentcurve(NULL), + cal1(NULL), + cal2(NULL), + point1(), + point2(), + repr(NULL), + cur(0,0), + vel(0,0), + vel_max(0), + acc(0,0), + ang(0,0), + last(0,0), + del(0,0), + pressure(DEFAULT_PRESSURE), + xtilt(0), + ytilt(0), + dragging(FALSE), + usepressure(FALSE), + usetilt(FALSE), + mass(0.3), + drag(DRAG_DEFAULT), + angle(30.0), + width(0.2), + vel_thin(0.1), + flatness(0.9), + tremor(0), + cap_rounding(0), + is_drawing(false), + abs_width(false) +{ +} + +DynamicBase::~DynamicBase() { + if (this->accumulated) { + this->accumulated = this->accumulated->unref(); + this->accumulated = 0; + } + + while (this->segments) { + sp_canvas_item_destroy(SP_CANVAS_ITEM(this->segments->data)); + this->segments = g_slist_remove(this->segments, this->segments->data); + } + + if (this->currentcurve) { + this->currentcurve = this->currentcurve->unref(); + this->currentcurve = 0; + } + + if (this->cal1) { + this->cal1 = this->cal1->unref(); + this->cal1 = 0; + } + + if (this->cal2) { + this->cal2 = this->cal2->unref(); + this->cal2 = 0; + } + + if (this->currentshape) { + sp_canvas_item_destroy(this->currentshape); + this->currentshape = 0; + } +} + +void DynamicBase::set(const Inkscape::Preferences::Entry& value) { + Glib::ustring path = value.getEntryName(); + + // ignore preset modifications + static Glib::ustring const presets_path = this->pref_observer->observed_path + "/preset"; + Glib::ustring const &full_path = value.getPath(); + + if (full_path.compare(0, presets_path.size(), presets_path) == 0) { + return; + } + + if (path == "mass") { + this->mass = 0.01 * CLAMP(value.getInt(10), 0, 100); + } else if (path == "wiggle") { + this->drag = CLAMP((1 - 0.01 * value.getInt()), DRAG_MIN, DRAG_MAX); // drag is inverse to wiggle + } else if (path == "angle") { + this->angle = CLAMP(value.getDouble(), -90, 90); + } else if (path == "width") { + this->width = 0.01 * CLAMP(value.getInt(10), 1, 100); + } else if (path == "thinning") { + this->vel_thin = 0.01 * CLAMP(value.getInt(10), -100, 100); + } else if (path == "tremor") { + this->tremor = 0.01 * CLAMP(value.getInt(), 0, 100); + } else if (path == "flatness") { + this->flatness = 0.01 * CLAMP(value.getInt(), 0, 100); + } else if (path == "usepressure") { + this->usepressure = value.getBool(); + } else if (path == "usetilt") { + this->usetilt = value.getBool(); + } else if (path == "abs_width") { + this->abs_width = value.getBool(); + } else if (path == "cap_rounding") { + this->cap_rounding = value.getDouble(); + } +} + +/* Get normalized point */ +Geom::Point DynamicBase::getNormalizedPoint(Geom::Point v) const { + Geom::Rect drect = this->desktop->get_display_area(); + + double const max = MAX ( drect.dimensions()[Geom::X], drect.dimensions()[Geom::Y] ); + + return Geom::Point(( v[Geom::X] - drect.min()[Geom::X] ) / max, ( v[Geom::Y] - drect.min()[Geom::Y] ) / max); +} + +/* Get view point */ +Geom::Point DynamicBase::getViewPoint(Geom::Point n) const { + Geom::Rect drect = this->desktop->get_display_area(); + + double const max = MAX ( drect.dimensions()[Geom::X], drect.dimensions()[Geom::Y] ); + + return Geom::Point(n[Geom::X] * max + drect.min()[Geom::X], n[Geom::Y] * max + drect.min()[Geom::Y]); +} + +} +} +} + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/ui/tools/dynamic-base.h b/src/ui/tools/dynamic-base.h new file mode 100644 index 000000000..9218eabd3 --- /dev/null +++ b/src/ui/tools/dynamic-base.h @@ -0,0 +1,123 @@ +#ifndef COMMON_CONTEXT_H_SEEN +#define COMMON_CONTEXT_H_SEEN + +/* + * Common drawing mode + * + * Authors: + * Mitsuru Oka + * Lauris Kaplinski + * + * The original dynadraw code: + * Paul Haeberli + * + * Copyright (C) 1998 The Free Software Foundation + * Copyright (C) 1999-2002 authors + * Copyright (C) 2001-2002 Ximian, Inc. + * Copyright (C) 2008 Jon A. Cruz + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include "ui/tools/tool-base.h" +#include "display/curve.h" +#include <2geom/point.h> + +#define SAMPLING_SIZE 8 /* fixme: ?? */ + +namespace Inkscape { +namespace UI { +namespace Tools { + +class DynamicBase : public ToolBase { +public: + DynamicBase(); + virtual ~DynamicBase(); + + virtual void set(const Inkscape::Preferences::Entry& val); + +protected: + /** accumulated shape which ultimately goes in svg:path */ + SPCurve *accumulated; + + /** canvas items for "comitted" segments */ + GSList *segments; + + /** canvas item for red "leading" segment */ + SPCanvasItem *currentshape; + /** shape of red "leading" segment */ + SPCurve *currentcurve; + + /** left edge of the stroke; combined to get accumulated */ + SPCurve *cal1; + + /** right edge of the stroke; combined to get accumulated */ + SPCurve *cal2; + + /** left edge points for this segment */ + Geom::Point point1[SAMPLING_SIZE]; + + /** right edge points for this segment */ + Geom::Point point2[SAMPLING_SIZE]; + + /** number of edge points for this segment */ + gint npoints; + + /* repr */ + Inkscape::XML::Node *repr; + + /* common */ + Geom::Point cur; + Geom::Point vel; + double vel_max; + Geom::Point acc; + Geom::Point ang; + Geom::Point last; + Geom::Point del; + + /* extended input data */ + gdouble pressure; + gdouble xtilt; + gdouble ytilt; + + /* attributes */ + guint dragging : 1; /* mouse state: mouse is dragging */ + guint usepressure : 1; + guint usetilt : 1; + double mass, drag; + double angle; + double width; + + double vel_thin; + double flatness; + double tremor; + double cap_rounding; + + //Inkscape::MessageContext *_message_context; + + bool is_drawing; + + /** uses absolute width independent of zoom */ + bool abs_width; + + Geom::Point getViewPoint(Geom::Point n) const; + Geom::Point getNormalizedPoint(Geom::Point v) const; +}; + +} +} +} + +#endif // COMMON_CONTEXT_H_SEEN + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : + diff --git a/src/ui/tools/eraser-tool.cpp b/src/ui/tools/eraser-tool.cpp new file mode 100644 index 000000000..270987d27 --- /dev/null +++ b/src/ui/tools/eraser-tool.cpp @@ -0,0 +1,1019 @@ +/* + * Eraser drawing mode + * + * Authors: + * Mitsuru Oka + * Lauris Kaplinski + * bulia byak + * MenTaLguY + * Jon A. Cruz + * Abhishek Sharma + * + * The original dynadraw code: + * Paul Haeberli + * + * Copyright (C) 1998 The Free Software Foundation + * Copyright (C) 1999-2005 authors + * Copyright (C) 2001-2002 Ximian, Inc. + * Copyright (C) 2005-2007 bulia byak + * Copyright (C) 2006 MenTaLguY + * Copyright (C) 2008 Jon A. Cruz + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#define noERASER_VERBOSE + +#include "config.h" + +#include +#include +#include +#include +#include +#include + +#include "svg/svg.h" +#include "display/sp-canvas.h" +#include "display/canvas-bpath.h" +#include <2geom/bezier-utils.h> + +#include +#include "macros.h" +#include "document.h" +#include "selection.h" +#include "desktop.h" +#include "desktop-events.h" +#include "desktop-handles.h" +#include "desktop-style.h" +#include "message-context.h" +#include "preferences.h" +#include "pixmaps/cursor-eraser.xpm" +#include "xml/repr.h" +#include "context-fns.h" +#include "sp-item.h" +#include "color.h" +#include "rubberband.h" +#include "splivarot.h" +#include "sp-item-group.h" +#include "sp-shape.h" +#include "sp-path.h" +#include "sp-text.h" +#include "display/canvas-bpath.h" +#include "display/canvas-arena.h" +#include "livarot/Shape.h" +#include "document-undo.h" +#include "verbs.h" +#include <2geom/math-utils.h> +#include <2geom/pathvector.h> + +#include "ui/tools/eraser-tool.h" + +using Inkscape::DocumentUndo; + +#define ERC_RED_RGBA 0xff0000ff + +#define TOLERANCE_ERASER 0.1 + +#define ERASER_EPSILON 0.5e-6 +#define ERASER_EPSILON_START 0.5e-2 +#define ERASER_VEL_START 1e-5 + +#define DRAG_MIN 0.0 +#define DRAG_DEFAULT 1.0 +#define DRAG_MAX 1.0 + +#include "tool-factory.h" + +namespace Inkscape { +namespace UI { +namespace Tools { + +namespace { + ToolBase* createEraserContext() { + return new EraserTool(); + } + + bool eraserContextRegistered = ToolFactory::instance().registerObject("/tools/eraser", createEraserContext); +} + +const std::string& EraserTool::getPrefsPath() { + return EraserTool::prefsPath; +} + +const std::string EraserTool::prefsPath = "/tools/eraser"; + +EraserTool::EraserTool() : DynamicBase() { + this->cursor_shape = cursor_eraser_xpm; + this->hot_x = 4; + this->hot_y = 4; +} + +EraserTool::~EraserTool() { +} + +void EraserTool::setup() { + DynamicBase::setup(); + + this->accumulated = new SPCurve(); + this->currentcurve = new SPCurve(); + + this->cal1 = new SPCurve(); + this->cal2 = new SPCurve(); + + this->currentshape = sp_canvas_item_new(sp_desktop_sketch(desktop), SP_TYPE_CANVAS_BPATH, NULL); + + sp_canvas_bpath_set_fill(SP_CANVAS_BPATH(this->currentshape), ERC_RED_RGBA, SP_WIND_RULE_EVENODD); + sp_canvas_bpath_set_stroke(SP_CANVAS_BPATH(this->currentshape), 0x00000000, 1.0, SP_STROKE_LINEJOIN_MITER, SP_STROKE_LINECAP_BUTT); + + /* fixme: Cannot we cascade it to root more clearly? */ + g_signal_connect(G_OBJECT(this->currentshape), "event", G_CALLBACK(sp_desktop_root_handler), desktop); + +/* +static ProfileFloatElement f_profile[PROFILE_FLOAT_SIZE] = { + {"mass",0.02, 0.0, 1.0}, + {"wiggle",0.0, 0.0, 1.0}, + {"angle",30.0, -90.0, 90.0}, + {"thinning",0.1, -1.0, 1.0}, + {"tremor",0.0, 0.0, 1.0}, + {"flatness",0.9, 0.0, 1.0}, + {"cap_rounding",0.0, 0.0, 5.0} +}; +*/ + + sp_event_context_read(this, "mass"); + sp_event_context_read(this, "wiggle"); + sp_event_context_read(this, "angle"); + sp_event_context_read(this, "width"); + sp_event_context_read(this, "thinning"); + sp_event_context_read(this, "tremor"); + sp_event_context_read(this, "flatness"); + sp_event_context_read(this, "tracebackground"); + sp_event_context_read(this, "usepressure"); + sp_event_context_read(this, "usetilt"); + sp_event_context_read(this, "abs_width"); + sp_event_context_read(this, "cap_rounding"); + + this->is_drawing = false; + + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + if (prefs->getBool("/tools/eraser/selcue", 0) != 0) { + this->enableSelectionCue(); + } + + // TODO temp force: + this->enableSelectionCue(); +} + +static double +flerp(double f0, double f1, double p) +{ + return f0 + ( f1 - f0 ) * p; +} + +void EraserTool::reset(Geom::Point p) { + this->last = this->cur = getNormalizedPoint(p); + this->vel = Geom::Point(0,0); + this->vel_max = 0; + this->acc = Geom::Point(0,0); + this->ang = Geom::Point(0,0); + this->del = Geom::Point(0,0); +} + +void EraserTool::extinput(GdkEvent *event) { + if (gdk_event_get_axis (event, GDK_AXIS_PRESSURE, &this->pressure)) + this->pressure = CLAMP (this->pressure, ERC_MIN_PRESSURE, ERC_MAX_PRESSURE); + else + this->pressure = ERC_DEFAULT_PRESSURE; + + if (gdk_event_get_axis (event, GDK_AXIS_XTILT, &this->xtilt)) + this->xtilt = CLAMP (this->xtilt, ERC_MIN_TILT, ERC_MAX_TILT); + else + this->xtilt = ERC_DEFAULT_TILT; + + if (gdk_event_get_axis (event, GDK_AXIS_YTILT, &this->ytilt)) + this->ytilt = CLAMP (this->ytilt, ERC_MIN_TILT, ERC_MAX_TILT); + else + this->ytilt = ERC_DEFAULT_TILT; +} + + +bool EraserTool::apply(Geom::Point p) { + Geom::Point n = getNormalizedPoint(p); + + /* Calculate mass and drag */ + double const mass = flerp(1.0, 160.0, this->mass); + double const drag = flerp(0.0, 0.5, this->drag * this->drag); + + /* Calculate force and acceleration */ + Geom::Point force = n - this->cur; + + // If force is below the absolute threshold ERASER_EPSILON, + // or we haven't yet reached ERASER_VEL_START (i.e. at the beginning of stroke) + // _and_ the force is below the (higher) ERASER_EPSILON_START threshold, + // discard this move. + // This prevents flips, blobs, and jerks caused by microscopic tremor of the tablet pen, + // especially bothersome at the start of the stroke where we don't yet have the inertia to + // smooth them out. + if ( Geom::L2(force) < ERASER_EPSILON || (this->vel_max < ERASER_VEL_START && Geom::L2(force) < ERASER_EPSILON_START)) { + return FALSE; + } + + this->acc = force / mass; + + /* Calculate new velocity */ + this->vel += this->acc; + + if (Geom::L2(this->vel) > this->vel_max) + this->vel_max = Geom::L2(this->vel); + + /* Calculate angle of drawing tool */ + + double a1; + if (this->usetilt) { + // 1a. calculate nib angle from input device tilt: + gdouble length = std::sqrt(this->xtilt*this->xtilt + this->ytilt*this->ytilt);; + + if (length > 0) { + Geom::Point ang1 = Geom::Point(this->ytilt/length, this->xtilt/length); + a1 = atan2(ang1); + } + else + a1 = 0.0; + } + else { + // 1b. fixed dc->angle (absolutely flat nib): + double const radians = ( (this->angle - 90) / 180.0 ) * M_PI; + Geom::Point ang1 = Geom::Point(-sin(radians), cos(radians)); + a1 = atan2(ang1); + } + + // 2. perpendicular to dc->vel (absolutely non-flat nib): + gdouble const mag_vel = Geom::L2(this->vel); + if ( mag_vel < ERASER_EPSILON ) { + return FALSE; + } + Geom::Point ang2 = Geom::rot90(this->vel) / mag_vel; + + // 3. Average them using flatness parameter: + // calculate angles + double a2 = atan2(ang2); + // flip a2 to force it to be in the same half-circle as a1 + bool flipped = false; + if (fabs (a2-a1) > 0.5*M_PI) { + a2 += M_PI; + flipped = true; + } + // normalize a2 + if (a2 > M_PI) + a2 -= 2*M_PI; + if (a2 < -M_PI) + a2 += 2*M_PI; + // find the flatness-weighted bisector angle, unflip if a2 was flipped + // FIXME: when dc->vel is oscillating around the fixed angle, the new_ang flips back and forth. How to avoid this? + double new_ang = a1 + (1 - this->flatness) * (a2 - a1) - (flipped? M_PI : 0); + + // Try to detect a sudden flip when the new angle differs too much from the previous for the + // current velocity; in that case discard this move + double angle_delta = Geom::L2(Geom::Point (cos (new_ang), sin (new_ang)) - this->ang); + if ( angle_delta / Geom::L2(this->vel) > 4000 ) { + return FALSE; + } + + // convert to point + this->ang = Geom::Point (cos (new_ang), sin (new_ang)); + +// g_print ("force %g acc %g vel_max %g vel %g a1 %g a2 %g new_ang %g\n", Geom::L2(force), Geom::L2(dc->acc), dc->vel_max, Geom::L2(dc->vel), a1, a2, new_ang); + + /* Apply drag */ + this->vel *= 1.0 - drag; + + /* Update position */ + this->last = this->cur; + this->cur += this->vel; + + return TRUE; +} + +void EraserTool::brush() { + g_assert( this->npoints >= 0 && this->npoints < SAMPLING_SIZE ); + + // How much velocity thins strokestyle + double vel_thin = flerp (0, 160, this->vel_thin); + + // Influence of pressure on thickness + double pressure_thick = (this->usepressure ? this->pressure : 1.0); + + // get the real brush point, not the same as pointer (affected by hatch tracking and/or mass + // drag) + Geom::Point brush = getViewPoint(this->cur); + //Geom::Point brush_w = SP_EVENT_CONTEXT(dc)->desktop->d2w(brush); + + double trace_thick = 1; + + double width = (pressure_thick * trace_thick - vel_thin * Geom::L2(this->vel)) * this->width; + + double tremble_left = 0, tremble_right = 0; + if (this->tremor > 0) { + // obtain two normally distributed random variables, using polar Box-Muller transform + double x1, x2, w, y1, y2; + do { + x1 = 2.0 * g_random_double_range(0,1) - 1.0; + x2 = 2.0 * g_random_double_range(0,1) - 1.0; + w = x1 * x1 + x2 * x2; + } while ( w >= 1.0 ); + w = sqrt( (-2.0 * log( w ) ) / w ); + y1 = x1 * w; + y2 = x2 * w; + + // deflect both left and right edges randomly and independently, so that: + // (1) dc->tremor=1 corresponds to sigma=1, decreasing dc->tremor narrows the bell curve; + // (2) deflection depends on width, but is upped for small widths for better visual uniformity across widths; + // (3) deflection somewhat depends on speed, to prevent fast strokes looking + // comparatively smooth and slow ones excessively jittery + tremble_left = (y1)*this->tremor * (0.15 + 0.8*width) * (0.35 + 14*Geom::L2(this->vel)); + tremble_right = (y2)*this->tremor * (0.15 + 0.8*width) * (0.35 + 14*Geom::L2(this->vel)); + } + + if ( width < 0.02 * this->width ) { + width = 0.02 * this->width; + } + + double dezoomify_factor = 0.05 * 1000; + if (!this->abs_width) { + dezoomify_factor /= SP_EVENT_CONTEXT(this)->desktop->current_zoom(); + } + + Geom::Point del_left = dezoomify_factor * (width + tremble_left) * this->ang; + Geom::Point del_right = dezoomify_factor * (width + tremble_right) * this->ang; + + this->point1[this->npoints] = brush + del_left; + this->point2[this->npoints] = brush - del_right; + + this->del = 0.5*(del_left + del_right); + + this->npoints++; +} + +static void +sp_erc_update_toolbox (SPDesktop *desktop, const gchar *id, double value) +{ + desktop->setToolboxAdjustmentValue (id, value); +} + +void EraserTool::cancel() { + SPDesktop *desktop = SP_EVENT_CONTEXT(this)->desktop; + this->dragging = FALSE; + this->is_drawing = false; + sp_canvas_item_ungrab(SP_CANVAS_ITEM(desktop->acetate), 0); + /* Remove all temporary line segments */ + while (this->segments) { + sp_canvas_item_destroy(SP_CANVAS_ITEM(this->segments->data)); + this->segments = g_slist_remove(this->segments, this->segments->data); + } + /* reset accumulated curve */ + this->accumulated->reset(); + this->clear_current(); + if (this->repr) { + this->repr = NULL; + } +} + +bool EraserTool::root_handler(GdkEvent* event) { + gint ret = FALSE; + + switch (event->type) { + case GDK_BUTTON_PRESS: + if (event->button.button == 1 && !this->space_panning) { + if (Inkscape::have_viable_layer(desktop, this->message_context) == false) { + return TRUE; + } + + Geom::Point const button_w(event->button.x, event->button.y); + Geom::Point const button_dt(desktop->w2d(button_w)); + + this->reset(button_dt); + this->extinput(event); + this->apply(button_dt); + + this->accumulated->reset(); + + if (this->repr) { + this->repr = NULL; + } + + Inkscape::Rubberband::get(desktop)->start(desktop, button_dt); + Inkscape::Rubberband::get(desktop)->setMode(RUBBERBAND_MODE_TOUCHPATH); + + /* initialize first point */ + this->npoints = 0; + + sp_canvas_item_grab(SP_CANVAS_ITEM(desktop->acetate), + ( GDK_KEY_PRESS_MASK | + GDK_BUTTON_RELEASE_MASK | + GDK_POINTER_MOTION_MASK | + GDK_BUTTON_PRESS_MASK ), + NULL, + event->button.time); + + ret = TRUE; + + desktop->canvas->forceFullRedrawAfterInterruptions(3); + this->is_drawing = true; + } + break; + + case GDK_MOTION_NOTIFY: { + Geom::Point const motion_w(event->motion.x, event->motion.y); + Geom::Point motion_dt(desktop->w2d(motion_w) + ); + this->extinput(event); + + this->message_context->clear(); + + if ( this->is_drawing && (event->motion.state & GDK_BUTTON1_MASK) && !this->space_panning) { + this->dragging = TRUE; + + this->message_context->set(Inkscape::NORMAL_MESSAGE, _("Drawing an eraser stroke")); + + if (!this->apply(motion_dt)) { + ret = TRUE; + break; + } + + if ( this->cur != this->last ) { + this->brush(); + g_assert( this->npoints > 0 ); + this->fit_and_split(false); + } + + ret = TRUE; + } + + Inkscape::Rubberband::get(desktop)->move(motion_dt); + } + break; + + case GDK_BUTTON_RELEASE: { + Geom::Point const motion_w(event->button.x, event->button.y); + Geom::Point const motion_dt(desktop->w2d(motion_w)); + + sp_canvas_item_ungrab(SP_CANVAS_ITEM(desktop->acetate), event->button.time); + desktop->canvas->endForcedFullRedraws(); + this->is_drawing = false; + + if (this->dragging && event->button.button == 1 && !this->space_panning) { + this->dragging = FALSE; + + this->apply(motion_dt); + + /* Remove all temporary line segments */ + while (this->segments) { + sp_canvas_item_destroy(SP_CANVAS_ITEM(this->segments->data)); + this->segments = g_slist_remove(this->segments, this->segments->data); + } + + /* Create object */ + this->fit_and_split(true); + this->accumulate(); + this->set_to_accumulated(); // performs document_done + + /* reset accumulated curve */ + this->accumulated->reset(); + + this->clear_current(); + if (this->repr) { + this->repr = NULL; + } + + this->message_context->clear(); + ret = TRUE; + } + + if (Inkscape::Rubberband::get(desktop)->is_started()) { + Inkscape::Rubberband::get(desktop)->stop(); + } + + break; + } + + case GDK_KEY_PRESS: + switch (get_group0_keyval (&event->key)) { + case GDK_KEY_Up: + case GDK_KEY_KP_Up: + if (!MOD__CTRL_ONLY(event)) { + this->angle += 5.0; + + if (this->angle > 90.0) { + this->angle = 90.0; + } + + sp_erc_update_toolbox (desktop, "eraser-angle", this->angle); + ret = TRUE; + } + break; + + case GDK_KEY_Down: + case GDK_KEY_KP_Down: + if (!MOD__CTRL_ONLY(event)) { + this->angle -= 5.0; + + if (this->angle < -90.0) { + this->angle = -90.0; + } + + sp_erc_update_toolbox (desktop, "eraser-angle", this->angle); + ret = TRUE; + } + break; + + case GDK_KEY_Right: + case GDK_KEY_KP_Right: + if (!MOD__CTRL_ONLY(event)) { + this->width += 0.01; + + if (this->width > 1.0) { + this->width = 1.0; + } + + sp_erc_update_toolbox (desktop, "altx-eraser", this->width * 100); // the same spinbutton is for alt+x + ret = TRUE; + } + break; + + case GDK_KEY_Left: + case GDK_KEY_KP_Left: + if (!MOD__CTRL_ONLY(event)) { + this->width -= 0.01; + + if (this->width < 0.01) { + this->width = 0.01; + } + + sp_erc_update_toolbox (desktop, "altx-eraser", this->width * 100); + ret = TRUE; + } + break; + + case GDK_KEY_Home: + case GDK_KEY_KP_Home: + this->width = 0.01; + sp_erc_update_toolbox (desktop, "altx-eraser", this->width * 100); + ret = TRUE; + break; + + case GDK_KEY_End: + case GDK_KEY_KP_End: + this->width = 1.0; + sp_erc_update_toolbox (desktop, "altx-eraser", this->width * 100); + ret = TRUE; + break; + + case GDK_KEY_x: + case GDK_KEY_X: + if (MOD__ALT_ONLY(event)) { + desktop->setToolboxFocusTo ("altx-eraser"); + ret = TRUE; + } + break; + + case GDK_KEY_Escape: + Inkscape::Rubberband::get(desktop)->stop(); + + if (this->is_drawing) { + // if drawing, cancel, otherwise pass it up for deselecting + this->cancel(); + ret = TRUE; + } + break; + + case GDK_KEY_z: + case GDK_KEY_Z: + if (MOD__CTRL_ONLY(event) && this->is_drawing) { + // if drawing, cancel, otherwise pass it up for undo + this->cancel(); + ret = TRUE; + } + break; + + default: + break; + } + break; + + case GDK_KEY_RELEASE: + switch (get_group0_keyval(&event->key)) { + case GDK_KEY_Control_L: + case GDK_KEY_Control_R: + this->message_context->clear(); + break; + + default: + break; + } + break; + + default: + break; + } + + if (!ret) { + ret = DynamicBase::root_handler(event); + } + + return ret; +} + +void EraserTool::clear_current() { + // reset bpath + sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(this->currentshape), NULL); + + // reset curve + this->currentcurve->reset(); + this->cal1->reset(); + this->cal2->reset(); + + // reset points + this->npoints = 0; +} + +void EraserTool::set_to_accumulated() { + bool workDone = false; + + if (!this->accumulated->is_empty()) { + if (!this->repr) { + /* Create object */ + Inkscape::XML::Document *xml_doc = desktop->doc()->getReprDoc(); + Inkscape::XML::Node *repr = xml_doc->createElement("svg:path"); + + /* Set style */ + sp_desktop_apply_style_tool (desktop, repr, "/tools/eraser", false); + + this->repr = repr; + + SPItem *item=SP_ITEM(desktop->currentLayer()->appendChildRepr(this->repr)); + Inkscape::GC::release(this->repr); + + item->transform = SP_ITEM(desktop->currentLayer())->i2doc_affine().inverse(); + item->updateRepr(); + } + + Geom::PathVector pathv = this->accumulated->get_pathvector() * desktop->dt2doc(); + gchar *str = sp_svg_write_path(pathv); + g_assert( str != NULL ); + this->repr->setAttribute("d", str); + g_free(str); + + if ( this->repr ) { + bool wasSelection = false; + Inkscape::Selection *selection = sp_desktop_selection(desktop); + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + + gint eraserMode = prefs->getBool("/tools/eraser/mode") ? 1 : 0; + Inkscape::XML::Document *xml_doc = desktop->doc()->getReprDoc(); + + SPItem* acid = SP_ITEM(desktop->doc()->getObjectByRepr(this->repr)); + Geom::OptRect eraserBbox = acid->visualBounds(); + Geom::Rect bounds = (*eraserBbox) * desktop->doc2dt(); + std::vector remainingItems; + GSList* toWorkOn = 0; + + if (selection->isEmpty()) { + if ( eraserMode ) { + toWorkOn = sp_desktop_document(desktop)->getItemsPartiallyInBox(desktop->dkey, bounds); + } else { + Inkscape::Rubberband *r = Inkscape::Rubberband::get(desktop); + toWorkOn = sp_desktop_document(desktop)->getItemsAtPoints(desktop->dkey, r->getPoints()); + } + + toWorkOn = g_slist_remove( toWorkOn, acid ); + } else { + toWorkOn = g_slist_copy(const_cast(selection->itemList())); + wasSelection = true; + } + + if ( g_slist_length(toWorkOn) > 0 ) { + if ( eraserMode ) { + for (GSList *i = toWorkOn ; i ; i = i->next ) { + SPItem *item = SP_ITEM(i->data); + + if ( eraserMode ) { + Geom::OptRect bbox = item->visualBounds(); + + if (bbox && bbox->intersects(*eraserBbox)) { + Inkscape::XML::Node* dup = this->repr->duplicate(xml_doc); + this->repr->parent()->appendChild(dup); + Inkscape::GC::release(dup); // parent takes over + + selection->set(item); + selection->add(dup); + sp_selected_path_diff_skip_undo(selection, desktop); + workDone = true; // TODO set this only if something was cut. + + if ( !selection->isEmpty() ) { + // If the item was not completely erased, track the new remainder. + GSList *nowSel = g_slist_copy(const_cast(selection->itemList())); + + for (GSList const *i2 = nowSel ; i2 ; i2 = i2->next ) { + remainingItems.push_back(SP_ITEM(i2->data)); + } + + g_slist_free(nowSel); + } + } else { + remainingItems.push_back(item); + } + } + } + } else { + for (GSList *i = toWorkOn ; i ; i = i->next ) { + sp_object_ref( SP_ITEM(i->data), 0 ); + } + + for (GSList *i = toWorkOn ; i ; i = i->next ) { + SPItem *item = SP_ITEM(i->data); + item->deleteObject(true); + sp_object_unref(item); + workDone = true; + } + } + + g_slist_free(toWorkOn); + + if ( !eraserMode ) { + //sp_selection_delete(desktop); + remainingItems.clear(); + } + + selection->clear(); + + if ( wasSelection ) { + if ( !remainingItems.empty() ) { + selection->add(remainingItems.begin(), remainingItems.end()); + } + } + } + + // Remove the eraser stroke itself: + sp_repr_unparent( this->repr ); + this->repr = 0; + } + } else { + if (this->repr) { + sp_repr_unparent(this->repr); + this->repr = 0; + } + } + + + if ( workDone ) { + DocumentUndo::done(sp_desktop_document(desktop), SP_VERB_CONTEXT_ERASER, _("Draw eraser stroke")); + } else { + DocumentUndo::cancel(sp_desktop_document(desktop)); + } +} + +static void +add_cap(SPCurve *curve, + Geom::Point const &pre, Geom::Point const &from, + Geom::Point const &to, Geom::Point const &post, + double rounding) +{ + Geom::Point vel = rounding * Geom::rot90( to - from ) / sqrt(2.0); + double mag = Geom::L2(vel); + + Geom::Point v_in = from - pre; + double mag_in = Geom::L2(v_in); + + if ( mag_in > ERASER_EPSILON ) { + v_in = mag * v_in / mag_in; + } else { + v_in = Geom::Point(0, 0); + } + + Geom::Point v_out = to - post; + double mag_out = Geom::L2(v_out); + + if ( mag_out > ERASER_EPSILON ) { + v_out = mag * v_out / mag_out; + } else { + v_out = Geom::Point(0, 0); + } + + if ( Geom::L2(v_in) > ERASER_EPSILON || Geom::L2(v_out) > ERASER_EPSILON ) { + curve->curveto(from + v_in, to + v_out, to); + } +} + +void EraserTool::accumulate() { + if ( !this->cal1->is_empty() && !this->cal2->is_empty() ) { + this->accumulated->reset(); /* Is this required ?? */ + SPCurve *rev_cal2 = this->cal2->create_reverse(); + + g_assert(this->cal1->get_segment_count() > 0); + g_assert(rev_cal2->get_segment_count() > 0); + g_assert( ! this->cal1->first_path()->closed() ); + g_assert( ! rev_cal2->first_path()->closed() ); + + Geom::CubicBezier const * dc_cal1_firstseg = dynamic_cast( this->cal1->first_segment() ); + Geom::CubicBezier const * rev_cal2_firstseg = dynamic_cast( rev_cal2->first_segment() ); + Geom::CubicBezier const * dc_cal1_lastseg = dynamic_cast( this->cal1->last_segment() ); + Geom::CubicBezier const * rev_cal2_lastseg = dynamic_cast( rev_cal2->last_segment() ); + + g_assert( dc_cal1_firstseg ); + g_assert( rev_cal2_firstseg ); + g_assert( dc_cal1_lastseg ); + g_assert( rev_cal2_lastseg ); + + this->accumulated->append(this->cal1, FALSE); + + add_cap(this->accumulated, (*dc_cal1_lastseg)[2], (*dc_cal1_lastseg)[3], (*rev_cal2_firstseg)[0], (*rev_cal2_firstseg)[1], this->cap_rounding); + + this->accumulated->append(rev_cal2, TRUE); + + add_cap(this->accumulated, (*rev_cal2_lastseg)[2], (*rev_cal2_lastseg)[3], (*dc_cal1_firstseg)[0], (*dc_cal1_firstseg)[1], this->cap_rounding); + + this->accumulated->closepath(); + + rev_cal2->unref(); + + this->cal1->reset(); + this->cal2->reset(); + } +} + +static double square(double const x) +{ + return x * x; +} + +void EraserTool::fit_and_split(bool release) { + double const tolerance_sq = square( desktop->w2d().descrim() * TOLERANCE_ERASER ); + +#ifdef ERASER_VERBOSE + g_print("[F&S:R=%c]", release?'T':'F'); +#endif + + if (!( this->npoints > 0 && this->npoints < SAMPLING_SIZE )) + return; // just clicked + + if ( this->npoints == SAMPLING_SIZE - 1 || release ) { +#define BEZIER_SIZE 4 +#define BEZIER_MAX_BEZIERS 8 +#define BEZIER_MAX_LENGTH ( BEZIER_SIZE * BEZIER_MAX_BEZIERS ) + +#ifdef ERASER_VERBOSE + g_print("[F&S:#] this->npoints:%d, release:%s\n", + dc->npoints, release ? "TRUE" : "FALSE"); +#endif + + /* Current eraser */ + if ( this->cal1->is_empty() || this->cal2->is_empty() ) { + /* dc->npoints > 0 */ + /* g_print("erasers(1|2) reset\n"); */ + this->cal1->reset(); + this->cal2->reset(); + + this->cal1->moveto(this->point1[0]); + this->cal2->moveto(this->point2[0]); + } + + Geom::Point b1[BEZIER_MAX_LENGTH]; + gint const nb1 = Geom::bezier_fit_cubic_r(b1, this->point1, this->npoints, tolerance_sq, BEZIER_MAX_BEZIERS); + g_assert( nb1 * BEZIER_SIZE <= gint(G_N_ELEMENTS(b1)) ); + + Geom::Point b2[BEZIER_MAX_LENGTH]; + gint const nb2 = Geom::bezier_fit_cubic_r(b2, this->point2, this->npoints, tolerance_sq, BEZIER_MAX_BEZIERS); + g_assert( nb2 * BEZIER_SIZE <= gint(G_N_ELEMENTS(b2)) ); + + if ( nb1 != -1 && nb2 != -1 ) { + /* Fit and draw and reset state */ +#ifdef ERASER_VERBOSE + g_print("nb1:%d nb2:%d\n", nb1, nb2); +#endif + + /* CanvasShape */ + if (! release) { + this->currentcurve->reset(); + this->currentcurve->moveto(b1[0]); + + for (Geom::Point *bp1 = b1; bp1 < b1 + BEZIER_SIZE * nb1; bp1 += BEZIER_SIZE) { + this->currentcurve->curveto(bp1[1], bp1[2], bp1[3]); + } + + this->currentcurve->lineto(b2[BEZIER_SIZE*(nb2-1) + 3]); + + for (Geom::Point *bp2 = b2 + BEZIER_SIZE * ( nb2 - 1 ); bp2 >= b2; bp2 -= BEZIER_SIZE) { + this->currentcurve->curveto(bp2[2], bp2[1], bp2[0]); + } + + // FIXME: this->segments is always NULL at this point?? + if (!this->segments) { // first segment + add_cap(this->currentcurve, b2[1], b2[0], b1[0], b1[1], this->cap_rounding); + } + + this->currentcurve->closepath(); + sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(this->currentshape), this->currentcurve); + } + + /* Current eraser */ + for (Geom::Point *bp1 = b1; bp1 < b1 + BEZIER_SIZE * nb1; bp1 += BEZIER_SIZE) { + this->cal1->curveto(bp1[1], bp1[2], bp1[3]); + } + + for (Geom::Point *bp2 = b2; bp2 < b2 + BEZIER_SIZE * nb2; bp2 += BEZIER_SIZE) { + this->cal2->curveto(bp2[1], bp2[2], bp2[3]); + } + } else { + /* fixme: ??? */ +#ifdef ERASER_VERBOSE + g_print("[fit_and_split] failed to fit-cubic.\n"); +#endif + this->draw_temporary_box(); + + for (gint i = 1; i < this->npoints; i++) { + this->cal1->lineto(this->point1[i]); + } + + for (gint i = 1; i < this->npoints; i++) { + this->cal2->lineto(this->point2[i]); + } + } + + /* Fit and draw and copy last point */ +#ifdef ERASER_VERBOSE + g_print("[%d]Yup\n", this->npoints); +#endif + if (!release) { + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + gint eraserMode = prefs->getBool("/tools/eraser/mode") ? 1 : 0; + g_assert(!this->currentcurve->is_empty()); + + SPCanvasItem *cbp = sp_canvas_item_new(sp_desktop_sketch(desktop), SP_TYPE_CANVAS_BPATH, NULL); + SPCurve *curve = this->currentcurve->copy(); + sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH (cbp), curve); + curve->unref(); + + guint32 fillColor = sp_desktop_get_color_tool (desktop, "/tools/eraser", true); + //guint32 strokeColor = sp_desktop_get_color_tool (desktop, "/tools/eraser", false); + double opacity = sp_desktop_get_master_opacity_tool (desktop, "/tools/eraser"); + double fillOpacity = sp_desktop_get_opacity_tool (desktop, "/tools/eraser", true); + //double strokeOpacity = sp_desktop_get_opacity_tool (desktop, "/tools/eraser", false); + sp_canvas_bpath_set_fill(SP_CANVAS_BPATH(cbp), ((fillColor & 0xffffff00) | SP_COLOR_F_TO_U(opacity*fillOpacity)), SP_WIND_RULE_EVENODD); + //on second thougtht don't do stroke yet because we don't have stoke-width yet and because stoke appears between segments while drawing + //sp_canvas_bpath_set_stroke(SP_CANVAS_BPATH(cbp), ((strokeColor & 0xffffff00) | SP_COLOR_F_TO_U(opacity*strokeOpacity)), 1.0, SP_STROKE_LINEJOIN_MITER, SP_STROKE_LINECAP_BUTT); + sp_canvas_bpath_set_stroke(SP_CANVAS_BPATH(cbp), 0x00000000, 1.0, SP_STROKE_LINEJOIN_MITER, SP_STROKE_LINECAP_BUTT); + /* fixme: Cannot we cascade it to root more clearly? */ + g_signal_connect(G_OBJECT(cbp), "event", G_CALLBACK(sp_desktop_root_handler), desktop); + + this->segments = g_slist_prepend(this->segments, cbp); + + if ( !eraserMode ) { + sp_canvas_item_hide(cbp); + sp_canvas_item_hide(this->currentshape); + } + } + + this->point1[0] = this->point1[this->npoints - 1]; + this->point2[0] = this->point2[this->npoints - 1]; + this->npoints = 1; + } else { + this->draw_temporary_box(); + } +} + +void EraserTool::draw_temporary_box() { + this->currentcurve->reset(); + + this->currentcurve->moveto(this->point1[this->npoints-1]); + + for (gint i = this->npoints-2; i >= 0; i--) { + this->currentcurve->lineto(this->point1[i]); + } + + for (gint i = 0; i < this->npoints; i++) { + this->currentcurve->lineto(this->point2[i]); + } + + if (this->npoints >= 2) { + add_cap(this->currentcurve, this->point2[this->npoints-2], this->point2[this->npoints-1], this->point1[this->npoints-1], this->point1[this->npoints-2], this->cap_rounding); + } + + this->currentcurve->closepath(); + sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(this->currentshape), this->currentcurve); +} + +} +} +} + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/ui/tools/eraser-tool.h b/src/ui/tools/eraser-tool.h new file mode 100644 index 000000000..eb7eb16e8 --- /dev/null +++ b/src/ui/tools/eraser-tool.h @@ -0,0 +1,76 @@ +#ifndef SP_ERASER_CONTEXT_H_SEEN +#define SP_ERASER_CONTEXT_H_SEEN + +/* + * Handwriting-like drawing mode + * + * Authors: + * Mitsuru Oka + * Lauris Kaplinski + * + * The original dynadraw code: + * Paul Haeberli + * + * Copyright (C) 1998 The Free Software Foundation + * Copyright (C) 1999-2002 authors + * Copyright (C) 2001-2002 Ximian, Inc. + * Copyright (C) 2008 Jon A. Cruz + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include "ui/tools/dynamic-base.h" + +#define ERC_MIN_PRESSURE 0.0 +#define ERC_MAX_PRESSURE 1.0 +#define ERC_DEFAULT_PRESSURE 1.0 + +#define ERC_MIN_TILT -1.0 +#define ERC_MAX_TILT 1.0 +#define ERC_DEFAULT_TILT 0.0 + +namespace Inkscape { +namespace UI { +namespace Tools { + +class EraserTool : public DynamicBase { +public: + EraserTool(); + virtual ~EraserTool(); + + static const std::string prefsPath; + + virtual void setup(); + virtual bool root_handler(GdkEvent* event); + + virtual const std::string& getPrefsPath(); + +private: + void reset(Geom::Point p); + void extinput(GdkEvent *event); + bool apply(Geom::Point p); + void brush(); + void cancel(); + void clear_current(); + void set_to_accumulated(); + void accumulate(); + void fit_and_split(bool release); + void draw_temporary_box(); +}; + +} +} +} + +#endif // SP_ERASER_CONTEXT_H_SEEN + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/ui/tools/flood-tool.cpp b/src/ui/tools/flood-tool.cpp new file mode 100644 index 000000000..0b72bc9f2 --- /dev/null +++ b/src/ui/tools/flood-tool.cpp @@ -0,0 +1,1263 @@ +/** + * @file + * Bucket fill drawing context, works by bitmap filling an area on a rendered version + * of the current display and then tracing the result using potrace. + */ +/* Author: + * Lauris Kaplinski + * bulia byak + * John Bintz + * Jon A. Cruz + * Abhishek Sharma + * + * Copyright (C) 2006 Johan Engelen + * Copyright (C) 2000-2005 authors + * Copyright (C) 2000-2001 Ximian, Inc. + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "trace/potrace/inkscape-potrace.h" +#include <2geom/pathvector.h> +#include +#include +#include +#include + +#include "color.h" +#include "context-fns.h" +#include "desktop.h" +#include "desktop-handles.h" +#include "desktop-style.h" +#include "display/cairo-utils.h" +#include "display/drawing-context.h" +#include "display/drawing-image.h" +#include "display/drawing-item.h" +#include "display/drawing.h" +#include "display/sp-canvas.h" +#include "document.h" +#include "document-undo.h" +#include "ui/tools/flood-tool.h" +#include "livarot/Path.h" +#include "livarot/Shape.h" +#include "macros.h" +#include "message-context.h" +#include "message-stack.h" +#include "preferences.h" +#include "rubberband.h" +#include "selection.h" +#include "shape-editor.h" +#include "sp-defs.h" +#include "sp-item.h" +#include "splivarot.h" +#include "sp-namedview.h" +#include "sp-object.h" +#include "sp-path.h" +#include "sp-rect.h" +#include "sp-root.h" +#include "svg/svg.h" +#include "trace/imagemap.h" +#include "trace/trace.h" +#include "xml/node-event-vector.h" +#include "xml/repr.h" +#include "verbs.h" + +#include "pixmaps/cursor-paintbucket.xpm" + +using Inkscape::DocumentUndo; + +using Inkscape::Display::ExtractARGB32; +using Inkscape::Display::ExtractRGB32; +using Inkscape::Display::AssembleARGB32; + +#include "tool-factory.h" + +namespace Inkscape { +namespace UI { +namespace Tools { + +namespace { + ToolBase* createPaintbucketContext() { + return new FloodTool(); + } + + bool paintbucketContextRegistered = ToolFactory::instance().registerObject("/tools/paintbucket", createPaintbucketContext); +} + +const std::string& FloodTool::getPrefsPath() { + return FloodTool::prefsPath; +} + +const std::string FloodTool::prefsPath = "/tools/paintbucket"; + +FloodTool::FloodTool() : ToolBase() { + this->cursor_shape = cursor_paintbucket_xpm; + this->hot_x = 11; + this->hot_y = 30; + this->xp = 0; + this->yp = 0; + this->tolerance = 4; + this->within_tolerance = false; + this->item_to_select = NULL; + + this->item = NULL; +} + +FloodTool::~FloodTool() { + this->sel_changed_connection.disconnect(); + + delete this->shape_editor; + this->shape_editor = NULL; + + /* fixme: This is necessary because we do not grab */ + if (this->item) { + this->finishItem(); + } +} + +/** + * Callback that processes the "changed" signal on the selection; + * destroys old and creates new knotholder. + */ +void FloodTool::selection_changed(Inkscape::Selection* selection) { + this->shape_editor->unset_item(SH_KNOTHOLDER); + this->shape_editor->set_item(selection->singleItem(), SH_KNOTHOLDER); +} + +void FloodTool::setup() { + ToolBase::setup(); + + this->shape_editor = new ShapeEditor(this->desktop); + + SPItem *item = sp_desktop_selection(this->desktop)->singleItem(); + if (item) { + this->shape_editor->set_item(item, SH_KNOTHOLDER); + } + + this->sel_changed_connection.disconnect(); + this->sel_changed_connection = sp_desktop_selection(this->desktop)->connectChanged( + sigc::mem_fun(this, &FloodTool::selection_changed) + ); + + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + + if (prefs->getBool("/tools/paintbucket/selcue")) { + this->enableSelectionCue(); + } +} + + +// Changes from 0.48 -> 0.49 (Cairo) +// 0.49: Ignores alpha in background +// 0.48: RGBA, 0.49 ARGB +// 0.49: premultiplied alpha +inline static guint32 compose_onto(guint32 px, guint32 bg) +{ + guint ap = 0, rp = 0, gp = 0, bp = 0; + guint rb = 0, gb = 0, bb = 0; + ExtractARGB32(px, ap, rp, gp, bp); + ExtractRGB32(bg, rb, gb, bb); + + // guint ao = 255*255 - (255-ap)*(255-bp); ao = (ao + 127) / 255; + // guint ao = (255-ap)*ab + 255*ap; ao = (ao + 127) / 255; + guint ao = 255; // Cairo version doesn't allow background to have alpha != 1. + guint ro = (255-ap)*rb + 255*rp; ro = (ro + 127) / 255; + guint go = (255-ap)*gb + 255*gp; go = (go + 127) / 255; + guint bo = (255-ap)*bb + 255*bp; bo = (bo + 127) / 255; + + guint pxout = AssembleARGB32(ao, ro, go, bo); + return pxout; +} + +/** + * Get the pointer to a pixel in a pixel buffer. + * @param px The pixel buffer. + * @param x The X coordinate. + * @param y The Y coordinate. + * @param stride The rowstride of the pixel buffer. + */ +inline guint32 get_pixel(guchar *px, int x, int y, int stride) { + return *reinterpret_cast(px + y * stride + x * 4); +} + +inline unsigned char * get_trace_pixel(guchar *trace_px, int x, int y, int width) { + return trace_px + (x + y * width); +} + +/** + * Generate the list of trace channel selection entries. + */ +GList * flood_channels_dropdown_items_list() { + GList *glist = NULL; + + glist = g_list_append (glist, _("Visible Colors")); + glist = g_list_append (glist, _("Red")); + glist = g_list_append (glist, _("Green")); + glist = g_list_append (glist, _("Blue")); + glist = g_list_append (glist, _("Hue")); + glist = g_list_append (glist, _("Saturation")); + glist = g_list_append (glist, _("Lightness")); + glist = g_list_append (glist, _("Alpha")); + + return glist; +} + +/** + * Generate the list of autogap selection entries. + */ +GList * flood_autogap_dropdown_items_list() { + GList *glist = NULL; + + glist = g_list_append (glist, (void*) C_("Flood autogap", "None")); + glist = g_list_append (glist, (void*) C_("Flood autogap", "Small")); + glist = g_list_append (glist, (void*) C_("Flood autogap", "Medium")); + glist = g_list_append (glist, (void*) C_("Flood autogap", "Large")); + + return glist; +} + +/** + * Compare a pixel in a pixel buffer with another pixel to determine if a point should be included in the fill operation. + * @param check The pixel in the pixel buffer to check. + * @param orig The original selected pixel to use as the fill target color. + * @param merged_orig_pixel The original pixel merged with the background. + * @param dtc The desktop background color. + * @param threshold The fill threshold. + * @param method The fill method to use as defined in PaintBucketChannels. + */ +static bool compare_pixels(guint32 check, guint32 orig, guint32 merged_orig_pixel, guint32 dtc, int threshold, PaintBucketChannels method) +{ + int diff = 0; + float hsl_check[3] = {0,0,0}, hsl_orig[3] = {0,0,0}; + + guint32 ac = 0, rc = 0, gc = 0, bc = 0; + ExtractARGB32(check, ac, rc, gc, bc); + + guint32 ao = 0, ro = 0, go = 0, bo = 0; + ExtractARGB32(orig, ao, ro, go, bo); + + guint32 ad = 0, rd = 0, gd = 0, bd = 0; + ExtractARGB32(dtc, ad, rd, gd, bd); + + guint32 amop = 0, rmop = 0, gmop = 0, bmop = 0; + ExtractARGB32(merged_orig_pixel, amop, rmop, gmop, bmop); + + if ((method == FLOOD_CHANNELS_H) || + (method == FLOOD_CHANNELS_S) || + (method == FLOOD_CHANNELS_L)) { + double dac = ac; + double dao = ao; + sp_color_rgb_to_hsl_floatv(hsl_check, rc / dac, gc / dac, bc / dac); + sp_color_rgb_to_hsl_floatv(hsl_orig, ro / dao, go / dao, bo / dao); + } + + switch (method) { + case FLOOD_CHANNELS_ALPHA: + return abs(static_cast(ac) - ao) <= threshold; + case FLOOD_CHANNELS_R: + return abs(static_cast(ac ? unpremul_alpha(rc, ac) : 0) - (ao ? unpremul_alpha(ro, ao) : 0)) <= threshold; + case FLOOD_CHANNELS_G: + return abs(static_cast(ac ? unpremul_alpha(gc, ac) : 0) - (ao ? unpremul_alpha(go, ao) : 0)) <= threshold; + case FLOOD_CHANNELS_B: + return abs(static_cast(ac ? unpremul_alpha(bc, ac) : 0) - (ao ? unpremul_alpha(bo, ao) : 0)) <= threshold; + case FLOOD_CHANNELS_RGB: + guint32 amc, rmc, bmc, gmc; + //amc = 255*255 - (255-ac)*(255-ad); amc = (amc + 127) / 255; + //amc = (255-ac)*ad + 255*ac; amc = (amc + 127) / 255; + amc = 255; // Why are we looking at desktop? Cairo version ignores destop alpha + rmc = (255-ac)*rd + 255*rc; rmc = (rmc + 127) / 255; + gmc = (255-ac)*gd + 255*gc; gmc = (gmc + 127) / 255; + bmc = (255-ac)*bd + 255*bc; bmc = (bmc + 127) / 255; + + diff += abs(static_cast(amc ? unpremul_alpha(rmc, amc) : 0) - (amop ? unpremul_alpha(rmop, amop) : 0)); + diff += abs(static_cast(amc ? unpremul_alpha(gmc, amc) : 0) - (amop ? unpremul_alpha(gmop, amop) : 0)); + diff += abs(static_cast(amc ? unpremul_alpha(bmc, amc) : 0) - (amop ? unpremul_alpha(bmop, amop) : 0)); + return ((diff / 3) <= ((threshold * 3) / 4)); + + case FLOOD_CHANNELS_H: + return ((int)(fabs(hsl_check[0] - hsl_orig[0]) * 100.0) <= threshold); + case FLOOD_CHANNELS_S: + return ((int)(fabs(hsl_check[1] - hsl_orig[1]) * 100.0) <= threshold); + case FLOOD_CHANNELS_L: + return ((int)(fabs(hsl_check[2] - hsl_orig[2]) * 100.0) <= threshold); + } + + return false; +} + +enum { + PIXEL_CHECKED = 1, + PIXEL_QUEUED = 2, + PIXEL_PAINTABLE = 4, + PIXEL_NOT_PAINTABLE = 8, + PIXEL_COLORED = 16 +}; + +static inline bool is_pixel_checked(unsigned char *t) { return (*t & PIXEL_CHECKED) == PIXEL_CHECKED; } +static inline bool is_pixel_queued(unsigned char *t) { return (*t & PIXEL_QUEUED) == PIXEL_QUEUED; } +static inline bool is_pixel_paintability_checked(unsigned char *t) { + return !((*t & PIXEL_PAINTABLE) == 0) && ((*t & PIXEL_NOT_PAINTABLE) == 0); +} +static inline bool is_pixel_paintable(unsigned char *t) { return (*t & PIXEL_PAINTABLE) == PIXEL_PAINTABLE; } +static inline bool is_pixel_colored(unsigned char *t) { return (*t & PIXEL_COLORED) == PIXEL_COLORED; } + +static inline void mark_pixel_checked(unsigned char *t) { *t |= PIXEL_CHECKED; } +static inline void mark_pixel_unchecked(unsigned char *t) { *t ^= PIXEL_CHECKED; } +static inline void mark_pixel_queued(unsigned char *t) { *t |= PIXEL_QUEUED; } +static inline void mark_pixel_paintable(unsigned char *t) { *t |= PIXEL_PAINTABLE; *t ^= PIXEL_NOT_PAINTABLE; } +static inline void mark_pixel_not_paintable(unsigned char *t) { *t |= PIXEL_NOT_PAINTABLE; *t ^= PIXEL_PAINTABLE; } +static inline void mark_pixel_colored(unsigned char *t) { *t |= PIXEL_COLORED; } + +static inline void clear_pixel_paintability(unsigned char *t) { *t ^= PIXEL_PAINTABLE; *t ^= PIXEL_NOT_PAINTABLE; } + +struct bitmap_coords_info { + bool is_left; + unsigned int x; + unsigned int y; + int y_limit; + unsigned int width; + unsigned int height; + unsigned int stride; + unsigned int threshold; + unsigned int radius; + PaintBucketChannels method; + guint32 dtc; + guint32 merged_orig_pixel; + Geom::Rect bbox; + Geom::Rect screen; + unsigned int max_queue_size; + unsigned int current_step; +}; + +/** + * Check if a pixel can be included in the fill. + * @param px The rendered pixel buffer to check. + * @param trace_t The pixel in the trace pixel buffer to check or mark. + * @param x The X coordinate. + * @param y The y coordinate. + * @param orig_color The original selected pixel to use as the fill target color. + * @param bci The bitmap_coords_info structure. + */ +inline static bool check_if_pixel_is_paintable(guchar *px, unsigned char *trace_t, int x, int y, guint32 orig_color, bitmap_coords_info bci) { + if (is_pixel_paintability_checked(trace_t)) { + return is_pixel_paintable(trace_t); + } else { + guint32 pixel = get_pixel(px, x, y, bci.stride); + if (compare_pixels(pixel, orig_color, bci.merged_orig_pixel, bci.dtc, bci.threshold, bci.method)) { + mark_pixel_paintable(trace_t); + return true; + } else { + mark_pixel_not_paintable(trace_t); + return false; + } + } +} + +/** + * Perform the bitmap-to-vector tracing and place the traced path onto the document. + * @param px The trace pixel buffer to trace to SVG. + * @param desktop The desktop on which to place the final SVG path. + * @param transform The transform to apply to the final SVG path. + * @param union_with_selection If true, merge the final SVG path with the current selection. + */ +static void do_trace(bitmap_coords_info bci, guchar *trace_px, SPDesktop *desktop, Geom::Affine transform, unsigned int min_x, unsigned int max_x, unsigned int min_y, unsigned int max_y, bool union_with_selection) { + SPDocument *document = sp_desktop_document(desktop); + + unsigned char *trace_t; + + GrayMap *gray_map = GrayMapCreate((max_x - min_x + 1), (max_y - min_y + 1)); + unsigned int gray_map_y = 0; + for (unsigned int y = min_y; y <= max_y; y++) { + unsigned long *gray_map_t = gray_map->rows[gray_map_y]; + + trace_t = get_trace_pixel(trace_px, min_x, y, bci.width); + for (unsigned int x = min_x; x <= max_x; x++) { + *gray_map_t = is_pixel_colored(trace_t) ? GRAYMAP_BLACK : GRAYMAP_WHITE; + gray_map_t++; + trace_t++; + } + gray_map_y++; + } + + Inkscape::Trace::Potrace::PotraceTracingEngine pte; + pte.keepGoing = 1; + std::vector results = pte.traceGrayMap(gray_map); + gray_map->destroy(gray_map); + + //XML Tree being used here directly while it shouldn't be...." + Inkscape::XML::Document *xml_doc = desktop->doc()->getReprDoc(); + + long totalNodeCount = 0L; + + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + double offset = prefs->getDouble("/tools/paintbucket/offset", 0.0); + + for (unsigned int i=0 ; icreateElement("svg:path"); + /* Set style */ + sp_desktop_apply_style_tool (desktop, pathRepr, "/tools/paintbucket", false); + + Geom::PathVector pathv = sp_svg_read_pathv(result.getPathData().c_str()); + Path *path = new Path; + path->LoadPathVector(pathv); + + if (offset != 0) { + + Shape *path_shape = new Shape(); + + path->ConvertWithBackData(0.03); + path->Fill(path_shape, 0); + delete path; + + Shape *expanded_path_shape = new Shape(); + + expanded_path_shape->ConvertToShape(path_shape, fill_nonZero); + path_shape->MakeOffset(expanded_path_shape, offset * desktop->current_zoom(), join_round, 4); + expanded_path_shape->ConvertToShape(path_shape, fill_positive); + + Path *expanded_path = new Path(); + + expanded_path->Reset(); + expanded_path_shape->ConvertToForme(expanded_path); + expanded_path->ConvertEvenLines(1.0); + expanded_path->Simplify(1.0); + + delete path_shape; + delete expanded_path_shape; + + gchar *str = expanded_path->svg_dump_path(); + if (str && *str) { + pathRepr->setAttribute("d", str); + g_free(str); + } else { + desktop->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Too much inset, the result is empty.")); + Inkscape::GC::release(pathRepr); + g_free(str); + return; + } + + delete expanded_path; + + } else { + gchar *str = path->svg_dump_path(); + delete path; + pathRepr->setAttribute("d", str); + g_free(str); + } + + desktop->currentLayer()->addChild(pathRepr,NULL); + + SPObject *reprobj = document->getObjectByRepr(pathRepr); + if (reprobj) { + SP_ITEM(reprobj)->doWriteTransform(pathRepr, transform, NULL); + + // premultiply the item transform by the accumulated parent transform in the paste layer + Geom::Affine local (SP_GROUP(desktop->currentLayer())->i2doc_affine()); + if (!local.isIdentity()) { + gchar const *t_str = pathRepr->attribute("transform"); + Geom::Affine item_t (Geom::identity()); + if (t_str) + sp_svg_transform_read(t_str, &item_t); + item_t *= local.inverse(); + // (we're dealing with unattached repr, so we write to its attr instead of using sp_item_set_transform) + gchar *affinestr=sp_svg_transform_write(item_t); + pathRepr->setAttribute("transform", affinestr); + g_free(affinestr); + } + + Inkscape::Selection *selection = sp_desktop_selection(desktop); + + pathRepr->setPosition(-1); + + if (union_with_selection) { + desktop->messageStack()->flashF( Inkscape::WARNING_MESSAGE, + ngettext("Area filled, path with %d node created and unioned with selection.","Area filled, path with %d nodes created and unioned with selection.", + SP_PATH(reprobj)->nodesInPath()), SP_PATH(reprobj)->nodesInPath() ); + selection->add(reprobj); + sp_selected_path_union_skip_undo(sp_desktop_selection(desktop), desktop); + } else { + desktop->messageStack()->flashF( Inkscape::WARNING_MESSAGE, + ngettext("Area filled, path with %d node created.","Area filled, path with %d nodes created.", + SP_PATH(reprobj)->nodesInPath()), SP_PATH(reprobj)->nodesInPath() ); + selection->set(reprobj); + } + + } + + Inkscape::GC::release(pathRepr); + + } +} + +/** + * The possible return states of perform_bitmap_scanline_check(). + */ +enum ScanlineCheckResult { + SCANLINE_CHECK_OK, + SCANLINE_CHECK_ABORTED, + SCANLINE_CHECK_BOUNDARY +}; + +/** + * Determine if the provided coordinates are within the pixel buffer limits. + * @param x The X coordinate. + * @param y The Y coordinate. + * @param bci The bitmap_coords_info structure. + */ +inline static bool coords_in_range(unsigned int x, unsigned int y, bitmap_coords_info bci) { + return (x < bci.width) && + (y < bci.height); +} + +#define PAINT_DIRECTION_LEFT 1 +#define PAINT_DIRECTION_RIGHT 2 +#define PAINT_DIRECTION_UP 4 +#define PAINT_DIRECTION_DOWN 8 +#define PAINT_DIRECTION_ALL 15 + +/** + * Paint a pixel or a square (if autogap is enabled) on the trace pixel buffer. + * @param px The rendered pixel buffer to check. + * @param trace_px The trace pixel buffer. + * @param orig_color The original selected pixel to use as the fill target color. + * @param bci The bitmap_coords_info structure. + * @param original_point_trace_t The original pixel in the trace pixel buffer to check. + */ +inline static unsigned int paint_pixel(guchar *px, guchar *trace_px, guint32 orig_color, bitmap_coords_info bci, unsigned char *original_point_trace_t) { + if (bci.radius == 0) { + mark_pixel_colored(original_point_trace_t); + return PAINT_DIRECTION_ALL; + } else { + unsigned char *trace_t; + + bool can_paint_up = true; + bool can_paint_down = true; + bool can_paint_left = true; + bool can_paint_right = true; + + for (unsigned int ty = bci.y - bci.radius; ty <= bci.y + bci.radius; ty++) { + for (unsigned int tx = bci.x - bci.radius; tx <= bci.x + bci.radius; tx++) { + if (coords_in_range(tx, ty, bci)) { + trace_t = get_trace_pixel(trace_px, tx, ty, bci.width); + if (!is_pixel_colored(trace_t)) { + if (check_if_pixel_is_paintable(px, trace_t, tx, ty, orig_color, bci)) { + mark_pixel_colored(trace_t); + } else { + if (tx < bci.x) { can_paint_left = false; } + if (tx > bci.x) { can_paint_right = false; } + if (ty < bci.y) { can_paint_up = false; } + if (ty > bci.y) { can_paint_down = false; } + } + } + } + } + } + + unsigned int paint_directions = 0; + if (can_paint_left) { paint_directions += PAINT_DIRECTION_LEFT; } + if (can_paint_right) { paint_directions += PAINT_DIRECTION_RIGHT; } + if (can_paint_up) { paint_directions += PAINT_DIRECTION_UP; } + if (can_paint_down) { paint_directions += PAINT_DIRECTION_DOWN; } + + return paint_directions; + } +} + +/** + * Push a point to be checked onto the bottom of the rendered pixel buffer check queue. + * @param fill_queue The fill queue to add the point to. + * @param max_queue_size The maximum size of the fill queue. + * @param trace_t The trace pixel buffer pixel. + * @param x The X coordinate. + * @param y The Y coordinate. + */ +static void push_point_onto_queue(std::deque *fill_queue, unsigned int max_queue_size, unsigned char *trace_t, unsigned int x, unsigned int y) { + if (!is_pixel_queued(trace_t)) { + if ((fill_queue->size() < max_queue_size)) { + fill_queue->push_back(Geom::Point(x, y)); + mark_pixel_queued(trace_t); + } + } +} + +/** + * Shift a point to be checked onto the top of the rendered pixel buffer check queue. + * @param fill_queue The fill queue to add the point to. + * @param max_queue_size The maximum size of the fill queue. + * @param trace_t The trace pixel buffer pixel. + * @param x The X coordinate. + * @param y The Y coordinate. + */ +static void shift_point_onto_queue(std::deque *fill_queue, unsigned int max_queue_size, unsigned char *trace_t, unsigned int x, unsigned int y) { + if (!is_pixel_queued(trace_t)) { + if ((fill_queue->size() < max_queue_size)) { + fill_queue->push_front(Geom::Point(x, y)); + mark_pixel_queued(trace_t); + } + } +} + +/** + * Scan a row in the rendered pixel buffer and add points to the fill queue as necessary. + * @param fill_queue The fill queue to add the point to. + * @param px The rendered pixel buffer. + * @param trace_px The trace pixel buffer. + * @param orig_color The original selected pixel to use as the fill target color. + * @param bci The bitmap_coords_info structure. + */ +static ScanlineCheckResult perform_bitmap_scanline_check(std::deque *fill_queue, guchar *px, guchar *trace_px, guint32 orig_color, bitmap_coords_info bci, unsigned int *min_x, unsigned int *max_x) { + bool aborted = false; + bool reached_screen_boundary = false; + bool ok; + + bool keep_tracing; + bool initial_paint = true; + + unsigned char *current_trace_t = get_trace_pixel(trace_px, bci.x, bci.y, bci.width); + unsigned int paint_directions; + + bool currently_painting_top = false; + bool currently_painting_bottom = false; + + unsigned int top_ty = bci.y - 1; + unsigned int bottom_ty = bci.y + 1; + + bool can_paint_top = (top_ty > 0); + bool can_paint_bottom = (bottom_ty < bci.height); + + Geom::Point t = fill_queue->front(); + + do { + ok = false; + if (bci.is_left) { + keep_tracing = (bci.x != 0); + } else { + keep_tracing = (bci.x < bci.width); + } + + *min_x = MIN(*min_x, bci.x); + *max_x = MAX(*max_x, bci.x); + + if (keep_tracing) { + if (check_if_pixel_is_paintable(px, current_trace_t, bci.x, bci.y, orig_color, bci)) { + paint_directions = paint_pixel(px, trace_px, orig_color, bci, current_trace_t); + if (bci.radius == 0) { + mark_pixel_checked(current_trace_t); + if ((t[Geom::X] == bci.x) && (t[Geom::Y] == bci.y)) { + fill_queue->pop_front(); t = fill_queue->front(); + } + } + + if (can_paint_top) { + if (paint_directions & PAINT_DIRECTION_UP) { + unsigned char *trace_t = current_trace_t - bci.width; + if (!is_pixel_queued(trace_t)) { + bool ok_to_paint = check_if_pixel_is_paintable(px, trace_t, bci.x, top_ty, orig_color, bci); + + if (initial_paint) { currently_painting_top = !ok_to_paint; } + + if (ok_to_paint && (!currently_painting_top)) { + currently_painting_top = true; + push_point_onto_queue(fill_queue, bci.max_queue_size, trace_t, bci.x, top_ty); + } + if ((!ok_to_paint) && currently_painting_top) { + currently_painting_top = false; + } + } + } + } + + if (can_paint_bottom) { + if (paint_directions & PAINT_DIRECTION_DOWN) { + unsigned char *trace_t = current_trace_t + bci.width; + if (!is_pixel_queued(trace_t)) { + bool ok_to_paint = check_if_pixel_is_paintable(px, trace_t, bci.x, bottom_ty, orig_color, bci); + + if (initial_paint) { currently_painting_bottom = !ok_to_paint; } + + if (ok_to_paint && (!currently_painting_bottom)) { + currently_painting_bottom = true; + push_point_onto_queue(fill_queue, bci.max_queue_size, trace_t, bci.x, bottom_ty); + } + if ((!ok_to_paint) && currently_painting_bottom) { + currently_painting_bottom = false; + } + } + } + } + + if (bci.is_left) { + if (paint_directions & PAINT_DIRECTION_LEFT) { + bci.x--; current_trace_t--; + ok = true; + } + } else { + if (paint_directions & PAINT_DIRECTION_RIGHT) { + bci.x++; current_trace_t++; + ok = true; + } + } + + initial_paint = false; + } + } else { + if (bci.bbox.min()[Geom::X] > bci.screen.min()[Geom::X]) { + aborted = true; break; + } else { + reached_screen_boundary = true; + } + } + } while (ok); + + if (aborted) { return SCANLINE_CHECK_ABORTED; } + if (reached_screen_boundary) { return SCANLINE_CHECK_BOUNDARY; } + return SCANLINE_CHECK_OK; +} + +/** + * Sort the rendered pixel buffer check queue vertically. + */ +static bool sort_fill_queue_vertical(Geom::Point a, Geom::Point b) { + return a[Geom::Y] > b[Geom::Y]; +} + +/** + * Sort the rendered pixel buffer check queue horizontally. + */ +static bool sort_fill_queue_horizontal(Geom::Point a, Geom::Point b) { + return a[Geom::X] > b[Geom::X]; +} + +/** + * Perform a flood fill operation. + * @param event_context The event context for this tool. + * @param event The details of this event. + * @param union_with_selection If true, union the new fill with the current selection. + * @param is_point_fill If false, use the Rubberband "touch selection" to get the initial points for the fill. + * @param is_touch_fill If true, use only the initial contact point in the Rubberband "touch selection" as the fill target color. + */ +static void sp_flood_do_flood_fill(ToolBase *event_context, GdkEvent *event, bool union_with_selection, bool is_point_fill, bool is_touch_fill) { + SPDesktop *desktop = event_context->desktop; + SPDocument *document = sp_desktop_document(desktop); + + document->ensureUpToDate(); + + Geom::OptRect bbox = document->getRoot()->visualBounds(); + + if (!bbox) { + desktop->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Area is not bounded, cannot fill.")); + return; + } + + double zoom_scale = desktop->current_zoom(); + + // Render 160% of the physical display to the render pixel buffer, so that available + // fill areas off the screen can be included in the fill. + double padding = 1.6; + + Geom::Rect screen = desktop->get_display_area(); + + unsigned int width = (int)ceil(screen.width() * zoom_scale * padding); + unsigned int height = (int)ceil(screen.height() * zoom_scale * padding); + + Geom::Point origin(screen.min()[Geom::X], + document->getHeight().value("px") - screen.height() - screen.min()[Geom::Y]); + + origin[Geom::X] += (screen.width() * ((1 - padding) / 2)); + origin[Geom::Y] += (screen.height() * ((1 - padding) / 2)); + + Geom::Scale scale(zoom_scale, zoom_scale); + Geom::Affine affine = scale * Geom::Translate(-origin * scale); + + int stride = cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, width); + guchar *px = g_new(guchar, stride * height); + guint32 bgcolor, dtc; + + // Draw image into data block px + { // this block limits the lifetime of Drawing and DrawingContext + /* Create DrawingItems and set transform */ + unsigned dkey = SPItem::display_key_new(1); + Inkscape::Drawing drawing; + Inkscape::DrawingItem *root = document->getRoot()->invoke_show( drawing, dkey, SP_ITEM_SHOW_DISPLAY); + root->setTransform(affine); + drawing.setRoot(root); + + Geom::IntRect final_bbox = Geom::IntRect::from_xywh(0, 0, width, height); + drawing.update(final_bbox); + + cairo_surface_t *s = cairo_image_surface_create_for_data( + px, CAIRO_FORMAT_ARGB32, width, height, stride); + Inkscape::DrawingContext ct(s, Geom::Point(0,0)); + // cairo_translate not necessary here - surface origin is at 0,0 + + SPNamedView *nv = sp_desktop_namedview(desktop); + bgcolor = nv->pagecolor; + // bgcolor is 0xrrggbbaa, we need 0xaarrggbb + dtc = (bgcolor >> 8) | (bgcolor << 24); + + ct.setSource(bgcolor); + ct.setOperator(CAIRO_OPERATOR_SOURCE); + ct.paint(); + ct.setOperator(CAIRO_OPERATOR_OVER); + + drawing.render(ct, final_bbox); + + //cairo_surface_write_to_png( s, "cairo.png" ); + + cairo_surface_flush(s); + cairo_surface_destroy(s); + + // Hide items + document->getRoot()->invoke_hide(dkey); + } + + // { + // // Dump data to png + // cairo_surface_t *s = cairo_image_surface_create_for_data( + // px, CAIRO_FORMAT_ARGB32, width, height, stride); + // cairo_surface_write_to_png( s, "cairo2.png" ); + // std::cout << " Wrote cairo2.png" << std::endl; + // } + + guchar *trace_px = g_new(guchar, width * height); + memset(trace_px, 0x00, width * height); + + std::deque fill_queue; + std::queue color_queue; + + std::vector fill_points; + + bool aborted = false; + int y_limit = height - 1; + + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + PaintBucketChannels method = (PaintBucketChannels) prefs->getInt("/tools/paintbucket/channels", 0); + int threshold = prefs->getIntLimited("/tools/paintbucket/threshold", 1, 0, 100); + + switch(method) { + case FLOOD_CHANNELS_ALPHA: + case FLOOD_CHANNELS_RGB: + case FLOOD_CHANNELS_R: + case FLOOD_CHANNELS_G: + case FLOOD_CHANNELS_B: + threshold = (255 * threshold) / 100; + break; + case FLOOD_CHANNELS_H: + case FLOOD_CHANNELS_S: + case FLOOD_CHANNELS_L: + break; + } + + bitmap_coords_info bci; + + bci.y_limit = y_limit; + bci.width = width; + bci.height = height; + bci.stride = stride; + bci.threshold = threshold; + bci.method = method; + bci.bbox = *bbox; + bci.screen = screen; + bci.dtc = dtc; + bci.radius = prefs->getIntLimited("/tools/paintbucket/autogap", 0, 0, 3); + bci.max_queue_size = (width * height) / 4; + bci.current_step = 0; + + if (is_point_fill) { + fill_points.push_back(Geom::Point(event->button.x, event->button.y)); + } else { + Inkscape::Rubberband *r = Inkscape::Rubberband::get(desktop); + fill_points = r->getPoints(); + } + + for (unsigned int i = 0; i < fill_points.size(); i++) { + Geom::Point pw = Geom::Point(fill_points[i][Geom::X] / zoom_scale, document->getHeight().value("px") + (fill_points[i][Geom::Y] / zoom_scale)) * affine; + + pw[Geom::X] = (int)MIN(width - 1, MAX(0, pw[Geom::X])); + pw[Geom::Y] = (int)MIN(height - 1, MAX(0, pw[Geom::Y])); + + if (is_touch_fill) { + if (i == 0) { + color_queue.push(pw); + } else { + unsigned char *trace_t = get_trace_pixel(trace_px, (int)pw[Geom::X], (int)pw[Geom::Y], width); + push_point_onto_queue(&fill_queue, bci.max_queue_size, trace_t, (int)pw[Geom::X], (int)pw[Geom::Y]); + } + } else { + color_queue.push(pw); + } + } + + bool reached_screen_boundary = false; + + bool first_run = true; + + unsigned long sort_size_threshold = 5; + + unsigned int min_y = height; + unsigned int max_y = 0; + unsigned int min_x = width; + unsigned int max_x = 0; + + while (!color_queue.empty() && !aborted) { + Geom::Point color_point = color_queue.front(); + color_queue.pop(); + + int cx = (int)color_point[Geom::X]; + int cy = (int)color_point[Geom::Y]; + + guint32 orig_color = get_pixel(px, cx, cy, stride); + bci.merged_orig_pixel = compose_onto(orig_color, dtc); + + unsigned char *trace_t = get_trace_pixel(trace_px, cx, cy, width); + if (!is_pixel_checked(trace_t) && !is_pixel_colored(trace_t)) { + if (check_if_pixel_is_paintable(px, trace_px, cx, cy, orig_color, bci)) { + shift_point_onto_queue(&fill_queue, bci.max_queue_size, trace_t, cx, cy); + + if (!first_run) { + for (unsigned int y = 0; y < height; y++) { + trace_t = get_trace_pixel(trace_px, 0, y, width); + for (unsigned int x = 0; x < width; x++) { + clear_pixel_paintability(trace_t); + trace_t++; + } + } + } + first_run = false; + } + } + + unsigned long old_fill_queue_size = fill_queue.size(); + + while (!fill_queue.empty() && !aborted) { + Geom::Point cp = fill_queue.front(); + + if (bci.radius == 0) { + unsigned long new_fill_queue_size = fill_queue.size(); + + /* + * To reduce the number of points in the fill queue, periodically + * resort all of the points in the queue so that scanline checks + * can complete more quickly. A point cannot be checked twice + * in a normal scanline checks, so forcing scanline checks to start + * from one corner of the rendered area as often as possible + * will reduce the number of points that need to be checked and queued. + */ + if (new_fill_queue_size > sort_size_threshold) { + if (new_fill_queue_size > old_fill_queue_size) { + std::sort(fill_queue.begin(), fill_queue.end(), sort_fill_queue_vertical); + + std::deque::iterator start_sort = fill_queue.begin(); + std::deque::iterator end_sort = fill_queue.begin(); + unsigned int sort_y = (unsigned int)cp[Geom::Y]; + unsigned int current_y = sort_y; + + for (std::deque::iterator i = fill_queue.begin(); i != fill_queue.end(); ++i) { + Geom::Point current = *i; + current_y = (unsigned int)current[Geom::Y]; + if (current_y != sort_y) { + if (start_sort != end_sort) { + std::sort(start_sort, end_sort, sort_fill_queue_horizontal); + } + sort_y = current_y; + start_sort = i; + } + end_sort = i; + } + if (start_sort != end_sort) { + std::sort(start_sort, end_sort, sort_fill_queue_horizontal); + } + + cp = fill_queue.front(); + } + } + + old_fill_queue_size = new_fill_queue_size; + } + + fill_queue.pop_front(); + + int x = (int)cp[Geom::X]; + int y = (int)cp[Geom::Y]; + + min_y = MIN((unsigned int)y, min_y); + max_y = MAX((unsigned int)y, max_y); + + unsigned char *trace_t = get_trace_pixel(trace_px, x, y, width); + if (!is_pixel_checked(trace_t)) { + mark_pixel_checked(trace_t); + + if (y == 0) { + if (bbox->min()[Geom::Y] > screen.min()[Geom::Y]) { + aborted = true; break; + } else { + reached_screen_boundary = true; + } + } + + if (y == y_limit) { + if (bbox->max()[Geom::Y] < screen.max()[Geom::Y]) { + aborted = true; break; + } else { + reached_screen_boundary = true; + } + } + + bci.is_left = true; + bci.x = x; + bci.y = y; + + ScanlineCheckResult result = perform_bitmap_scanline_check(&fill_queue, px, trace_px, orig_color, bci, &min_x, &max_x); + + switch (result) { + case SCANLINE_CHECK_ABORTED: + aborted = true; + break; + case SCANLINE_CHECK_BOUNDARY: + reached_screen_boundary = true; + break; + default: + break; + } + + if (bci.x < width) { + trace_t++; + if (!is_pixel_checked(trace_t) && !is_pixel_queued(trace_t)) { + mark_pixel_checked(trace_t); + bci.is_left = false; + bci.x = x + 1; + + result = perform_bitmap_scanline_check(&fill_queue, px, trace_px, orig_color, bci, &min_x, &max_x); + + switch (result) { + case SCANLINE_CHECK_ABORTED: + aborted = true; + break; + case SCANLINE_CHECK_BOUNDARY: + reached_screen_boundary = true; + break; + default: + break; + } + } + } + } + + bci.current_step++; + + if (bci.current_step > bci.max_queue_size) { + aborted = true; + } + } + } + + g_free(px); + + if (aborted) { + g_free(trace_px); + desktop->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Area is not bounded, cannot fill.")); + return; + } + + if (reached_screen_boundary) { + desktop->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Only the visible part of the bounded area was filled. If you want to fill all of the area, undo, zoom out, and fill again.")); + } + + unsigned int trace_padding = bci.radius + 1; + if (min_y > trace_padding) { min_y -= trace_padding; } + if (max_y < (y_limit - trace_padding)) { max_y += trace_padding; } + if (min_x > trace_padding) { min_x -= trace_padding; } + if (max_x < (width - 1 - trace_padding)) { max_x += trace_padding; } + + Geom::Point min_start = Geom::Point(min_x, min_y); + + affine = scale * Geom::Translate(-origin * scale - min_start); + Geom::Affine inverted_affine = Geom::Affine(affine).inverse(); + + do_trace(bci, trace_px, desktop, inverted_affine, min_x, max_x, min_y, max_y, union_with_selection); + + g_free(trace_px); + + DocumentUndo::done(document, SP_VERB_CONTEXT_PAINTBUCKET, _("Fill bounded area")); +} + +bool FloodTool::item_handler(SPItem* item, GdkEvent* event) { + gint ret = FALSE; + + switch (event->type) { + case GDK_BUTTON_PRESS: + if ((event->button.state & GDK_CONTROL_MASK) && event->button.button == 1 && !this->space_panning) { + Geom::Point const button_w(event->button.x, event->button.y); + + SPItem *item = sp_event_context_find_item (desktop, button_w, TRUE, TRUE); + + // Set style + desktop->applyCurrentOrToolStyle(item, "/tools/paintbucket", false); + + DocumentUndo::done(sp_desktop_document(desktop), SP_VERB_CONTEXT_PAINTBUCKET, _("Set style on object")); + + ret = TRUE; + } + break; + + default: + break; + } + +// if (((ToolBaseClass *) sp_flood_context_parent_class)->item_handler) { +// ret = ((ToolBaseClass *) sp_flood_context_parent_class)->item_handler(event_context, item, event); +// } + // CPPIFY: ret is overwritten... + ret = ToolBase::item_handler(item, event); + + return ret; +} + +bool FloodTool::root_handler(GdkEvent* event) { + static bool dragging; + + gint ret = FALSE; + + switch (event->type) { + case GDK_BUTTON_PRESS: + if (event->button.button == 1 && !this->space_panning) { + if (!(event->button.state & GDK_CONTROL_MASK)) { + Geom::Point const button_w(event->button.x, event->button.y); + + if (Inkscape::have_viable_layer(desktop, this->defaultMessageContext())) { + // save drag origin + this->xp = (gint) button_w[Geom::X]; + this->yp = (gint) button_w[Geom::Y]; + this->within_tolerance = true; + + dragging = true; + + Geom::Point const p(desktop->w2d(button_w)); + Inkscape::Rubberband::get(desktop)->setMode(RUBBERBAND_MODE_TOUCHPATH); + Inkscape::Rubberband::get(desktop)->start(desktop, p); + } + } + } + + case GDK_MOTION_NOTIFY: + if ( dragging && ( event->motion.state & GDK_BUTTON1_MASK ) && !this->space_panning) { + if ( this->within_tolerance + && ( abs( (gint) event->motion.x - this->xp ) < this->tolerance ) + && ( abs( (gint) event->motion.y - this->yp ) < this->tolerance ) ) { + break; // do not drag if we're within tolerance from origin + } + + this->within_tolerance = false; + + Geom::Point const motion_pt(event->motion.x, event->motion.y); + Geom::Point const p(desktop->w2d(motion_pt)); + + if (Inkscape::Rubberband::get(desktop)->is_started()) { + Inkscape::Rubberband::get(desktop)->move(p); + this->defaultMessageContext()->set(Inkscape::NORMAL_MESSAGE, _("Draw over areas to add to fill, hold Alt for touch fill")); + gobble_motion_events(GDK_BUTTON1_MASK); + } + } + break; + + case GDK_BUTTON_RELEASE: + if (event->button.button == 1 && !this->space_panning) { + Inkscape::Rubberband *r = Inkscape::Rubberband::get(desktop); + + if (r->is_started()) { + // set "busy" cursor + desktop->setWaitingCursor(); + + if (SP_IS_EVENT_CONTEXT(this)) { + // Since setWaitingCursor runs main loop iterations, we may have already left this tool! + // So check if the tool is valid before doing anything + dragging = false; + + bool is_point_fill = this->within_tolerance; + bool is_touch_fill = event->button.state & GDK_MOD1_MASK; + + sp_flood_do_flood_fill(this, event, event->button.state & GDK_SHIFT_MASK, is_point_fill, is_touch_fill); + + desktop->clearWaitingCursor(); + // restore cursor when done; note that it may already be different if e.g. user + // switched to another tool during interruptible tracing or drawing, in which case do nothing + + ret = TRUE; + } + + r->stop(); + + //if (SP_IS_EVENT_CONTEXT(this)) { + this->defaultMessageContext()->clear(); + //} + } + } + break; + case GDK_KEY_PRESS: + switch (get_group0_keyval (&event->key)) { + case GDK_KEY_Up: + case GDK_KEY_Down: + case GDK_KEY_KP_Up: + case GDK_KEY_KP_Down: + // prevent the zoom field from activation + if (!MOD__CTRL_ONLY(event)) + ret = TRUE; + break; + default: + break; + } + break; + + default: + break; + } + + if (!ret) { + ret = ToolBase::root_handler(event); + } + + return ret; +} + +void FloodTool::finishItem() { + this->message_context->clear(); + + if (this->item != NULL) { + this->item->updateRepr(); + + desktop->canvas->endForcedFullRedraws(); + + sp_desktop_selection(desktop)->set(this->item); + + DocumentUndo::done(sp_desktop_document(desktop), SP_VERB_CONTEXT_PAINTBUCKET, _("Fill bounded area")); + + this->item = NULL; + } +} + +void FloodTool::set_channels(gint channels) { + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + prefs->setInt("/tools/paintbucket/channels", channels); +} + +} +} +} + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/ui/tools/flood-tool.h b/src/ui/tools/flood-tool.h new file mode 100644 index 000000000..77dd2f13a --- /dev/null +++ b/src/ui/tools/flood-tool.h @@ -0,0 +1,74 @@ +#ifndef __SP_FLOOD_CONTEXT_H__ +#define __SP_FLOOD_CONTEXT_H__ + +/* + * Flood fill drawing context + * + * Authors: + * Lauris Kaplinski + * John Bintz + * + * Released under GNU GPL + */ + +#include +#include +#include +#include "ui/tools/tool-base.h" + +#define SP_FLOOD_CONTEXT(obj) (dynamic_cast((Inkscape::UI::Tools::ToolBase*)obj)) +#define SP_IS_FLOOD_CONTEXT(obj) (dynamic_cast((const Inkscape::UI::Tools::ToolBase*)obj) != NULL) + + +#define FLOOD_COLOR_CHANNEL_R 1 +#define FLOOD_COLOR_CHANNEL_G 2 +#define FLOOD_COLOR_CHANNEL_B 4 +#define FLOOD_COLOR_CHANNEL_A 8 + +namespace Inkscape { +namespace UI { +namespace Tools { + +class FloodTool : public ToolBase { +public: + FloodTool(); + virtual ~FloodTool(); + + SPItem *item; + + sigc::connection sel_changed_connection; + + static const std::string prefsPath; + + virtual void setup(); + virtual bool root_handler(GdkEvent* event); + virtual bool item_handler(SPItem* item, GdkEvent* event); + + virtual const std::string& getPrefsPath(); + + static void set_channels(gint channels); + +private: + void selection_changed(Inkscape::Selection* selection); + void finishItem(); +}; + +GList* flood_channels_dropdown_items_list (void); +GList* flood_autogap_dropdown_items_list (void); + +enum PaintBucketChannels { + FLOOD_CHANNELS_RGB, + FLOOD_CHANNELS_R, + FLOOD_CHANNELS_G, + FLOOD_CHANNELS_B, + FLOOD_CHANNELS_H, + FLOOD_CHANNELS_S, + FLOOD_CHANNELS_L, + FLOOD_CHANNELS_ALPHA +}; + +} +} +} + +#endif diff --git a/src/ui/tools/freehand-base.cpp b/src/ui/tools/freehand-base.cpp new file mode 100644 index 000000000..6cf1d7a5b --- /dev/null +++ b/src/ui/tools/freehand-base.cpp @@ -0,0 +1,785 @@ +/* + * Generic drawing context + * + * Author: + * Lauris Kaplinski + * Abhishek Sharma + * Jon A. Cruz + * + * Copyright (C) 2000 Lauris Kaplinski + * Copyright (C) 2000-2001 Ximian, Inc. + * Copyright (C) 2002 Lauris Kaplinski + * Copyright (C) 2012 Johan Engelen + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#define DRAW_VERBOSE + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "live_effects/lpe-patternalongpath.h" +#include "display/canvas-bpath.h" +#include "xml/repr.h" +#include "svg/svg.h" +#include +#include "display/curve.h" +#include "desktop.h" +#include "desktop-handles.h" +#include "desktop-style.h" +#include "document.h" +#include "draw-anchor.h" +#include "macros.h" +#include "message-stack.h" +#include "ui/tools/pen-tool.h" +#include "ui/tools/lpe-tool.h" +#include "preferences.h" +#include "selection.h" +#include "selection-chemistry.h" +#include "snap.h" +#include "sp-path.h" +#include "sp-namedview.h" +#include "live_effects/lpe-powerstroke.h" +#include "style.h" +#include "ui/control-manager.h" +#include "ui/tools/freehand-base.h" + +#include + +using Inkscape::DocumentUndo; + +namespace Inkscape { +namespace UI { +namespace Tools { + +static void spdc_selection_changed(Inkscape::Selection *sel, FreehandBase *dc); +static void spdc_selection_modified(Inkscape::Selection *sel, guint flags, FreehandBase *dc); + +static void spdc_attach_selection(FreehandBase *dc, Inkscape::Selection *sel); + +/** + * Flushes white curve(s) and additional curve into object. + * + * No cleaning of colored curves - this has to be done by caller + * No rereading of white data, so if you cannot rely on ::modified, do it in caller + */ +static void spdc_flush_white(FreehandBase *dc, SPCurve *gc); + +static void spdc_reset_white(FreehandBase *dc); +static void spdc_free_colors(FreehandBase *dc); + +FreehandBase::FreehandBase() : ToolBase() { + this->selection = 0; + this->grab = 0; + this->anchor_statusbar = false; + + this->attach = FALSE; + + this->red_color = 0xff00007f; + this->blue_color = 0x0000ff7f; + this->green_color = 0x00ff007f; + this->red_curve_is_valid = false; + + this->red_bpath = NULL; + this->red_curve = NULL; + + this->blue_bpath = NULL; + this->blue_curve = NULL; + + this->green_bpaths = NULL; + this->green_curve = NULL; + this->green_anchor = NULL; + this->green_closed = false; + + this->white_item = NULL; + this->white_curves = NULL; + this->white_anchors = NULL; + + this->sa = NULL; + this->ea = NULL; + + this->waiting_LPE_type = Inkscape::LivePathEffect::INVALID_LPE; +} + +FreehandBase::~FreehandBase() { + if (this->grab) { + sp_canvas_item_ungrab(this->grab, GDK_CURRENT_TIME); + this->grab = NULL; + } + + if (this->selection) { + this->selection = NULL; + } + + spdc_free_colors(this); +} + +void FreehandBase::setup() { + ToolBase::setup(); + + this->selection = sp_desktop_selection(desktop); + + // Connect signals to track selection changes + this->sel_changed_connection = this->selection->connectChanged( + sigc::bind(sigc::ptr_fun(&spdc_selection_changed), this) + ); + this->sel_modified_connection = this->selection->connectModified( + sigc::bind(sigc::ptr_fun(&spdc_selection_modified), this) + ); + + // Create red bpath + this->red_bpath = sp_canvas_bpath_new(sp_desktop_sketch(this->desktop), NULL); + sp_canvas_bpath_set_stroke(SP_CANVAS_BPATH(this->red_bpath), this->red_color, 1.0, SP_STROKE_LINEJOIN_MITER, SP_STROKE_LINECAP_BUTT); + + // Create red curve + this->red_curve = new SPCurve(); + + // Create blue bpath + this->blue_bpath = sp_canvas_bpath_new(sp_desktop_sketch(this->desktop), NULL); + sp_canvas_bpath_set_stroke(SP_CANVAS_BPATH(this->blue_bpath), this->blue_color, 1.0, SP_STROKE_LINEJOIN_MITER, SP_STROKE_LINECAP_BUTT); + + // Create blue curve + this->blue_curve = new SPCurve(); + + // Create green curve + this->green_curve = new SPCurve(); + + // No green anchor by default + this->green_anchor = NULL; + this->green_closed = FALSE; + + this->attach = TRUE; + spdc_attach_selection(this, this->selection); +} + +void FreehandBase::finish() { + this->sel_changed_connection.disconnect(); + this->sel_modified_connection.disconnect(); + + if (this->grab) { + sp_canvas_item_ungrab(this->grab, GDK_CURRENT_TIME); + } + + if (this->selection) { + this->selection = NULL; + } + + spdc_free_colors(this); +} + +void FreehandBase::set(const Inkscape::Preferences::Entry& /*value*/) { +} + +bool FreehandBase::root_handler(GdkEvent* event) { + gint ret = FALSE; + + switch (event->type) { + case GDK_KEY_PRESS: + switch (get_group0_keyval (&event->key)) { + case GDK_KEY_Up: + case GDK_KEY_Down: + case GDK_KEY_KP_Up: + case GDK_KEY_KP_Down: + // prevent the zoom field from activation + if (!MOD__CTRL_ONLY(event)) { + ret = TRUE; + } + break; + default: + break; + } + break; + default: + break; + } + + if (!ret) { + ret = ToolBase::root_handler(event); + } + + return ret; +} + +static Glib::ustring const tool_name(FreehandBase *dc) +{ + return ( SP_IS_PEN_CONTEXT(dc) + ? "/tools/freehand/pen" + : "/tools/freehand/pencil" ); +} + +static void spdc_paste_curve_as_freehand_shape(const SPCurve *c, FreehandBase *dc, SPItem *item) +{ + using namespace Inkscape::LivePathEffect; + + // TODO: Don't paste path if nothing is on the clipboard + + Effect::createAndApply(PATTERN_ALONG_PATH, dc->desktop->doc(), item); + Effect* lpe = SP_LPE_ITEM(item)->getCurrentLPE(); + gchar *svgd = sp_svg_write_path(c->get_pathvector()); + static_cast(lpe)->pattern.paste_param_path(svgd); +} + +static void spdc_apply_powerstroke_shape(const std::vector & points, FreehandBase *dc, SPItem *item) +{ + using namespace Inkscape::LivePathEffect; + + Effect::createAndApply(POWERSTROKE, dc->desktop->doc(), item); + Effect* lpe = SP_LPE_ITEM(item)->getCurrentLPE(); + static_cast(lpe)->offset_points.param_set_and_write_new_value(points); + + // write powerstroke parameters: + lpe->getRepr()->setAttribute("start_linecap_type", "zerowidth"); + lpe->getRepr()->setAttribute("end_linecap_type", "zerowidth"); + lpe->getRepr()->setAttribute("cusp_linecap_type", "round"); + lpe->getRepr()->setAttribute("sort_points", "true"); + lpe->getRepr()->setAttribute("interpolator_type", "CubicBezierJohan"); + lpe->getRepr()->setAttribute("interpolator_beta", "0.2"); +} + +static void spdc_check_for_and_apply_waiting_LPE(FreehandBase *dc, SPItem *item, SPCurve *curve) +{ + using namespace Inkscape::LivePathEffect; + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + + if (item && SP_IS_LPE_ITEM(item)) { + if (prefs->getInt(tool_name(dc) + "/freehand-mode", 0) == 1) { + Effect::createAndApply(SPIRO, dc->desktop->doc(), item); + } + + int shape = prefs->getInt(tool_name(dc) + "/shape", 0); + bool shape_applied = false; + SPCSSAttr *css_item = sp_css_attr_from_object(item, SP_STYLE_FLAG_ALWAYS); + const char *cstroke = sp_repr_css_property(css_item, "stroke", "none"); + +#define SHAPE_LENGTH 10 +#define SHAPE_HEIGHT 10 + + switch (shape) { + case 0: + // don't apply any shape + break; + case 1: + { + // "triangle in" + std::vector points(1); + points[0] = Geom::Point(0., SHAPE_HEIGHT/2); + spdc_apply_powerstroke_shape(points, dc, item); + + shape_applied = true; + break; + } + case 2: + { + // "triangle out" + guint curve_length = curve->get_segment_count(); + std::vector points(1); + points[0] = Geom::Point((double)curve_length, SHAPE_HEIGHT/2); + spdc_apply_powerstroke_shape(points, dc, item); + + shape_applied = true; + break; + } + case 3: + { + // "ellipse" + SPCurve *c = new SPCurve(); + const double C1 = 0.552; + c->moveto(0, SHAPE_HEIGHT/2); + c->curveto(0, (1 - C1) * SHAPE_HEIGHT/2, (1 - C1) * SHAPE_LENGTH/2, 0, SHAPE_LENGTH/2, 0); + c->curveto((1 + C1) * SHAPE_LENGTH/2, 0, SHAPE_LENGTH, (1 - C1) * SHAPE_HEIGHT/2, SHAPE_LENGTH, SHAPE_HEIGHT/2); + c->curveto(SHAPE_LENGTH, (1 + C1) * SHAPE_HEIGHT/2, (1 + C1) * SHAPE_LENGTH/2, SHAPE_HEIGHT, SHAPE_LENGTH/2, SHAPE_HEIGHT); + c->curveto((1 - C1) * SHAPE_LENGTH/2, SHAPE_HEIGHT, 0, (1 + C1) * SHAPE_HEIGHT/2, 0, SHAPE_HEIGHT/2); + c->closepath(); + spdc_paste_curve_as_freehand_shape(c, dc, item); + c->unref(); + shape_applied = true; + break; + } + case 4: + { + // take shape from clipboard; TODO: catch the case where clipboard is empty + Effect::createAndApply(PATTERN_ALONG_PATH, dc->desktop->doc(), item); + Effect* lpe = SP_LPE_ITEM(item)->getCurrentLPE(); + static_cast(lpe)->pattern.on_paste_button_click(); + + shape_applied = true; + break; + } + default: + break; + } + if (shape_applied) { + // apply original stroke color as fill and unset stroke; then return + SPCSSAttr *css = sp_repr_css_attr_new(); + + if (!strcmp(cstroke, "none")){ + sp_repr_css_set_property (css, "fill", "black"); + } else { + sp_repr_css_set_property (css, "fill", cstroke); + } + sp_repr_css_set_property (css, "stroke", "none"); + sp_desktop_apply_css_recursive(item, css, true); + sp_repr_css_attr_unref(css); + return; + } + + if (dc->waiting_LPE_type != INVALID_LPE) { + Effect::createAndApply(dc->waiting_LPE_type, dc->desktop->doc(), item); + dc->waiting_LPE_type = INVALID_LPE; + + if (SP_IS_LPETOOL_CONTEXT(dc)) { + // since a geometric LPE was applied, we switch back to "inactive" mode + lpetool_context_switch_mode(SP_LPETOOL_CONTEXT(dc), INVALID_LPE); + } + } + if (SP_IS_PEN_CONTEXT(dc)) { + sp_pen_context_set_polyline_mode(SP_PEN_CONTEXT(dc)); + } + } +} + +/* + * Selection handlers + */ + +static void spdc_selection_changed(Inkscape::Selection *sel, FreehandBase *dc) +{ + if (dc->attach) { + spdc_attach_selection(dc, sel); + } +} + +/* fixme: We have to ensure this is not delayed (Lauris) */ + +static void spdc_selection_modified(Inkscape::Selection *sel, guint /*flags*/, FreehandBase *dc) +{ + if (dc->attach) { + spdc_attach_selection(dc, sel); + } +} + +static void spdc_attach_selection(FreehandBase *dc, Inkscape::Selection */*sel*/) +{ + // We reset white and forget white/start/end anchors + spdc_reset_white(dc); + dc->sa = NULL; + dc->ea = NULL; + + SPItem *item = dc->selection ? dc->selection->singleItem() : NULL; + + if ( item && SP_IS_PATH(item) ) { + // Create new white data + // Item + dc->white_item = item; + + // Curve list + // We keep it in desktop coordinates to eliminate calculation errors + SPCurve *norm = SP_PATH(item)->get_curve_for_edit(); + norm->transform((dc->white_item)->i2dt_affine()); + g_return_if_fail( norm != NULL ); + dc->white_curves = g_slist_reverse(norm->split()); + norm->unref(); + + // Anchor list + for (GSList *l = dc->white_curves; l != NULL; l = l->next) { + SPCurve *c; + c = static_cast(l->data); + g_return_if_fail( c->get_segment_count() > 0 ); + if ( !c->is_closed() ) { + SPDrawAnchor *a; + a = sp_draw_anchor_new(dc, c, TRUE, *(c->first_point())); + if (a) + dc->white_anchors = g_slist_prepend(dc->white_anchors, a); + a = sp_draw_anchor_new(dc, c, FALSE, *(c->last_point())); + if (a) + dc->white_anchors = g_slist_prepend(dc->white_anchors, a); + } + } + // fixme: recalculate active anchor? + } +} + + +void spdc_endpoint_snap_rotation(ToolBase const *const ec, Geom::Point &p, Geom::Point const &o, + guint state) +{ + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + unsigned const snaps = abs(prefs->getInt("/options/rotationsnapsperpi/value", 12)); + + SnapManager &m = SP_EVENT_CONTEXT_DESKTOP(ec)->namedview->snap_manager; + m.setup(SP_EVENT_CONTEXT_DESKTOP(ec)); + + bool snap_enabled = m.snapprefs.getSnapEnabledGlobally(); + if (state & GDK_SHIFT_MASK) { + // SHIFT disables all snapping, except the angular snapping. After all, the user explicitly asked for angular + // snapping by pressing CTRL, otherwise we wouldn't have arrived here. But although we temporarily disable + // the snapping here, we must still call for a constrained snap in order to apply the constraints (i.e. round + // to the nearest angle increment) + m.snapprefs.setSnapEnabledGlobally(false); + } + + Inkscape::SnappedPoint dummy = m.constrainedAngularSnap(Inkscape::SnapCandidatePoint(p, Inkscape::SNAPSOURCE_NODE_HANDLE), boost::optional(), o, snaps); + p = dummy.getPoint(); + + if (state & GDK_SHIFT_MASK) { + m.snapprefs.setSnapEnabledGlobally(snap_enabled); // restore the original setting + } + + m.unSetup(); +} + + +void spdc_endpoint_snap_free(ToolBase const * const ec, Geom::Point& p, boost::optional &start_of_line, guint const /*state*/) +{ + SPDesktop *dt = SP_EVENT_CONTEXT_DESKTOP(ec); + SnapManager &m = dt->namedview->snap_manager; + Inkscape::Selection *selection = sp_desktop_selection (dt); + + // selection->singleItem() is the item that is currently being drawn. This item will not be snapped to (to avoid self-snapping) + // TODO: Allow snapping to the stationary parts of the item, and only ignore the last segment + + m.setup(dt, true, selection->singleItem()); + Inkscape::SnapCandidatePoint scp(p, Inkscape::SNAPSOURCE_NODE_HANDLE); + if (start_of_line) { + scp.addOrigin(*start_of_line); + } + + Inkscape::SnappedPoint sp = m.freeSnap(scp); + p = sp.getPoint(); + + m.unSetup(); +} + +static SPCurve *reverse_then_unref(SPCurve *orig) +{ + SPCurve *ret = orig->create_reverse(); + orig->unref(); + return ret; +} + +void spdc_concat_colors_and_flush(FreehandBase *dc, gboolean forceclosed) +{ + // Concat RBG + SPCurve *c = dc->green_curve; + + // Green + dc->green_curve = new SPCurve(); + while (dc->green_bpaths) { + sp_canvas_item_destroy(SP_CANVAS_ITEM(dc->green_bpaths->data)); + dc->green_bpaths = g_slist_remove(dc->green_bpaths, dc->green_bpaths->data); + } + + // Blue + c->append_continuous(dc->blue_curve, 0.0625); + dc->blue_curve->reset(); + sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(dc->blue_bpath), NULL); + + // Red + if (dc->red_curve_is_valid) { + c->append_continuous(dc->red_curve, 0.0625); + } + dc->red_curve->reset(); + sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(dc->red_bpath), NULL); + + if (c->is_empty()) { + c->unref(); + return; + } + + // Step A - test, whether we ended on green anchor + if ( forceclosed || ( dc->green_anchor && dc->green_anchor->active ) ) { + // We hit green anchor, closing Green-Blue-Red + SP_EVENT_CONTEXT_DESKTOP(dc)->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("Path is closed.")); + c->closepath_current(); + // Closed path, just flush + spdc_flush_white(dc, c); + c->unref(); + return; + } + + // Step B - both start and end anchored to same curve + if ( dc->sa && dc->ea + && ( dc->sa->curve == dc->ea->curve ) + && ( ( dc->sa != dc->ea ) + || dc->sa->curve->is_closed() ) ) + { + // We hit bot start and end of single curve, closing paths + SP_EVENT_CONTEXT_DESKTOP(dc)->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("Closing path.")); + if (dc->sa->start && !(dc->sa->curve->is_closed()) ) { + c = reverse_then_unref(c); + } + dc->sa->curve->append_continuous(c, 0.0625); + c->unref(); + dc->sa->curve->closepath_current(); + spdc_flush_white(dc, NULL); + return; + } + + // Step C - test start + if (dc->sa) { + SPCurve *s = dc->sa->curve; + dc->white_curves = g_slist_remove(dc->white_curves, s); + if (dc->sa->start) { + s = reverse_then_unref(s); + } + s->append_continuous(c, 0.0625); + c->unref(); + c = s; + } else /* Step D - test end */ if (dc->ea) { + SPCurve *e = dc->ea->curve; + dc->white_curves = g_slist_remove(dc->white_curves, e); + if (!dc->ea->start) { + e = reverse_then_unref(e); + } + c->append_continuous(e, 0.0625); + e->unref(); + } + + + spdc_flush_white(dc, c); + + c->unref(); +} + +static void spdc_flush_white(FreehandBase *dc, SPCurve *gc) +{ + SPCurve *c; + + if (dc->white_curves) { + g_assert(dc->white_item); + c = SPCurve::concat(dc->white_curves); + g_slist_free(dc->white_curves); + dc->white_curves = NULL; + if (gc) { + c->append(gc, FALSE); + } + } else if (gc) { + c = gc; + c->ref(); + } else { + return; + } + + // Now we have to go back to item coordinates at last + c->transform( dc->white_item + ? (dc->white_item)->dt2i_affine() + : SP_EVENT_CONTEXT_DESKTOP(dc)->dt2doc() ); + + SPDesktop *desktop = SP_EVENT_CONTEXT_DESKTOP(dc); + SPDocument *doc = sp_desktop_document(desktop); + Inkscape::XML::Document *xml_doc = doc->getReprDoc(); + + if ( c && !c->is_empty() ) { + // We actually have something to write + + bool has_lpe = false; + Inkscape::XML::Node *repr; + if (dc->white_item) { + repr = dc->white_item->getRepr(); + has_lpe = SP_LPE_ITEM(dc->white_item)->hasPathEffectRecursive(); + } else { + repr = xml_doc->createElement("svg:path"); + // Set style + sp_desktop_apply_style_tool(desktop, repr, tool_name(dc).data(), false); + } + + gchar *str = sp_svg_write_path( c->get_pathvector() ); + g_assert( str != NULL ); + if (has_lpe) + repr->setAttribute("inkscape:original-d", str); + else + repr->setAttribute("d", str); + g_free(str); + + if (!dc->white_item) { + // Attach repr + SPItem *item = SP_ITEM(desktop->currentLayer()->appendChildRepr(repr)); + + // we finished the path; now apply any waiting LPEs or freehand shapes + spdc_check_for_and_apply_waiting_LPE(dc, item, c); + + dc->selection->set(repr); + Inkscape::GC::release(repr); + item->transform = SP_ITEM(desktop->currentLayer())->i2doc_affine().inverse(); + item->doWriteTransform(item->getRepr(), item->transform, NULL, true); + item->updateRepr(); + } + + DocumentUndo::done(doc, SP_IS_PEN_CONTEXT(dc)? SP_VERB_CONTEXT_PEN : SP_VERB_CONTEXT_PENCIL, + _("Draw path")); + + // When quickly drawing several subpaths with Shift, the next subpath may be finished and + // flushed before the selection_modified signal is fired by the previous change, which + // results in the tool losing all of the selected path's curve except that last subpath. To + // fix this, we force the selection_modified callback now, to make sure the tool's curve is + // in sync immediately. + spdc_selection_modified(sp_desktop_selection(desktop), 0, dc); + } + + c->unref(); + + // Flush pending updates + doc->ensureUpToDate(); +} + +SPDrawAnchor *spdc_test_inside(FreehandBase *dc, Geom::Point p) +{ + SPDrawAnchor *active = NULL; + + // Test green anchor + if (dc->green_anchor) { + active = sp_draw_anchor_test(dc->green_anchor, p, TRUE); + } + + for (GSList *l = dc->white_anchors; l != NULL; l = l->next) { + SPDrawAnchor *na = sp_draw_anchor_test(static_cast(l->data), p, !active); + if ( !active && na ) { + active = na; + } + } + + return active; +} + +static void spdc_reset_white(FreehandBase *dc) +{ + if (dc->white_item) { + // We do not hold refcount + dc->white_item = NULL; + } + while (dc->white_curves) { + reinterpret_cast(dc->white_curves->data)->unref(); + dc->white_curves = g_slist_remove(dc->white_curves, dc->white_curves->data); + } + while (dc->white_anchors) { + sp_draw_anchor_destroy(static_cast(dc->white_anchors->data)); + dc->white_anchors = g_slist_remove(dc->white_anchors, dc->white_anchors->data); + } +} + +static void spdc_free_colors(FreehandBase *dc) +{ + // Red + if (dc->red_bpath) { + sp_canvas_item_destroy(SP_CANVAS_ITEM(dc->red_bpath)); + dc->red_bpath = NULL; + } + if (dc->red_curve) { + dc->red_curve = dc->red_curve->unref(); + } + + // Blue + if (dc->blue_bpath) { + sp_canvas_item_destroy(SP_CANVAS_ITEM(dc->blue_bpath)); + dc->blue_bpath = NULL; + } + if (dc->blue_curve) { + dc->blue_curve = dc->blue_curve->unref(); + } + + // Green + while (dc->green_bpaths) { + sp_canvas_item_destroy(SP_CANVAS_ITEM(dc->green_bpaths->data)); + dc->green_bpaths = g_slist_remove(dc->green_bpaths, dc->green_bpaths->data); + } + if (dc->green_curve) { + dc->green_curve = dc->green_curve->unref(); + } + if (dc->green_anchor) { + dc->green_anchor = sp_draw_anchor_destroy(dc->green_anchor); + } + + // White + if (dc->white_item) { + // We do not hold refcount + dc->white_item = NULL; + } + while (dc->white_curves) { + reinterpret_cast(dc->white_curves->data)->unref(); + dc->white_curves = g_slist_remove(dc->white_curves, dc->white_curves->data); + } + while (dc->white_anchors) { + sp_draw_anchor_destroy(static_cast(dc->white_anchors->data)); + dc->white_anchors = g_slist_remove(dc->white_anchors, dc->white_anchors->data); + } +} + +void spdc_create_single_dot(ToolBase *ec, Geom::Point const &pt, char const *tool, guint event_state) { + g_return_if_fail(!strcmp(tool, "/tools/freehand/pen") || !strcmp(tool, "/tools/freehand/pencil")); + Glib::ustring tool_path = tool; + + SPDesktop *desktop = SP_EVENT_CONTEXT_DESKTOP(ec); + Inkscape::XML::Document *xml_doc = desktop->doc()->getReprDoc(); + Inkscape::XML::Node *repr = xml_doc->createElement("svg:path"); + repr->setAttribute("sodipodi:type", "arc"); + SPItem *item = SP_ITEM(desktop->currentLayer()->appendChildRepr(repr)); + Inkscape::GC::release(repr); + + // apply the tool's current style + sp_desktop_apply_style_tool(desktop, repr, tool, false); + + // find out stroke width (TODO: is there an easier way??) + double stroke_width = 3.0; + gchar const *style_str = NULL; + style_str = repr->attribute("style"); + if (style_str) { + SPStyle *style = sp_style_new(SP_ACTIVE_DOCUMENT); + sp_style_merge_from_style_string(style, style_str); + stroke_width = style->stroke_width.computed; + style->stroke_width.computed = 0; + sp_style_unref(style); + } + + // unset stroke and set fill color to former stroke color + gchar * str; + str = g_strdup_printf("fill:#%06x;stroke:none;", sp_desktop_get_color_tool(desktop, tool, false) >> 8); + repr->setAttribute("style", str); + g_free(str); + + // put the circle where the mouse click occurred and set the diameter to the + // current stroke width, multiplied by the amount specified in the preferences + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + + Geom::Affine const i2d (item->i2dt_affine ()); + Geom::Point pp = pt * i2d.inverse(); + double rad = 0.5 * prefs->getDouble(tool_path + "/dot-size", 3.0); + if (event_state & GDK_MOD1_MASK) { + // TODO: We vary the dot size between 0.5*rad and 1.5*rad, where rad is the dot size + // as specified in prefs. Very simple, but it might be sufficient in practice. If not, + // we need to devise something more sophisticated. + double s = g_random_double_range(-0.5, 0.5); + rad *= (1 + s); + } + if (event_state & GDK_SHIFT_MASK) { + // double the point size + rad *= 2; + } + + sp_repr_set_svg_double (repr, "sodipodi:cx", pp[Geom::X]); + sp_repr_set_svg_double (repr, "sodipodi:cy", pp[Geom::Y]); + sp_repr_set_svg_double (repr, "sodipodi:rx", rad * stroke_width); + sp_repr_set_svg_double (repr, "sodipodi:ry", rad * stroke_width); + item->updateRepr(); + + sp_desktop_selection(desktop)->set(item); + + desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("Creating single dot")); + DocumentUndo::done(sp_desktop_document(desktop), SP_VERB_NONE, _("Create single dot")); +} + +} +} +} + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/ui/tools/freehand-base.h b/src/ui/tools/freehand-base.h new file mode 100644 index 000000000..7e53684e3 --- /dev/null +++ b/src/ui/tools/freehand-base.h @@ -0,0 +1,144 @@ +#ifndef SEEN_SP_DRAW_CONTEXT_H +#define SEEN_SP_DRAW_CONTEXT_H + +/* + * Generic drawing context + * + * Author: + * Lauris Kaplinski + * + * Copyright (C) 2000 Lauris Kaplinski + * Copyright (C) 2000-2001 Ximian, Inc. + * Copyright (C) 2002 Lauris Kaplinski + * + * Released under GNU GPL + */ + +#include +#include +#include <2geom/point.h> +#include "ui/tools/tool-base.h" +#include "live_effects/effect.h" + +/* Freehand context */ + +#define SP_DRAW_CONTEXT(obj) (dynamic_cast((Inkscape::UI::Tools::ToolBase*)obj)) +#define SP_IS_DRAW_CONTEXT(obj) (dynamic_cast((const Inkscape::UI::Tools::ToolBase*)obj) != NULL) + +struct SPDrawAnchor; +namespace Inkscape +{ + class Selection; +} + +namespace Inkscape { +namespace UI { +namespace Tools { + +class FreehandBase : public ToolBase { +public: + FreehandBase(); + virtual ~FreehandBase(); + + Inkscape::Selection *selection; + SPCanvasItem *grab; + + guint attach : 1; + + guint32 red_color; + guint32 blue_color; + guint32 green_color; + + // Red + SPCanvasItem *red_bpath; + SPCurve *red_curve; + + // Blue + SPCanvasItem *blue_bpath; + SPCurve *blue_curve; + + // Green + GSList *green_bpaths; + SPCurve *green_curve; + SPDrawAnchor *green_anchor; + gboolean green_closed; // a flag meaning we hit the green anchor, so close the path on itself + + // White + SPItem *white_item; + GSList *white_curves; + GSList *white_anchors; + + // Start anchor + SPDrawAnchor *sa; + + // End anchor + SPDrawAnchor *ea; + + /* type of the LPE that is to be applied automatically to a finished path (if any) */ + Inkscape::LivePathEffect::EffectType waiting_LPE_type; + + sigc::connection sel_changed_connection; + sigc::connection sel_modified_connection; + + bool red_curve_is_valid; + + bool anchor_statusbar; + +protected: + virtual void setup(); + virtual void finish(); + virtual void set(const Inkscape::Preferences::Entry& val); + virtual bool root_handler(GdkEvent* event); +}; + +/** + * Returns FIRST active anchor (the activated one). + */ +SPDrawAnchor *spdc_test_inside(FreehandBase *dc, Geom::Point p); + +/** + * Concats red, blue and green. + * If any anchors are defined, process these, optionally removing curves from white list + * Invoke _flush_white to write result back to object. + */ +void spdc_concat_colors_and_flush(FreehandBase *dc, gboolean forceclosed); + +/** + * Snaps node or handle to PI/rotationsnapsperpi degree increments. + * + * @param dc draw context. + * @param p cursor point (to be changed by snapping). + * @param o origin point. + * @param state keyboard state to check if ctrl or shift was pressed. + */ +void spdc_endpoint_snap_rotation(ToolBase const *const ec, Geom::Point &p, Geom::Point const &o, guint state); + +void spdc_endpoint_snap_free(ToolBase const *ec, Geom::Point &p, boost::optional &start_of_line, guint state); + +/** + * If we have an item and a waiting LPE, apply the effect to the item + * (spiro spline mode is treated separately). + */ +void spdc_check_for_and_apply_waiting_LPE(FreehandBase *dc, SPItem *item); + +/** + * Create a single dot represented by a circle. + */ +void spdc_create_single_dot(ToolBase *ec, Geom::Point const &pt, char const *tool, guint event_state); + +} +} +} + +#endif // SEEN_SP_DRAW_CONTEXT_H + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/ui/tools/gradient-tool.cpp b/src/ui/tools/gradient-tool.cpp new file mode 100644 index 000000000..e4ab7b424 --- /dev/null +++ b/src/ui/tools/gradient-tool.cpp @@ -0,0 +1,977 @@ +/* + * Gradient drawing and editing tool + * + * Authors: + * bulia byak + * Johan Engelen + * Abhishek Sharma + * + * Copyright (C) 2007 Johan Engelen + * Copyright (C) 2005 Authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + + +#include + +#include "macros.h" +#include "document.h" +#include "selection.h" +#include "desktop.h" +#include "desktop-handles.h" +#include "message-context.h" +#include "message-stack.h" +#include "pixmaps/cursor-gradient.xpm" +#include "pixmaps/cursor-gradient-add.xpm" +#include "ui/tools/gradient-tool.h" +#include "gradient-chemistry.h" +#include +#include "preferences.h" +#include "gradient-drag.h" +#include "gradient-chemistry.h" +#include "xml/repr.h" +#include "sp-item.h" +#include "display/sp-ctrlline.h" +#include "sp-linear-gradient.h" +#include "sp-radial-gradient.h" +#include "sp-stop.h" +#include "svg/css-ostringstream.h" +#include "svg/svg-color.h" +#include "snap.h" +#include "sp-namedview.h" +#include "rubberband.h" +#include "document-undo.h" +#include "verbs.h" +#include "selection-chemistry.h" + +using Inkscape::DocumentUndo; + +#include "tool-factory.h" + +namespace Inkscape { +namespace UI { +namespace Tools { + +static void sp_gradient_drag(GradientTool &rc, Geom::Point const pt, guint state, guint32 etime); + +namespace { + ToolBase* createGradientContext() { + return new GradientTool(); + } + + bool gradientContextRegistered = ToolFactory::instance().registerObject("/tools/gradient", createGradientContext); +} + +const std::string& GradientTool::getPrefsPath() { + return GradientTool::prefsPath; +} + +const std::string GradientTool::prefsPath = "/tools/gradient"; + + +GradientTool::GradientTool() : ToolBase() { + this->node_added = false; + this->subselcon = 0; + this->selcon = 0; + + this->cursor_addnode = false; + this->cursor_shape = cursor_gradient_xpm; + this->hot_x = 4; + this->hot_y = 4; + this->xp = 0; + this->yp = 0; + this->tolerance = 6; + this->within_tolerance = false; + this->item_to_select = NULL; +} + +GradientTool::~GradientTool() { + this->enableGrDrag(false); + + this->selcon->disconnect(); + delete this->selcon; + + this->subselcon->disconnect(); + delete this->subselcon; +} + +const gchar *gr_handle_descr [] = { + N_("Linear gradient start"), //POINT_LG_BEGIN + N_("Linear gradient end"), + N_("Linear gradient mid stop"), + N_("Radial gradient center"), + N_("Radial gradient radius"), + N_("Radial gradient radius"), + N_("Radial gradient focus"), // POINT_RG_FOCUS + N_("Radial gradient mid stop"), + N_("Radial gradient mid stop") +}; + +void GradientTool::selection_changed(Inkscape::Selection*) { + GradientTool *rc = (GradientTool *) this; + + GrDrag *drag = rc->_grdrag; + Inkscape::Selection *selection = sp_desktop_selection(SP_EVENT_CONTEXT(rc)->desktop); + if (selection == NULL) { + return; + } + guint n_obj = g_slist_length((GSList *) selection->itemList()); + + if (!drag->isNonEmpty() || selection->isEmpty()) + return; + guint n_tot = drag->numDraggers(); + guint n_sel = drag->numSelected(); + + //The use of ngettext in the following code is intentional even if the English singular form would never be used + if (n_sel == 1) { + if (drag->singleSelectedDraggerNumDraggables() == 1) { + gchar * message = g_strconcat( + //TRANSLATORS: %s will be substituted with the point name (see previous messages); This is part of a compound message + _("%s selected"), + //TRANSLATORS: Mind the space in front. This is part of a compound message + ngettext(" out of %d gradient handle"," out of %d gradient handles",n_tot), + ngettext(" on %d selected object"," on %d selected objects",n_obj),NULL); + rc->message_context->setF(Inkscape::NORMAL_MESSAGE, + message,_(gr_handle_descr[drag->singleSelectedDraggerSingleDraggableType()]), n_tot, n_obj); + } else { + gchar * message = g_strconcat( + //TRANSLATORS: This is a part of a compound message (out of two more indicating: grandint handle count & object count) + ngettext("One handle merging %d stop (drag with Shift to separate) selected", + "One handle merging %d stops (drag with Shift to separate) selected",drag->singleSelectedDraggerNumDraggables()), + ngettext(" out of %d gradient handle"," out of %d gradient handles",n_tot), + ngettext(" on %d selected object"," on %d selected objects",n_obj),NULL); + rc->message_context->setF(Inkscape::NORMAL_MESSAGE,message,drag->singleSelectedDraggerNumDraggables(), n_tot, n_obj); + } + } else if (n_sel > 1) { + //TRANSLATORS: The plural refers to number of selected gradient handles. This is part of a compound message (part two indicates selected object count) + gchar * message = g_strconcat(ngettext("%d gradient handle selected out of %d","%d gradient handles selected out of %d",n_sel), + //TRANSLATORS: Mind the space in front. (Refers to gradient handles selected). This is part of a compound message + ngettext(" on %d selected object"," on %d selected objects",n_obj),NULL); + rc->message_context->setF(Inkscape::NORMAL_MESSAGE,message, n_sel, n_tot, n_obj); + } else if (n_sel == 0) { + rc->message_context->setF(Inkscape::NORMAL_MESSAGE, + //TRANSLATORS: The plural refers to number of selected objects + ngettext("No gradient handles selected out of %d on %d selected object", + "No gradient handles selected out of %d on %d selected objects",n_obj), n_tot, n_obj); + } +} + +void GradientTool::setup() { + ToolBase::setup(); + + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + + if (prefs->getBool("/tools/gradient/selcue", true)) { + this->enableSelectionCue(); + } + + this->enableGrDrag(); + Inkscape::Selection *selection = sp_desktop_selection(this->desktop); + + this->selcon = new sigc::connection(selection->connectChanged( + sigc::mem_fun(this, &GradientTool::selection_changed) + )); + + this->subselcon = new sigc::connection(this->desktop->connectToolSubselectionChanged( + sigc::hide(sigc::bind( + sigc::mem_fun(this, &GradientTool::selection_changed), + (Inkscape::Selection*)NULL + )) + )); + + this->selection_changed(selection); +} + +void +sp_gradient_context_select_next (ToolBase *event_context) +{ + GrDrag *drag = event_context->_grdrag; + g_assert (drag); + + GrDragger *d = drag->select_next(); + + event_context->desktop->scroll_to_point(d->point, 1.0); +} + +void +sp_gradient_context_select_prev (ToolBase *event_context) +{ + GrDrag *drag = event_context->_grdrag; + g_assert (drag); + + GrDragger *d = drag->select_prev(); + + event_context->desktop->scroll_to_point(d->point, 1.0); +} + +static bool +sp_gradient_context_is_over_line (GradientTool *rc, SPItem *item, Geom::Point event_p) +{ + SPDesktop *desktop = SP_EVENT_CONTEXT (rc)->desktop; + + //Translate mouse point into proper coord system + rc->mousepoint_doc = desktop->w2d(event_p); + + SPCtrlLine* line = SP_CTRLLINE(item); + + Geom::LineSegment ls(line->s, line->e); + Geom::Point nearest = ls.pointAt(ls.nearestPoint(rc->mousepoint_doc)); + double dist_screen = Geom::L2 (rc->mousepoint_doc - nearest) * desktop->current_zoom(); + + double tolerance = (double) SP_EVENT_CONTEXT(rc)->tolerance; + + bool close = (dist_screen < tolerance); + + return close; +} + +static std::vector +sp_gradient_context_get_stop_intervals (GrDrag *drag, GSList **these_stops, GSList **next_stops) +{ + std::vector coords; + + // for all selected draggers + for (GList *i = drag->selected; i != NULL; i = i->next) { + GrDragger *dragger = (GrDragger *) i->data; + // remember the coord of the dragger to reselect it later + coords.push_back(dragger->point); + // for all draggables of dragger + for (GSList const* j = dragger->draggables; j != NULL; j = j->next) { + GrDraggable *d = (GrDraggable *) j->data; + + // find the gradient + SPGradient *gradient = getGradient(d->item, d->fill_or_stroke); + SPGradient *vector = sp_gradient_get_forked_vector_if_necessary (gradient, false); + + // these draggable types cannot have a next draggabe to insert a stop between them + if (d->point_type == POINT_LG_END || + d->point_type == POINT_RG_FOCUS || + d->point_type == POINT_RG_R1 || + d->point_type == POINT_RG_R2) { + continue; + } + + // from draggables to stops + SPStop *this_stop = sp_get_stop_i (vector, d->point_i); + SPStop *next_stop = this_stop->getNextStop(); + SPStop *last_stop = sp_last_stop (vector); + + Inkscape::PaintTarget fs = d->fill_or_stroke; + SPItem *item = d->item; + gint type = d->point_type; + gint p_i = d->point_i; + + // if there's a next stop, + if (next_stop) { + GrDragger *dnext = NULL; + // find its dragger + // (complex because it may have different types, and because in radial, + // more than one dragger may correspond to a stop, so we must distinguish) + if (type == POINT_LG_BEGIN || type == POINT_LG_MID) { + if (next_stop == last_stop) { + dnext = drag->getDraggerFor(item, POINT_LG_END, p_i+1, fs); + } else { + dnext = drag->getDraggerFor(item, POINT_LG_MID, p_i+1, fs); + } + } else { // radial + if (type == POINT_RG_CENTER || type == POINT_RG_MID1) { + if (next_stop == last_stop) { + dnext = drag->getDraggerFor(item, POINT_RG_R1, p_i+1, fs); + } else { + dnext = drag->getDraggerFor(item, POINT_RG_MID1, p_i+1, fs); + } + } + if ((type == POINT_RG_MID2) || + (type == POINT_RG_CENTER && dnext && !dnext->isSelected())) { + if (next_stop == last_stop) { + dnext = drag->getDraggerFor(item, POINT_RG_R2, p_i+1, fs); + } else { + dnext = drag->getDraggerFor(item, POINT_RG_MID2, p_i+1, fs); + } + } + } + + // if both adjacent draggers selected, + if (!g_slist_find(*these_stops, this_stop) && dnext && dnext->isSelected()) { + + // remember the coords of the future dragger to select it + coords.push_back(0.5*(dragger->point + dnext->point)); + + // do not insert a stop now, it will confuse the loop; + // just remember the stops + *these_stops = g_slist_prepend (*these_stops, this_stop); + *next_stops = g_slist_prepend (*next_stops, next_stop); + } + } + } + } + return coords; +} + +void +sp_gradient_context_add_stops_between_selected_stops (GradientTool *rc) +{ + SPDocument *doc = NULL; + GrDrag *drag = rc->_grdrag; + + GSList *these_stops = NULL; + GSList *next_stops = NULL; + + std::vector coords = sp_gradient_context_get_stop_intervals (drag, &these_stops, &next_stops); + + if (g_slist_length(these_stops) == 0 && drag->numSelected() == 1) { + // if a single stop is selected, add between that stop and the next one + GrDragger *dragger = (GrDragger *) drag->selected->data; + for (GSList const* j = dragger->draggables; j != NULL; j = j->next) { + GrDraggable *d = (GrDraggable *) j->data; + if (d->point_type == POINT_RG_FOCUS) { + /* + * There are 2 draggables at the center (start) of a radial gradient + * To avoid creating 2 seperate stops, ignore this draggable point type + */ + continue; + } + SPGradient *gradient = getGradient(d->item, d->fill_or_stroke); + SPGradient *vector = sp_gradient_get_forked_vector_if_necessary (gradient, false); + SPStop *this_stop = sp_get_stop_i (vector, d->point_i); + SPStop *next_stop = this_stop->getNextStop(); + if (this_stop && next_stop) { + these_stops = g_slist_prepend (these_stops, this_stop); + next_stops = g_slist_prepend (next_stops, next_stop); + } + } + } + + // now actually create the new stops + GSList *i = these_stops; + GSList *j = next_stops; + GSList *new_stops = NULL; + + for (; i != NULL && j != NULL; i = i->next, j = j->next) { + SPStop *this_stop = (SPStop *) i->data; + SPStop *next_stop = (SPStop *) j->data; + gfloat offset = 0.5*(this_stop->offset + next_stop->offset); + SPObject *parent = this_stop->parent; + if (SP_IS_GRADIENT (parent)) { + doc = parent->document; + SPStop *new_stop = sp_vector_add_stop (SP_GRADIENT (parent), this_stop, next_stop, offset); + new_stops = g_slist_prepend (new_stops, new_stop); + SP_GRADIENT(parent)->ensureVector(); + } + } + + if (g_slist_length(these_stops) > 0 && doc) { + DocumentUndo::done(doc, SP_VERB_CONTEXT_GRADIENT, _("Add gradient stop")); + drag->updateDraggers(); + // so that it does not automatically update draggers in idle loop, as this would deselect + drag->local_change = true; + + // select the newly created stops + for (GSList *s = new_stops; s != NULL; s = s->next) { + drag->selectByStop((SPStop *)s->data); + } + + } + + g_slist_free (these_stops); + g_slist_free (next_stops); + g_slist_free (new_stops); +} + +static double sqr(double x) {return x*x;} + +static void +sp_gradient_simplify(GradientTool *rc, double tolerance) +{ + SPDocument *doc = NULL; + GrDrag *drag = rc->_grdrag; + + GSList *these_stops = NULL; + GSList *next_stops = NULL; + + std::vector coords = sp_gradient_context_get_stop_intervals (drag, &these_stops, &next_stops); + + GSList *todel = NULL; + + GSList *i = these_stops; + GSList *j = next_stops; + for (; i != NULL && j != NULL; i = i->next, j = j->next) { + SPStop *stop0 = (SPStop *) i->data; + SPStop *stop1 = (SPStop *) j->data; + + gint i1 = g_slist_index(these_stops, stop1); + if (i1 != -1) { + GSList *next_next = g_slist_nth (next_stops, i1); + if (next_next) { + SPStop *stop2 = (SPStop *) next_next->data; + + if (g_slist_find(todel, stop0) || g_slist_find(todel, stop2)) + continue; + + guint32 const c0 = stop0->get_rgba32(); + guint32 const c2 = stop2->get_rgba32(); + guint32 const c1r = stop1->get_rgba32(); + guint32 c1 = average_color (c0, c2, + (stop1->offset - stop0->offset) / (stop2->offset - stop0->offset)); + + double diff = + sqr(SP_RGBA32_R_F(c1) - SP_RGBA32_R_F(c1r)) + + sqr(SP_RGBA32_G_F(c1) - SP_RGBA32_G_F(c1r)) + + sqr(SP_RGBA32_B_F(c1) - SP_RGBA32_B_F(c1r)) + + sqr(SP_RGBA32_A_F(c1) - SP_RGBA32_A_F(c1r)); + + if (diff < tolerance) + todel = g_slist_prepend (todel, stop1); + } + } + } + + for (i = todel; i != NULL; i = i->next) { + SPStop *stop = (SPStop*) i->data; + doc = stop->document; + Inkscape::XML::Node * parent = stop->getRepr()->parent(); + parent->removeChild( stop->getRepr() ); + } + + if (g_slist_length(todel) > 0) { + DocumentUndo::done(doc, SP_VERB_CONTEXT_GRADIENT, _("Simplify gradient")); + drag->local_change = true; + drag->updateDraggers(); + drag->selectByCoords(coords); + } + + g_slist_free (todel); + g_slist_free (these_stops); + g_slist_free (next_stops); +} + + +static void +sp_gradient_context_add_stop_near_point (GradientTool *rc, SPItem *item, Geom::Point mouse_p, guint32 /*etime*/) +{ + // item is the selected item. mouse_p the location in doc coordinates of where to add the stop + + ToolBase *ec = SP_EVENT_CONTEXT(rc); + SPDesktop *desktop = SP_EVENT_CONTEXT (rc)->desktop; + + double tolerance = (double) ec->tolerance; + + SPStop *newstop = ec->get_drag()->addStopNearPoint (item, mouse_p, tolerance/desktop->current_zoom()); + + DocumentUndo::done(sp_desktop_document (desktop), SP_VERB_CONTEXT_GRADIENT, + _("Add gradient stop")); + + ec->get_drag()->updateDraggers(); + ec->get_drag()->local_change = true; + ec->get_drag()->selectByStop(newstop); +} + +bool GradientTool::root_handler(GdkEvent* event) { + static bool dragging; + + Inkscape::Selection *selection = sp_desktop_selection (desktop); + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + + this->tolerance = prefs->getIntLimited("/options/dragtolerance/value", 0, 0, 100); + double const nudge = prefs->getDoubleLimited("/options/nudgedistance/value", 2, 0, 1000, "px"); // in px + + GrDrag *drag = this->_grdrag; + g_assert (drag); + + gint ret = FALSE; + + switch (event->type) { + case GDK_2BUTTON_PRESS: + if ( event->button.button == 1 ) { + bool over_line = false; + SPCtrlLine *line = NULL; + + if (drag->lines) { + for (GSList *l = drag->lines; (l != NULL) && (!over_line); l = l->next) { + line = (SPCtrlLine*) l->data; + over_line |= sp_gradient_context_is_over_line (this, (SPItem*) line, Geom::Point(event->motion.x, event->motion.y)); + } + } + + if (over_line) { + // we take the first item in selection, because with doubleclick, the first click + // always resets selection to the single object under cursor + sp_gradient_context_add_stop_near_point(this, SP_ITEM(selection->itemList()->data), this->mousepoint_doc, event->button.time); + } else { + for (GSList const* i = selection->itemList(); i != NULL; i = i->next) { + SPItem *item = SP_ITEM(i->data); + SPGradientType new_type = (SPGradientType) prefs->getInt("/tools/gradient/newgradient", SP_GRADIENT_TYPE_LINEAR); + Inkscape::PaintTarget fsmode = (prefs->getInt("/tools/gradient/newfillorstroke", 1) != 0) ? Inkscape::FOR_FILL : Inkscape::FOR_STROKE; + + SPGradient *vector = sp_gradient_vector_for_object(sp_desktop_document(desktop), desktop, item, fsmode); + + SPGradient *priv = sp_item_set_gradient(item, vector, new_type, fsmode); + sp_gradient_reset_to_userspace(priv, item); + } + + DocumentUndo::done(sp_desktop_document (desktop), SP_VERB_CONTEXT_GRADIENT, + _("Create default gradient")); + } + ret = TRUE; + } + break; + + case GDK_BUTTON_PRESS: + if ( event->button.button == 1 && !this->space_panning ) { + Geom::Point button_w(event->button.x, event->button.y); + + // save drag origin + this->xp = (gint) button_w[Geom::X]; + this->yp = (gint) button_w[Geom::Y]; + this->within_tolerance = true; + + dragging = true; + + Geom::Point button_dt = desktop->w2d(button_w); + if (event->button.state & GDK_SHIFT_MASK) { + Inkscape::Rubberband::get(desktop)->start(desktop, button_dt); + } else { + // remember clicked item, disregarding groups, honoring Alt; do nothing with Crtl to + // enable Ctrl+doubleclick of exactly the selected item(s) + if (!(event->button.state & GDK_CONTROL_MASK)) { + this->item_to_select = sp_event_context_find_item (desktop, button_w, event->button.state & GDK_MOD1_MASK, TRUE); + } + + if (!selection->isEmpty()) { + SnapManager &m = desktop->namedview->snap_manager; + m.setup(desktop); + m.freeSnapReturnByRef(button_dt, Inkscape::SNAPSOURCE_NODE_HANDLE); + m.unSetup(); + } + + this->origin = button_dt; + } + + ret = TRUE; + } + break; + + case GDK_MOTION_NOTIFY: + if (dragging && ( event->motion.state & GDK_BUTTON1_MASK ) && !this->space_panning) { + if ( this->within_tolerance + && ( abs( (gint) event->motion.x - this->xp ) < this->tolerance ) + && ( abs( (gint) event->motion.y - this->yp ) < this->tolerance ) ) { + break; // do not drag if we're within tolerance from origin + } + // Once the user has moved farther than tolerance from the original location + // (indicating they intend to draw, not click), then always process the + // motion notify coordinates as given (no snapping back to origin) + this->within_tolerance = false; + + Geom::Point const motion_w(event->motion.x, + event->motion.y); + Geom::Point const motion_dt = this->desktop->w2d(motion_w); + + if (Inkscape::Rubberband::get(desktop)->is_started()) { + Inkscape::Rubberband::get(desktop)->move(motion_dt); + this->defaultMessageContext()->set(Inkscape::NORMAL_MESSAGE, _("Draw around handles to select them")); + } else { + sp_gradient_drag(*this, motion_dt, event->motion.state, event->motion.time); + } + + gobble_motion_events(GDK_BUTTON1_MASK); + + ret = TRUE; + } else { + if (!drag->mouseOver() && !selection->isEmpty()) { + SnapManager &m = desktop->namedview->snap_manager; + m.setup(desktop); + + Geom::Point const motion_w(event->motion.x, event->motion.y); + Geom::Point const motion_dt = this->desktop->w2d(motion_w); + + m.preSnap(Inkscape::SnapCandidatePoint(motion_dt, Inkscape::SNAPSOURCE_OTHER_HANDLE)); + m.unSetup(); + } + + bool over_line = false; + + if (drag->lines) { + for (GSList *l = drag->lines; l != NULL; l = l->next) { + over_line |= sp_gradient_context_is_over_line (this, (SPItem*) l->data, Geom::Point(event->motion.x, event->motion.y)); + } + } + + if (this->cursor_addnode && !over_line) { + this->cursor_shape = cursor_gradient_xpm; + this->sp_event_context_update_cursor(); + this->cursor_addnode = false; + } else if (!this->cursor_addnode && over_line) { + this->cursor_shape = cursor_gradient_add_xpm; + this->sp_event_context_update_cursor(); + this->cursor_addnode = true; + } + } + break; + + case GDK_BUTTON_RELEASE: + this->xp = this->yp = 0; + + if ( event->button.button == 1 && !this->space_panning ) { + bool over_line = false; + SPCtrlLine *line = NULL; + + if (drag->lines) { + for (GSList *l = drag->lines; (l != NULL) && (!over_line); l = l->next) { + line = (SPCtrlLine*) l->data; + over_line = sp_gradient_context_is_over_line (this, (SPItem*) line, Geom::Point(event->motion.x, event->motion.y)); + if (over_line) + break; + } + } + + if ( (event->button.state & GDK_CONTROL_MASK) && (event->button.state & GDK_MOD1_MASK ) ) { + if (over_line && line) { + sp_gradient_context_add_stop_near_point(this, line->item, this->mousepoint_doc, 0); + ret = TRUE; + } + } else { + dragging = false; + + // unless clicked with Ctrl (to enable Ctrl+doubleclick). + if (event->button.state & GDK_CONTROL_MASK) { + ret = TRUE; + break; + } + + if (!this->within_tolerance) { + // we've been dragging, either do nothing (grdrag handles that), + // or rubberband-select if we have rubberband + Inkscape::Rubberband *r = Inkscape::Rubberband::get(desktop); + + if (r->is_started() && !this->within_tolerance) { + // this was a rubberband drag + if (r->getMode() == RUBBERBAND_MODE_RECT) { + Geom::OptRect const b = r->getRectangle(); + drag->selectRect(*b); + } + } + } else if (this->item_to_select) { + if (over_line && line) { + // Clicked on an existing gradient line, dont change selection. This stops + // possible change in selection during a double click with overlapping objects + } else { + // no dragging, select clicked item if any + if (event->button.state & GDK_SHIFT_MASK) { + selection->toggle(this->item_to_select); + } else { + drag->deselectAll(); + selection->set(this->item_to_select); + } + } + } else { + // click in an empty space; do the same as Esc + if (drag->selected) { + drag->deselectAll(); + } else { + selection->clear(); + } + } + + this->item_to_select = NULL; + ret = TRUE; + } + + Inkscape::Rubberband::get(desktop)->stop(); + } + break; + + case GDK_KEY_PRESS: + switch (get_group0_keyval (&event->key)) { + case GDK_KEY_Alt_L: + case GDK_KEY_Alt_R: + case GDK_KEY_Control_L: + case GDK_KEY_Control_R: + case GDK_KEY_Shift_L: + case GDK_KEY_Shift_R: + case GDK_KEY_Meta_L: // Meta is when you press Shift+Alt (at least on my machine) + case GDK_KEY_Meta_R: + sp_event_show_modifier_tip (this->defaultMessageContext(), event, + _("Ctrl: snap gradient angle"), + _("Shift: draw gradient around the starting point"), + NULL); + break; + + case GDK_KEY_x: + case GDK_KEY_X: + if (MOD__ALT_ONLY(event)) { + desktop->setToolboxFocusTo ("altx-grad"); + ret = TRUE; + } + break; + + case GDK_KEY_A: + case GDK_KEY_a: + if (MOD__CTRL_ONLY(event) && drag->isNonEmpty()) { + drag->selectAll(); + ret = TRUE; + } + break; + + case GDK_KEY_L: + case GDK_KEY_l: + if (MOD__CTRL_ONLY(event) && drag->isNonEmpty() && drag->hasSelection()) { + sp_gradient_simplify(this, 1e-4); + ret = TRUE; + } + break; + + case GDK_KEY_Escape: + if (drag->selected) { + drag->deselectAll(); + } else { + Inkscape::SelectionHelper::selectNone(desktop); + } + ret = TRUE; + //TODO: make dragging escapable by Esc + break; + + case GDK_KEY_Left: // move handle left + case GDK_KEY_KP_Left: + case GDK_KEY_KP_4: + if (!MOD__CTRL(event)) { // not ctrl + gint mul = 1 + gobble_key_events( + get_group0_keyval(&event->key), 0); // with any mask + if (MOD__ALT(event)) { // alt + if (MOD__SHIFT(event)) { + drag->selected_move_screen(mul*-10, 0); // shift + } else { + drag->selected_move_screen(mul*-1, 0); // no shift + } + } else { // no alt + if (MOD__SHIFT(event)) { + drag->selected_move(mul*-10*nudge, 0); // shift + } else { + drag->selected_move(mul*-nudge, 0); // no shift + } + } + ret = TRUE; + } + break; + + case GDK_KEY_Up: // move handle up + case GDK_KEY_KP_Up: + case GDK_KEY_KP_8: + if (!MOD__CTRL(event)) { // not ctrl + gint mul = 1 + gobble_key_events( + get_group0_keyval(&event->key), 0); // with any mask + if (MOD__ALT(event)) { // alt + if (MOD__SHIFT(event)) { + drag->selected_move_screen(0, mul*10); // shift + } else { + drag->selected_move_screen(0, mul*1); // no shift + } + } else { // no alt + if (MOD__SHIFT(event)) { + drag->selected_move(0, mul*10*nudge); // shift + } else { + drag->selected_move(0, mul*nudge); // no shift + } + } + + ret = TRUE; + } + break; + + case GDK_KEY_Right: // move handle right + case GDK_KEY_KP_Right: + case GDK_KEY_KP_6: + if (!MOD__CTRL(event)) { // not ctrl + gint mul = 1 + gobble_key_events( + get_group0_keyval(&event->key), 0); // with any mask + + if (MOD__ALT(event)) { // alt + if (MOD__SHIFT(event)) { + drag->selected_move_screen(mul*10, 0); // shift + } else { + drag->selected_move_screen(mul*1, 0); // no shift + } + } else { // no alt + if (MOD__SHIFT(event)) { + drag->selected_move(mul*10*nudge, 0); // shift + } else { + drag->selected_move(mul*nudge, 0); // no shift + } + } + + ret = TRUE; + } + break; + + case GDK_KEY_Down: // move handle down + case GDK_KEY_KP_Down: + case GDK_KEY_KP_2: + if (!MOD__CTRL(event)) { // not ctrl + gint mul = 1 + gobble_key_events( + get_group0_keyval(&event->key), 0); // with any mask + + if (MOD__ALT(event)) { // alt + if (MOD__SHIFT(event)) { + drag->selected_move_screen(0, mul*-10); // shift + } else { + drag->selected_move_screen(0, mul*-1); // no shift + } + } else { // no alt + if (MOD__SHIFT(event)) { + drag->selected_move(0, mul*-10*nudge); // shift + } else { + drag->selected_move(0, mul*-nudge); // no shift + } + } + + ret = TRUE; + } + break; + + case GDK_KEY_r: + case GDK_KEY_R: + if (MOD__SHIFT_ONLY(event)) { + sp_gradient_reverse_selected_gradients(desktop); + ret = TRUE; + } + break; + + case GDK_KEY_Insert: + case GDK_KEY_KP_Insert: + // with any modifiers: + sp_gradient_context_add_stops_between_selected_stops (this); + ret = TRUE; + break; + + case GDK_KEY_Delete: + case GDK_KEY_KP_Delete: + case GDK_KEY_BackSpace: + ret = this->deleteSelectedDrag(MOD__CTRL_ONLY(event)); + break; + + default: + break; + } + break; + + case GDK_KEY_RELEASE: + switch (get_group0_keyval (&event->key)) { + case GDK_KEY_Alt_L: + case GDK_KEY_Alt_R: + case GDK_KEY_Control_L: + case GDK_KEY_Control_R: + case GDK_KEY_Shift_L: + case GDK_KEY_Shift_R: + case GDK_KEY_Meta_L: // Meta is when you press Shift+Alt + case GDK_KEY_Meta_R: + this->defaultMessageContext()->clear(); + break; + + default: + break; + } + break; + + default: + break; + } + + if (!ret) { + ret = ToolBase::root_handler(event); + } + + return ret; +} + +static void sp_gradient_drag(GradientTool &rc, Geom::Point const pt, guint /*state*/, guint32 etime) +{ + SPDesktop *desktop = SP_EVENT_CONTEXT(&rc)->desktop; + Inkscape::Selection *selection = sp_desktop_selection(desktop); + SPDocument *document = sp_desktop_document(desktop); + ToolBase *ec = SP_EVENT_CONTEXT(&rc); + + if (!selection->isEmpty()) { + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + int type = prefs->getInt("/tools/gradient/newgradient", 1); + Inkscape::PaintTarget fill_or_stroke = (prefs->getInt("/tools/gradient/newfillorstroke", 1) != 0) ? Inkscape::FOR_FILL : Inkscape::FOR_STROKE; + + SPGradient *vector; + if (ec->item_to_select) { + // pick color from the object where drag started + vector = sp_gradient_vector_for_object(document, desktop, ec->item_to_select, fill_or_stroke); + } else { + // Starting from empty space: + // Sort items so that the topmost comes last + GSList *items = g_slist_copy ((GSList *) selection->itemList()); + items = g_slist_sort(items, (GCompareFunc) sp_item_repr_compare_position); + // take topmost + vector = sp_gradient_vector_for_object(document, desktop, SP_ITEM(g_slist_last(items)->data), fill_or_stroke); + g_slist_free (items); + } + + // HACK: reset fill-opacity - that 0.75 is annoying; BUT remove this when we have an opacity slider for all tabs + SPCSSAttr *css = sp_repr_css_attr_new(); + sp_repr_css_set_property(css, "fill-opacity", "1.0"); + + for (GSList const *i = selection->itemList(); i != NULL; i = i->next) { + + //FIXME: see above + sp_repr_css_change_recursive(SP_OBJECT(i->data)->getRepr(), css, "style"); + + sp_item_set_gradient(SP_ITEM(i->data), vector, (SPGradientType) type, fill_or_stroke); + + if (type == SP_GRADIENT_TYPE_LINEAR) { + sp_item_gradient_set_coords (SP_ITEM(i->data), POINT_LG_BEGIN, 0, rc.origin, fill_or_stroke, true, false); + sp_item_gradient_set_coords (SP_ITEM(i->data), POINT_LG_END, 0, pt, fill_or_stroke, true, false); + } else if (type == SP_GRADIENT_TYPE_RADIAL) { + sp_item_gradient_set_coords (SP_ITEM(i->data), POINT_RG_CENTER, 0, rc.origin, fill_or_stroke, true, false); + sp_item_gradient_set_coords (SP_ITEM(i->data), POINT_RG_R1, 0, pt, fill_or_stroke, true, false); + } + SP_OBJECT(i->data)->requestModified(SP_OBJECT_MODIFIED_FLAG); + } + if (ec->_grdrag) { + ec->_grdrag->updateDraggers(); + // prevent regenerating draggers by selection modified signal, which sometimes + // comes too late and thus destroys the knot which we will now grab: + ec->_grdrag->local_change = true; + // give the grab out-of-bounds values of xp/yp because we're already dragging + // and therefore are already out of tolerance + ec->_grdrag->grabKnot (SP_ITEM(selection->itemList()->data), + type == SP_GRADIENT_TYPE_LINEAR? POINT_LG_END : POINT_RG_R1, + -1, // ignore number (though it is always 1) + fill_or_stroke, 99999, 99999, etime); + } + // We did an undoable action, but SPDocumentUndo::done will be called by the knot when released + + // status text; we do not track coords because this branch is run once, not all the time + // during drag + int n_objects = g_slist_length((GSList *) selection->itemList()); + rc.message_context->setF(Inkscape::NORMAL_MESSAGE, + ngettext("Gradient for %d object; with Ctrl to snap angle", + "Gradient for %d objects; with Ctrl to snap angle", n_objects), + n_objects); + } else { + sp_desktop_message_stack(desktop)->flash(Inkscape::WARNING_MESSAGE, _("Select objects on which to create gradient.")); + } +} + +} +} +} + + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/ui/tools/gradient-tool.h b/src/ui/tools/gradient-tool.h new file mode 100644 index 000000000..6fe3bca9f --- /dev/null +++ b/src/ui/tools/gradient-tool.h @@ -0,0 +1,76 @@ +#ifndef __SP_GRADIENT_CONTEXT_H__ +#define __SP_GRADIENT_CONTEXT_H__ + +/* + * Gradient drawing and editing tool + * + * Authors: + * bulia byak + * Johan Engelen + * Jon A. Cruz +#include +#include "ui/tools/tool-base.h" + +#define SP_GRADIENT_CONTEXT(obj) (dynamic_cast((Inkscape::UI::Tools::ToolBase*)obj)) +#define SP_IS_GRADIENT_CONTEXT(obj) (dynamic_cast((const Inkscape::UI::Tools::ToolBase*)obj) != NULL) + +namespace Inkscape { +namespace UI { +namespace Tools { + +class GradientTool : public ToolBase { +public: + GradientTool(); + virtual ~GradientTool(); + + Geom::Point origin; + + bool cursor_addnode; + + bool node_added; + + Geom::Point mousepoint_doc; // stores mousepoint when over_line in doc coords + + sigc::connection *selcon; + sigc::connection *subselcon; + + static const std::string prefsPath; + + virtual void setup(); + virtual bool root_handler(GdkEvent* event); + + virtual const std::string& getPrefsPath(); + +private: + void selection_changed(Inkscape::Selection*); +}; + +void sp_gradient_context_select_next (ToolBase *event_context); +void sp_gradient_context_select_prev (ToolBase *event_context); +void sp_gradient_context_add_stops_between_selected_stops (GradientTool *rc); + +} +} +} + +#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:fileencoding=utf-8:textwidth=99 : diff --git a/src/ui/tools/lpe-tool.cpp b/src/ui/tools/lpe-tool.cpp new file mode 100644 index 000000000..a5406f1c5 --- /dev/null +++ b/src/ui/tools/lpe-tool.cpp @@ -0,0 +1,503 @@ +/* + * LPEToolContext: a context for a generic tool composed of subtools that are given by LPEs + * + * Authors: + * Maximilian Albert + * Lauris Kaplinski + * Abhishek Sharma + * + * Copyright (C) 1998 The Free Software Foundation + * Copyright (C) 1999-2005 authors + * Copyright (C) 2001-2002 Ximian, Inc. + * Copyright (C) 2008 Maximilian Albert + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <2geom/sbasis-geometric.h> +#include + +#include +#include "macros.h" +#include "pixmaps/cursor-crosshairs.xpm" +#include +#include "desktop.h" +#include "message-context.h" +#include "preferences.h" +#include "shape-editor.h" +#include "selection.h" +#include "desktop-handles.h" +#include "document.h" +#include "display/curve.h" +#include "display/canvas-bpath.h" +#include "display/canvas-text.h" +#include "message-stack.h" +#include "sp-path.h" +#include "util/units.h" + +#include "ui/tools/lpe-tool.h" + +using Inkscape::Util::unit_table; +using Inkscape::UI::Tools::PenTool; + +const int num_subtools = 8; + +SubtoolEntry lpesubtools[] = { + // this must be here to account for the "all inactive" action + {Inkscape::LivePathEffect::INVALID_LPE, "draw-geometry-inactive"}, + {Inkscape::LivePathEffect::LINE_SEGMENT, "draw-geometry-line-segment"}, + {Inkscape::LivePathEffect::CIRCLE_3PTS, "draw-geometry-circle-from-three-points"}, + {Inkscape::LivePathEffect::CIRCLE_WITH_RADIUS, "draw-geometry-circle-from-radius"}, + {Inkscape::LivePathEffect::PARALLEL, "draw-geometry-line-parallel"}, + {Inkscape::LivePathEffect::PERP_BISECTOR, "draw-geometry-line-perpendicular"}, + {Inkscape::LivePathEffect::ANGLE_BISECTOR, "draw-geometry-angle-bisector"}, + {Inkscape::LivePathEffect::MIRROR_SYMMETRY, "draw-geometry-mirror"} +}; + + +#include "tool-factory.h" + +namespace Inkscape { +namespace UI { +namespace Tools { + +void sp_lpetool_context_selection_changed(Inkscape::Selection *selection, gpointer data); + +namespace { + ToolBase* createLPEToolContext() { + return new LpeTool(); + } + + bool lpetoolContextRegistered = ToolFactory::instance().registerObject("/tools/lpetool", createLPEToolContext); +} + +const std::string& LpeTool::getPrefsPath() { + return LpeTool::prefsPath; +} + +const std::string LpeTool::prefsPath = "/tools/lpetool"; + +LpeTool::LpeTool() : PenTool() { + this->mode = Inkscape::LivePathEffect::BEND_PATH; + this->shape_editor = 0; + + this->cursor_shape = cursor_crosshairs_xpm; + this->hot_x = 7; + this->hot_y = 7; + + this->canvas_bbox = NULL; + this->measuring_items = new std::map; +} + +LpeTool::~LpeTool() { + delete this->shape_editor; + this->shape_editor = NULL; + + if (this->canvas_bbox) { + sp_canvas_item_destroy(SP_CANVAS_ITEM(this->canvas_bbox)); + this->canvas_bbox = NULL; + } + + lpetool_delete_measuring_items(this); + delete this->measuring_items; + this->measuring_items = NULL; + + this->sel_changed_connection.disconnect(); +} + +void LpeTool::setup() { + PenTool::setup(); + + Inkscape::Selection *selection = sp_desktop_selection (this->desktop); + SPItem *item = selection->singleItem(); + + this->sel_changed_connection.disconnect(); + this->sel_changed_connection = + selection->connectChanged(sigc::bind(sigc::ptr_fun(&sp_lpetool_context_selection_changed), (gpointer)this)); + + this->shape_editor = new ShapeEditor(this->desktop); + + lpetool_context_switch_mode(this, Inkscape::LivePathEffect::INVALID_LPE); + lpetool_context_reset_limiting_bbox(this); + lpetool_create_measuring_items(this); + +// TODO temp force: + this->enableSelectionCue(); + + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + + if (item) { + this->shape_editor->set_item(item, SH_NODEPATH); + this->shape_editor->set_item(item, SH_KNOTHOLDER); + } + + if (prefs->getBool("/tools/lpetool/selcue")) { + this->enableSelectionCue(); + } +} + +/** + * Callback that processes the "changed" signal on the selection; + * destroys old and creates new nodepath and reassigns listeners to the new selected item's repr. + */ +void sp_lpetool_context_selection_changed(Inkscape::Selection *selection, gpointer data) +{ + LpeTool *lc = SP_LPETOOL_CONTEXT(data); + + lc->shape_editor->unset_item(SH_KNOTHOLDER); + SPItem *item = selection->singleItem(); + lc->shape_editor->set_item(item, SH_KNOTHOLDER); +} + +void LpeTool::set(const Inkscape::Preferences::Entry& val) { + if (val.getEntryName() == "mode") { + Inkscape::Preferences::get()->setString("/tools/geometric/mode", "drag"); + SP_PEN_CONTEXT(this)->mode = PenTool::MODE_DRAG; + } +} + +bool LpeTool::item_handler(SPItem* item, GdkEvent* event) { + gint ret = FALSE; + + switch (event->type) { + case GDK_BUTTON_PRESS: + { + // select the clicked item but do nothing else + Inkscape::Selection * const selection = sp_desktop_selection(this->desktop); + selection->clear(); + selection->add(item); + ret = TRUE; + break; + } + case GDK_BUTTON_RELEASE: + // TODO: do we need to catch this or can we pass it on to the parent handler? + ret = TRUE; + break; + default: + break; + } + + if (!ret) { + ret = PenTool::item_handler(item, event); + } + + return ret; +} + +bool LpeTool::root_handler(GdkEvent* event) { + Inkscape::Selection *selection = sp_desktop_selection (desktop); + + bool ret = false; + + if (sp_pen_context_has_waiting_LPE(this)) { + // quit when we are waiting for a LPE to be applied + //ret = ((ToolBaseClass *) sp_lpetool_context_parent_class)->root_handler(event_context, event); + return PenTool::root_handler(event); + } + + switch (event->type) { + case GDK_BUTTON_PRESS: + if (event->button.button == 1 && !this->space_panning) { + if (this->mode == Inkscape::LivePathEffect::INVALID_LPE) { + // don't do anything for now if we are inactive (except clearing the selection + // since this was a click into empty space) + selection->clear(); + desktop->messageStack()->flash(Inkscape::WARNING_MESSAGE, _("Choose a construction tool from the toolbar.")); + ret = true; + break; + } + + // save drag origin + this->xp = (gint) event->button.x; + this->yp = (gint) event->button.y; + this->within_tolerance = true; + + using namespace Inkscape::LivePathEffect; + + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + int mode = prefs->getInt("/tools/lpetool/mode"); + EffectType type = lpesubtools[mode].type; + + //bool over_stroke = lc->shape_editor->is_over_stroke(Geom::Point(event->button.x, event->button.y), true); + + sp_pen_context_wait_for_LPE_mouse_clicks(this, type, Inkscape::LivePathEffect::Effect::acceptsNumClicks(type)); + + // we pass the mouse click on to pen tool as the first click which it should collect + //ret = ((ToolBaseClass *) sp_lpetool_context_parent_class)->root_handler(event_context, event); + ret = PenTool::root_handler(event); + } + break; + + + case GDK_BUTTON_RELEASE: + { + /** + break; + **/ + } + + case GDK_KEY_PRESS: + /** + switch (get_group0_keyval (&event->key)) { + } + break; + **/ + + case GDK_KEY_RELEASE: + /** + switch (get_group0_keyval(&event->key)) { + case GDK_Control_L: + case GDK_Control_R: + dc->_message_context->clear(); + break; + default: + break; + } + **/ + + default: + break; + } + + if (!ret) { + ret = PenTool::root_handler(event); + } + + return ret; +} + +/* + * Finds the index in the list of geometric subtools corresponding to the given LPE type. + * Returns -1 if no subtool is found. + */ +int +lpetool_mode_to_index(Inkscape::LivePathEffect::EffectType const type) { + for (int i = 0; i < num_subtools; ++i) { + if (lpesubtools[i].type == type) { + return i; + } + } + return -1; +} + +/* + * Checks whether an item has a construction applied as LPE and if so returns the index in + * lpesubtools of this construction + */ +int lpetool_item_has_construction(LpeTool */*lc*/, SPItem *item) +{ + if (!SP_IS_LPE_ITEM(item)) { + return -1; + } + + Inkscape::LivePathEffect::Effect* lpe = SP_LPE_ITEM(item)->getCurrentLPE(); + if (!lpe) { + return -1; + } + return lpetool_mode_to_index(lpe->effectType()); +} + +/* + * Attempts to perform the construction of the given type (i.e., to apply the corresponding LPE) to + * a single selected item. Returns whether we succeeded. + */ +bool +lpetool_try_construction(LpeTool *lc, Inkscape::LivePathEffect::EffectType const type) +{ + Inkscape::Selection *selection = sp_desktop_selection(lc->desktop); + SPItem *item = selection->singleItem(); + + // TODO: should we check whether type represents a valid geometric construction? + if (item && SP_IS_LPE_ITEM(item) && Inkscape::LivePathEffect::Effect::acceptsNumClicks(type) == 0) { + Inkscape::LivePathEffect::Effect::createAndApply(type, sp_desktop_document(lc->desktop), item); + return true; + } + return false; +} + +void +lpetool_context_switch_mode(LpeTool *lc, Inkscape::LivePathEffect::EffectType const type) +{ + int index = lpetool_mode_to_index(type); + if (index != -1) { + lc->mode = type; + lc->desktop->setToolboxSelectOneValue ("lpetool_mode_action", index); + } else { + g_warning ("Invalid mode selected: %d", type); + return; + } +} + +void +lpetool_get_limiting_bbox_corners(SPDocument *document, Geom::Point &A, Geom::Point &B) { + Geom::Coord w = document->getWidth().value("px"); + Geom::Coord h = document->getHeight().value("px"); + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + + double ulx = prefs->getDouble("/tools/lpetool/bbox_upperleftx", 0); + double uly = prefs->getDouble("/tools/lpetool/bbox_upperlefty", 0); + double lrx = prefs->getDouble("/tools/lpetool/bbox_lowerrightx", w); + double lry = prefs->getDouble("/tools/lpetool/bbox_lowerrighty", h); + + A = Geom::Point(ulx, uly); + B = Geom::Point(lrx, lry); +} + +/* + * Reads the limiting bounding box from preferences and draws it on the screen + */ +// TODO: Note that currently the bbox is not user-settable; we simply use the page borders +void +lpetool_context_reset_limiting_bbox(LpeTool *lc) +{ + if (lc->canvas_bbox) { + sp_canvas_item_destroy(lc->canvas_bbox); + lc->canvas_bbox = NULL; + } + + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + if (!prefs->getBool("/tools/lpetool/show_bbox", true)) + return; + + SPDocument *document = sp_desktop_document(lc->desktop); + + Geom::Point A, B; + lpetool_get_limiting_bbox_corners(document, A, B); + Geom::Affine doc2dt(lc->desktop->doc2dt()); + A *= doc2dt; + B *= doc2dt; + + Geom::Rect rect(A, B); + SPCurve *curve = SPCurve::new_from_rect(rect); + + lc->canvas_bbox = sp_canvas_bpath_new (sp_desktop_controls(lc->desktop), curve); + sp_canvas_bpath_set_stroke(SP_CANVAS_BPATH(lc->canvas_bbox), 0x0000ffff, 0.8, SP_STROKE_LINEJOIN_MITER, SP_STROKE_LINECAP_BUTT, 5, 5); +} + +static void +set_pos_and_anchor(SPCanvasText *canvas_text, const Geom::Piecewise > &pwd2, + const double t, const double length, bool /*use_curvature*/ = false) +{ + using namespace Geom; + + Piecewise > pwd2_reparam = arc_length_parametrization(pwd2, 2 , 0.1); + double t_reparam = pwd2_reparam.cuts.back() * t; + Point pos = pwd2_reparam.valueAt(t_reparam); + Point dir = unit_vector(derivative(pwd2_reparam).valueAt(t_reparam)); + Point n = -rot90(dir); + double angle = Geom::angle_between(dir, Point(1,0)); + + sp_canvastext_set_coords(canvas_text, pos + n * length); + sp_canvastext_set_anchor_manually(canvas_text, std::sin(angle), -std::cos(angle)); +} + +void +lpetool_create_measuring_items(LpeTool *lc, Inkscape::Selection *selection) +{ + if (!selection) { + selection = sp_desktop_selection(lc->desktop); + } + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + bool show = prefs->getBool("/tools/lpetool/show_measuring_info", true); + + SPPath *path; + SPCurve *curve; + SPCanvasText *canvas_text; + SPCanvasGroup *tmpgrp = sp_desktop_tempgroup(lc->desktop); + gchar *arc_length; + double lengthval; + + for (GSList const *i = selection->itemList(); i != NULL; i = i->next) { + if (SP_IS_PATH(i->data)) { + path = SP_PATH(i->data); + curve = path->getCurve(); + Geom::Piecewise > pwd2 = paths_to_pw(curve->get_pathvector()); + canvas_text = (SPCanvasText *) sp_canvastext_new(tmpgrp, lc->desktop, Geom::Point(0,0), ""); + if (!show) + sp_canvas_item_hide(SP_CANVAS_ITEM(canvas_text)); + + Inkscape::Util::Unit const * unit = NULL; + if (prefs->getString("/tools/lpetool/unit").compare("")) { + unit = unit_table.getUnit(prefs->getString("/tools/lpetool/unit")); + } else { + unit = unit_table.getUnit("px"); + } + + lengthval = Geom::length(pwd2); + lengthval = Inkscape::Util::Quantity::convert(lengthval, "px", unit); + arc_length = g_strdup_printf("%.2f %s", lengthval, unit->abbr.c_str()); + sp_canvastext_set_text (canvas_text, arc_length); + set_pos_and_anchor(canvas_text, pwd2, 0.5, 10); + // TODO: must we free arc_length? + (*lc->measuring_items)[path] = SP_CANVAS_ITEM(canvas_text); + } + } +} + +void +lpetool_delete_measuring_items(LpeTool *lc) +{ + std::map::iterator i; + for (i = lc->measuring_items->begin(); i != lc->measuring_items->end(); ++i) { + sp_canvas_item_destroy(i->second); + } + lc->measuring_items->clear(); +} + +void +lpetool_update_measuring_items(LpeTool *lc) +{ + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + for ( std::map::iterator i = lc->measuring_items->begin(); + i != lc->measuring_items->end(); + ++i ) + { + SPPath *path = i->first; + SPCurve *curve = path->getCurve(); + Geom::Piecewise > pwd2 = Geom::paths_to_pw(curve->get_pathvector()); + Inkscape::Util::Unit const * unit = NULL; + if (prefs->getString("/tools/lpetool/unit").compare("")) { + unit = unit_table.getUnit(prefs->getString("/tools/lpetool/unit")); + } else { + unit = unit_table.getUnit("px"); + } + double lengthval = Geom::length(pwd2); + lengthval = Inkscape::Util::Quantity::convert(lengthval, "px", unit); + gchar *arc_length = g_strdup_printf("%.2f %s", lengthval, unit->abbr.c_str()); + sp_canvastext_set_text (SP_CANVASTEXT(i->second), arc_length); + set_pos_and_anchor(SP_CANVASTEXT(i->second), pwd2, 0.5, 10); + // TODO: must we free arc_length? + } +} + +void +lpetool_show_measuring_info(LpeTool *lc, bool show) +{ + std::map::iterator i; + for (i = lc->measuring_items->begin(); i != lc->measuring_items->end(); ++i) { + if (show) { + sp_canvas_item_show(i->second); + } else { + sp_canvas_item_hide(i->second); + } + } +} + +} +} +} + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/ui/tools/lpe-tool.h b/src/ui/tools/lpe-tool.h new file mode 100644 index 000000000..df78c205c --- /dev/null +++ b/src/ui/tools/lpe-tool.h @@ -0,0 +1,99 @@ +#ifndef SP_LPETOOL_CONTEXT_H_SEEN +#define SP_LPETOOL_CONTEXT_H_SEEN + +/* + * LPEToolContext: a context for a generic tool composed of subtools that are given by LPEs + * + * Authors: + * Maximilian Albert + * + * Copyright (C) 1998 The Free Software Foundation + * Copyright (C) 1999-2002 authors + * Copyright (C) 2001-2002 Ximian, Inc. + * Copyright (C) 2008 Maximilian Albert + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include "ui/tools/pen-tool.h" + +#define SP_LPETOOL_CONTEXT(obj) (dynamic_cast((Inkscape::UI::Tools::ToolBase*)obj)) +#define SP_IS_LPETOOL_CONTEXT(obj) (dynamic_cast((const Inkscape::UI::Tools::ToolBase*)obj) != NULL) + +/* This is the list of subtools from which the toolbar of the LPETool is built automatically */ +extern const int num_subtools; + +struct SubtoolEntry { + Inkscape::LivePathEffect::EffectType type; + gchar const *icon_name; +}; + +extern SubtoolEntry lpesubtools[]; + +enum LPEToolState { + LPETOOL_STATE_PEN, + LPETOOL_STATE_NODE +}; + +namespace Inkscape { +class Selection; +} + +class ShapeEditor; + +namespace Inkscape { +namespace UI { +namespace Tools { + +class LpeTool : public PenTool { +public: + LpeTool(); + virtual ~LpeTool(); + + ShapeEditor* shape_editor; + SPCanvasItem *canvas_bbox; + Inkscape::LivePathEffect::EffectType mode; + + std::map *measuring_items; + + sigc::connection sel_changed_connection; + sigc::connection sel_modified_connection; + + static const std::string prefsPath; + + virtual const std::string& getPrefsPath(); + +protected: + virtual void setup(); + virtual void set(const Inkscape::Preferences::Entry& val); + virtual bool root_handler(GdkEvent* event); + virtual bool item_handler(SPItem* item, GdkEvent* event); +}; + +int lpetool_mode_to_index(Inkscape::LivePathEffect::EffectType const type); +int lpetool_item_has_construction(LpeTool *lc, SPItem *item); +bool lpetool_try_construction(LpeTool *lc, Inkscape::LivePathEffect::EffectType const type); +void lpetool_context_switch_mode(LpeTool *lc, Inkscape::LivePathEffect::EffectType const type); +void lpetool_get_limiting_bbox_corners(SPDocument *document, Geom::Point &A, Geom::Point &B); +void lpetool_context_reset_limiting_bbox(LpeTool *lc); +void lpetool_create_measuring_items(LpeTool *lc, Inkscape::Selection *selection = NULL); +void lpetool_delete_measuring_items(LpeTool *lc); +void lpetool_update_measuring_items(LpeTool *lc); +void lpetool_show_measuring_info(LpeTool *lc, bool show = true); + +} +} +} + +#endif // SP_LPETOOL_CONTEXT_H_SEEN + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/ui/tools/measure-tool.cpp b/src/ui/tools/measure-tool.cpp new file mode 100644 index 000000000..0d823dfda --- /dev/null +++ b/src/ui/tools/measure-tool.cpp @@ -0,0 +1,777 @@ +/* + * Our nice measuring tool + * + * Authors: + * Felipe Correa da Silva Sanches + * Jon A. Cruz + * + * Copyright (C) 2011 Authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + + +#include +#include +#include "util/units.h" +#include "macros.h" +#include "display/curve.h" +#include "sp-shape.h" +#include "sp-text.h" +#include "sp-flowtext.h" +#include "text-editing.h" +#include "display/sp-ctrlline.h" +#include "display/sodipodi-ctrl.h" +#include "display/sp-canvas-item.h" +#include "display/sp-canvas-util.h" +#include "desktop.h" +#include "document.h" +#include "pixmaps/cursor-measure.xpm" +#include "preferences.h" +#include "inkscape.h" +#include "desktop-handles.h" +#include "ui/tools/measure-tool.h" +#include "ui/tools/freehand-base.h" +#include "display/canvas-text.h" +#include "path-chemistry.h" +#include "2geom/line.h" +#include <2geom/path-intersection.h> +#include <2geom/pathvector.h> +#include <2geom/crossing.h> +#include <2geom/angle.h> +#include "snap.h" +#include "sp-namedview.h" +#include "enums.h" +#include "ui/control-manager.h" + +using Inkscape::ControlManager; +using Inkscape::CTLINE_SECONDARY; +using Inkscape::Util::unit_table; + +#include "tool-factory.h" + +namespace Inkscape { +namespace UI { +namespace Tools { + +std::vector measure_tmp_items; + +namespace { + ToolBase* createMeasureContext() { + return new MeasureTool(); + } + + bool measureContextRegistered = ToolFactory::instance().registerObject("/tools/measure", createMeasureContext); +} + +const std::string& MeasureTool::getPrefsPath() { + return MeasureTool::prefsPath; +} + +const std::string MeasureTool::prefsPath = "/tools/measure"; + +namespace +{ + +gint const DIMENSION_OFFSET = 35; + +/** + * Simple class to use for removing label overlap. + */ +class LabelPlacement { +public: + + double lengthVal; + double offset; + Geom::Point start; + Geom::Point end; +}; + +bool SortLabelPlacement(LabelPlacement const &first, LabelPlacement const &second) +{ + if (first.end[Geom::Y] == second.end[Geom::Y]) { + return first.end[Geom::X] < second.end[Geom::X]; + } else { + return first.end[Geom::Y] < second.end[Geom::Y]; + } +} + +void repositionOverlappingLabels(std::vector &placements, SPDesktop *desktop, Geom::Point const &normal, double fontsize) +{ + std::sort(placements.begin(), placements.end(), SortLabelPlacement); + + double border = 3; + Geom::Rect box; + { + Geom::Point tmp(fontsize * 8 + (border * 2), fontsize + (border * 2)); + tmp = desktop->w2d(tmp); + box = Geom::Rect(-tmp[Geom::X] / 2, -tmp[Geom::Y] / 2, tmp[Geom::X] / 2, tmp[Geom::Y] / 2); + } + + // Using index since vector may be re-ordered as we go. + // Starting at one, since the first item can't overlap itself + for (size_t i = 1; i < placements.size(); i++) { + LabelPlacement &place = placements[i]; + + bool changed = false; + do { + Geom::Rect current(box + place.end); + + changed = false; + bool overlaps = false; + for (size_t j = i; (j > 0) && !overlaps; --j) { + LabelPlacement &otherPlace = placements[j - 1]; + Geom::Rect target(box + otherPlace.end); + if (current.intersects(target)) { + overlaps = true; + } + } + if (overlaps) { + place.offset += (fontsize + border); + place.end = place.start - desktop->w2d(normal * place.offset); + changed = true; + } + } while (changed); + + std::sort(placements.begin(), placements.begin() + i + 1, SortLabelPlacement); + } +} + +/** + * Calculates where to place the anchor for the display text and arc. + * + * @param desktop the desktop that is being used. + * @param angle the angle to be displaying. + * @param baseAngle the angle of the initial baseline. + * @param startPoint the point that is the vertex of the selected angle. + * @param endPoint the point that is the end the user is manipulating for measurement. + * @param fontsize the size to display the text label at. + */ +Geom::Point calcAngleDisplayAnchor(SPDesktop *desktop, double angle, double baseAngle, + Geom::Point const &startPoint, Geom::Point const &endPoint, + double fontsize) +{ + // Time for the trick work of figuring out where things should go, and how. + double lengthVal = (endPoint - startPoint).length(); + double effective = baseAngle + (angle / 2); + Geom::Point where(lengthVal, 0); + where *= Geom::Affine(Geom::Rotate(effective)) * Geom::Affine(Geom::Translate(startPoint)); + + // When the angle is tight, the label would end up under the cursor and/or lines. Bump it + double scaledFontsize = std::abs(fontsize * desktop->w2d(Geom::Point(0, 1.0))[Geom::Y]); + if (std::abs((where - endPoint).length()) < scaledFontsize) { + where[Geom::Y] += scaledFontsize * 2; + } + + // We now have the ideal position, but need to see if it will fit/work. + + Geom::Rect visibleArea = desktop->get_display_area(); + // Bring it in to "title safe" for the anchor point + Geom::Point textBox = desktop->w2d(Geom::Point(fontsize * 3, fontsize / 2)); + textBox[Geom::Y] = std::abs(textBox[Geom::Y]); + + visibleArea = Geom::Rect(visibleArea.min()[Geom::X] + textBox[Geom::X], + visibleArea.min()[Geom::Y] + textBox[Geom::Y], + visibleArea.max()[Geom::X] - textBox[Geom::X], + visibleArea.max()[Geom::Y] - textBox[Geom::Y]); + + where[Geom::X] = std::min(where[Geom::X], visibleArea.max()[Geom::X]); + where[Geom::X] = std::max(where[Geom::X], visibleArea.min()[Geom::X]); + where[Geom::Y] = std::min(where[Geom::Y], visibleArea.max()[Geom::Y]); + where[Geom::Y] = std::max(where[Geom::Y], visibleArea.min()[Geom::Y]); + + return where; +} + +/** + * Given an angle, the arc center and edge point, draw an arc segment centered around that edge point. + * + * @param desktop the desktop that is being used. + * @param center the center point for the arc. + * @param end the point that ends at the edge of the arc segment. + * @param anchor the anchor point for displaying the text label. + * @param angle the angle of the arc segment to draw. + */ +void createAngleDisplayCurve(SPDesktop *desktop, Geom::Point const ¢er, Geom::Point const &end, Geom::Point const &anchor, double angle) +{ + // Given that we have a point on the arc's edge and the angle of the arc, we need to get the two endpoints. + + double textLen = std::abs((anchor - center).length()); + double sideLen = std::abs((end - center).length()); + if (sideLen > 0.0) { + double factor = std::min(1.0, textLen / sideLen); + + // arc start + Geom::Point p1 = end * (Geom::Affine(Geom::Translate(-center)) + * Geom::Affine(Geom::Scale(factor)) + * Geom::Affine(Geom::Translate(center))); + + // arc end + Geom::Point p4 = p1 * (Geom::Affine(Geom::Translate(-center)) + * Geom::Affine(Geom::Rotate(-angle)) + * Geom::Affine(Geom::Translate(center))); + + // from Riskus + double xc = center[Geom::X]; + double yc = center[Geom::Y]; + double ax = p1[Geom::X] - xc; + double ay = p1[Geom::Y] - yc; + double bx = p4[Geom::X] - xc; + double by = p4[Geom::Y] - yc; + double q1 = (ax * ax) + (ay * ay); + double q2 = q1 + (ax * bx) + (ay * by); + + double k2 = (4.0 / 3.0) * (std::sqrt(2 * q1 * q2) - q2) / ((ax * by) - (ay * bx)); + + Geom::Point p2(xc + ax - (k2 * ay), + yc + ay + (k2 * ax)); + Geom::Point p3(xc + bx + (k2 * by), + yc + by - (k2 * bx)); + SPCtrlCurve *curve = ControlManager::getManager().createControlCurve(sp_desktop_tempgroup(desktop), p1, p2, p3, p4, CTLINE_SECONDARY); + + measure_tmp_items.push_back(desktop->add_temporary_canvasitem(SP_CANVAS_ITEM(curve), 0, true)); + } +} + +} // namespace + + +MeasureTool::MeasureTool() : ToolBase() { + this->grabbed = 0; + + this->cursor_shape = cursor_measure_xpm; + this->hot_x = 4; + this->hot_y = 4; +} + +MeasureTool::~MeasureTool() { +} + +void MeasureTool::finish() { + this->enableGrDrag(false); + + if (this->grabbed) { + sp_canvas_item_ungrab(this->grabbed, GDK_CURRENT_TIME); + this->grabbed = NULL; + } +} + +//void MeasureTool::setup() { +// ToolBase* ec = this; +// +//// if (SP_EVENT_CONTEXT_CLASS(sp_measure_context_parent_class)->setup) { +//// SP_EVENT_CONTEXT_CLASS(sp_measure_context_parent_class)->setup(ec); +//// } +// ToolBase::setup(); +//} + +//gint MeasureTool::item_handler(SPItem* item, GdkEvent* event) { +// gint ret = FALSE; +// +//// if (SP_EVENT_CONTEXT_CLASS(sp_measure_context_parent_class)->item_handler) { +//// ret = SP_EVENT_CONTEXT_CLASS(sp_measure_context_parent_class)->item_handler(event_context, item, event); +//// } +// ret = ToolBase::item_handler(item, event); +// +// return ret; +//} + +static bool GeomPointSortPredicate(const Geom::Point& p1, const Geom::Point& p2) +{ + if (p1[Geom::Y] == p2[Geom::Y]) { + return p1[Geom::X] < p2[Geom::X]; + } else { + return p1[Geom::Y] < p2[Geom::Y]; + } +} + +static void calculate_intersections(SPDesktop * /*desktop*/, SPItem* item, Geom::PathVector const &lineseg, SPCurve *curve, std::vector &intersections) +{ + curve->transform(item->i2doc_affine()); + + // Find all intersections of the control-line with this shape + Geom::CrossingSet cs = Geom::crossings(lineseg, curve->get_pathvector()); + + // Reconstruct and store the points of intersection + for (Geom::Crossings::const_iterator m = cs[0].begin(); m != cs[0].end(); ++m) { +#if 0 +//TODO: consider only visible intersections + Geom::Point intersection = lineseg[0].pointAt((*m).ta); + double eps = 0.0001; + SPDocument* doc = sp_desktop_document(desktop); + if (((*m).ta > eps && + item == doc->getItemAtPoint(desktop->dkey, lineseg[0].pointAt((*m).ta - eps), false, NULL)) || + ((*m).ta + eps < 1 && + item == doc->getItemAtPoint(desktop->dkey, lineseg[0].pointAt((*m).ta + eps), false, NULL)) ) { + intersections.push_back(intersection); + } +#else + intersections.push_back(lineseg[0].pointAt((*m).ta)); +#endif + } +} + +bool MeasureTool::root_handler(GdkEvent* event) { + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + tolerance = prefs->getIntLimited("/options/dragtolerance/value", 0, 0, 100); + + gint ret = FALSE; + + switch (event->type) { + case GDK_BUTTON_PRESS: { + Geom::Point const button_w(event->button.x, event->button.y); + explicitBase = boost::none; + lastEnd = boost::none; + start_point = desktop->w2d(button_w); + + if (event->button.button == 1 && !this->space_panning) { + // save drag origin + xp = static_cast(event->button.x); + yp = static_cast(event->button.y); + within_tolerance = true; + + ret = TRUE; + } + + SnapManager &m = desktop->namedview->snap_manager; + m.setup(desktop); + m.freeSnapReturnByRef(start_point, Inkscape::SNAPSOURCE_OTHER_HANDLE); + m.unSetup(); + + sp_canvas_item_grab(SP_CANVAS_ITEM(desktop->acetate), + GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK | GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK, + NULL, event->button.time); + this->grabbed = SP_CANVAS_ITEM(desktop->acetate); + break; + } + case GDK_KEY_PRESS: { + if ((event->key.keyval == GDK_KEY_Shift_L) || (event->key.keyval == GDK_KEY_Shift_R)) { + if (lastEnd) { + explicitBase = lastEnd; + } + } + break; + } + case GDK_MOTION_NOTIFY: { + if (!((event->motion.state & GDK_BUTTON1_MASK) && !this->space_panning)) { + if (!(event->motion.state & GDK_SHIFT_MASK)) { + Geom::Point const motion_w(event->motion.x, event->motion.y); + Geom::Point const motion_dt(desktop->w2d(motion_w)); + + SnapManager &m = desktop->namedview->snap_manager; + m.setup(desktop); + + Inkscape::SnapCandidatePoint scp(motion_dt, Inkscape::SNAPSOURCE_OTHER_HANDLE); + scp.addOrigin(start_point); + + m.preSnap(scp); + m.unSetup(); + } + } else { + ret = TRUE; + + if ( within_tolerance + && ( abs( static_cast(event->motion.x) - xp ) < tolerance ) + && ( abs( static_cast(event->motion.y) - yp ) < tolerance ) ) { + break; // do not drag if we're within tolerance from origin + } + // Once the user has moved farther than tolerance from the original location + // (indicating they intend to move the object, not click), then always process the + // motion notify coordinates as given (no snapping back to origin) + within_tolerance = false; + + //clear previous temporary canvas items, we'll draw new ones + for (size_t idx = 0; idx < measure_tmp_items.size(); ++idx) { + desktop->remove_temporary_canvasitem(measure_tmp_items[idx]); + } + + measure_tmp_items.clear(); + + Geom::Point const motion_w(event->motion.x, event->motion.y); + Geom::Point const motion_dt(desktop->w2d(motion_w)); + Geom::Point end_point = motion_dt; + + if (event->motion.state & GDK_CONTROL_MASK) { + spdc_endpoint_snap_rotation(this, end_point, start_point, event->motion.state); + } else { + if (!(event->motion.state & GDK_SHIFT_MASK)) { + SnapManager &m = desktop->namedview->snap_manager; + m.setup(desktop); + Inkscape::SnapCandidatePoint scp(end_point, Inkscape::SNAPSOURCE_OTHER_HANDLE); + scp.addOrigin(start_point); + Inkscape::SnappedPoint sp = m.freeSnap(scp); + end_point = sp.getPoint(); + m.unSetup(); + } + } + + Geom::PathVector lineseg; + Geom::Path p; + p.start(desktop->dt2doc(start_point)); + p.appendNew(desktop->dt2doc(end_point)); + lineseg.push_back(p); + + double deltax = end_point[Geom::X] - start_point[Geom::X]; + double deltay = end_point[Geom::Y] - start_point[Geom::Y]; + double angle = atan2(deltay, deltax); + double baseAngle = 0; + + if (explicitBase) { + double deltax2 = explicitBase.get()[Geom::X] - start_point[Geom::X]; + double deltay2 = explicitBase.get()[Geom::Y] - start_point[Geom::Y]; + + baseAngle = atan2(deltay2, deltax2); + angle -= baseAngle; + + if (angle < -M_PI) { + angle += 2 * M_PI; + } else if (angle > M_PI) { + angle -= 2 * M_PI; + } + } + +//TODO: calculate NPOINTS +//800 seems to be a good value for 800x600 resolution +#define NPOINTS 800 + + std::vector points; + + for (double i = 0; i < NPOINTS; i++) { + points.push_back(desktop->d2w(start_point + (i / NPOINTS) * (end_point - start_point))); + } + +// TODO: Felipe, why don't you simply iterate over all items, and test whether their bounding boxes intersect +// with the measurement line, instead of interpolating? E.g. bbox_of_measurement_line.intersects(*bbox_of_item). +// That's also how the object-snapper works, see _findCandidates() in object-snapper.cpp. + + //select elements crossed by line segment: + GSList *items = sp_desktop_document(desktop)->getItemsAtPoints(desktop->dkey, points); + std::vector intersections; + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + bool ignore_1st_and_last = prefs->getBool("/tools/measure/ignore_1st_and_last", true); + + if (!ignore_1st_and_last) { + intersections.push_back(desktop->dt2doc(start_point)); + } + + std::vector placements; + + // TODO switch to a different variable name. The single letter 'l' is easy to misread. + for (GSList *l = items; l != NULL; l = l->next) { + SPItem *item = static_cast(l->data); + + if (SP_IS_SHAPE(item)) { + calculate_intersections(desktop, item, lineseg, SP_SHAPE(item)->getCurve(), intersections); + } else { + if (SP_IS_TEXT(item) || SP_IS_FLOWTEXT(item)) { + Inkscape::Text::Layout::iterator iter = te_get_layout(item)->begin(); + do { + Inkscape::Text::Layout::iterator iter_next = iter; + iter_next.nextGlyph(); // iter_next is one glyph ahead from iter + if (iter == iter_next) { + break; + } + + // get path from iter to iter_next: + SPCurve *curve = te_get_layout(item)->convertToCurves(iter, iter_next); + iter = iter_next; // shift to next glyph + if (!curve) { + continue; // error converting this glyph + } + if (curve->is_empty()) { // whitespace glyph? + curve->unref(); + continue; + } + + curve->transform(item->i2doc_affine()); + + calculate_intersections(desktop, item, lineseg, curve, intersections); + + if (iter == te_get_layout(item)->end()) { + break; + } + } while (true); + } + } + } + + if (!ignore_1st_and_last) { + intersections.push_back(desktop->dt2doc(end_point)); + } + + //sort intersections + if (intersections.size() > 2) { + std::sort(intersections.begin(), intersections.end(), GeomPointSortPredicate); + } + + Glib::ustring unit_name = prefs->getString("/tools/measure/unit"); + if (!unit_name.compare("")) { + unit_name = "px"; + } + + double fontsize = prefs->getInt("/tools/measure/fontsize"); + + // Normal will be used for lines and text + Geom::Point windowNormal = Geom::unit_vector(Geom::rot90(desktop->d2w(end_point - start_point))); + Geom::Point normal = desktop->w2d(windowNormal); + + for (size_t idx = 1; idx < intersections.size(); ++idx) { + LabelPlacement placement; + placement.lengthVal = (intersections[idx] - intersections[idx - 1]).length(); + placement.lengthVal = Inkscape::Util::Quantity::convert(placement.lengthVal, "px", unit_name); + placement.offset = DIMENSION_OFFSET; + placement.start = desktop->doc2dt( (intersections[idx - 1] + intersections[idx]) / 2 ); + placement.end = placement.start - (normal * placement.offset); + + placements.push_back(placement); + } + + // Adjust positions + repositionOverlappingLabels(placements, desktop, windowNormal, fontsize); + + for (std::vector::iterator it = placements.begin(); it != placements.end(); ++it) + { + LabelPlacement &place = *it; + + // TODO cleanup memory, Glib::ustring, etc.: + gchar *measure_str = g_strdup_printf("%.2f %s", place.lengthVal, unit_name.c_str()); + SPCanvasText *canvas_tooltip = sp_canvastext_new(sp_desktop_tempgroup(desktop), + desktop, + place.end, + measure_str); + sp_canvastext_set_fontsize(canvas_tooltip, fontsize); + canvas_tooltip->rgba = 0xffffffff; + canvas_tooltip->rgba_background = 0x0000007f; + canvas_tooltip->outline = false; + canvas_tooltip->background = true; + canvas_tooltip->anchor_position = TEXT_ANCHOR_CENTER; + + measure_tmp_items.push_back(desktop->add_temporary_canvasitem(canvas_tooltip, 0)); + g_free(measure_str); + } + + Geom::Point angleDisplayPt = calcAngleDisplayAnchor(desktop, angle, baseAngle, + start_point, end_point, + fontsize); + + { + // TODO cleanup memory, Glib::ustring, etc.: + gchar *angle_str = g_strdup_printf("%.2f °", angle * 180/M_PI); + + SPCanvasText *canvas_tooltip = sp_canvastext_new(sp_desktop_tempgroup(desktop), + desktop, + angleDisplayPt, + angle_str); + sp_canvastext_set_fontsize(canvas_tooltip, fontsize); + canvas_tooltip->rgba = 0xffffffff; + canvas_tooltip->rgba_background = 0x337f337f; + canvas_tooltip->outline = false; + canvas_tooltip->background = true; + canvas_tooltip->anchor_position = TEXT_ANCHOR_CENTER; + + measure_tmp_items.push_back(desktop->add_temporary_canvasitem(canvas_tooltip, 0)); + g_free(angle_str); + } + + { + double totallengthval = (end_point - start_point).length(); + totallengthval = Inkscape::Util::Quantity::convert(totallengthval, "px", unit_name); + + // TODO cleanup memory, Glib::ustring, etc.: + gchar *totallength_str = g_strdup_printf("%.2f %s", totallengthval, unit_name.c_str()); + SPCanvasText *canvas_tooltip = sp_canvastext_new(sp_desktop_tempgroup(desktop), + desktop, + end_point + desktop->w2d(Geom::Point(3*fontsize, -fontsize)), + totallength_str); + sp_canvastext_set_fontsize(canvas_tooltip, fontsize); + canvas_tooltip->rgba = 0xffffffff; + canvas_tooltip->rgba_background = 0x3333337f; + canvas_tooltip->outline = false; + canvas_tooltip->background = true; + canvas_tooltip->anchor_position = TEXT_ANCHOR_LEFT; + + measure_tmp_items.push_back(desktop->add_temporary_canvasitem(canvas_tooltip, 0)); + g_free(totallength_str); + } + + if (intersections.size() > 2) { + double totallengthval = (intersections[intersections.size()-1] - intersections[0]).length(); + totallengthval = Inkscape::Util::Quantity::convert(totallengthval, "px", unit_name); + + // TODO cleanup memory, Glib::ustring, etc.: + gchar *total_str = g_strdup_printf("%.2f %s", totallengthval, unit_name.c_str()); + SPCanvasText *canvas_tooltip = sp_canvastext_new(sp_desktop_tempgroup(desktop), + desktop, + desktop->doc2dt((intersections[0] + intersections[intersections.size()-1])/2) + normal * 60, + total_str); + sp_canvastext_set_fontsize(canvas_tooltip, fontsize); + canvas_tooltip->rgba = 0xffffffff; + canvas_tooltip->rgba_background = 0x33337f7f; + canvas_tooltip->outline = false; + canvas_tooltip->background = true; + canvas_tooltip->anchor_position = TEXT_ANCHOR_CENTER; + + measure_tmp_items.push_back(desktop->add_temporary_canvasitem(canvas_tooltip, 0)); + g_free(total_str); + } + + // Now that text has been added, we can add lines and controls so that they go underneath + + for (size_t idx = 0; idx < intersections.size(); ++idx) { + // Display the intersection indicator (i.e. the cross) + SPCanvasItem * canvasitem = sp_canvas_item_new(sp_desktop_tempgroup(desktop), + SP_TYPE_CTRL, + "anchor", SP_ANCHOR_CENTER, + "size", 8.0, + "stroked", TRUE, + "stroke_color", 0xff0000ff, + "mode", SP_KNOT_MODE_XOR, + "shape", SP_KNOT_SHAPE_CROSS, + NULL ); + + SP_CTRL(canvasitem)->moveto(desktop->doc2dt(intersections[idx])); + measure_tmp_items.push_back(desktop->add_temporary_canvasitem(canvasitem, 0)); + } + + // Since adding goes to the bottom, do all lines last. + + // draw main control line + { + SPCtrlLine *control_line = ControlManager::getManager().createControlLine(sp_desktop_tempgroup(desktop), + start_point, + end_point); + measure_tmp_items.push_back(desktop->add_temporary_canvasitem(control_line, 0)); + + if ((end_point[Geom::X] != start_point[Geom::X]) && (end_point[Geom::Y] != start_point[Geom::Y])) { + double length = std::abs((end_point - start_point).length()); + Geom::Point anchorEnd = start_point; + anchorEnd[Geom::X] += length; + if (explicitBase) { + anchorEnd *= (Geom::Affine(Geom::Translate(-start_point)) + * Geom::Affine(Geom::Rotate(baseAngle)) + * Geom::Affine(Geom::Translate(start_point))); + } + + SPCtrlLine *control_line = ControlManager::getManager().createControlLine(sp_desktop_tempgroup(desktop), + start_point, + anchorEnd, + CTLINE_SECONDARY); + measure_tmp_items.push_back(desktop->add_temporary_canvasitem(control_line, 0)); + + createAngleDisplayCurve(desktop, start_point, end_point, angleDisplayPt, angle); + } + } + + if (intersections.size() > 2) { + ControlManager &mgr = ControlManager::getManager(); + SPCtrlLine *control_line = 0; + control_line = mgr.createControlLine(sp_desktop_tempgroup(desktop), + desktop->doc2dt(intersections[0]) + normal * 60, + desktop->doc2dt(intersections[intersections.size() - 1]) + normal * 60); + measure_tmp_items.push_back(desktop->add_temporary_canvasitem(control_line, 0)); + + control_line = mgr.createControlLine(sp_desktop_tempgroup(desktop), + desktop->doc2dt(intersections[0]), + desktop->doc2dt(intersections[0]) + normal * 65); + measure_tmp_items.push_back(desktop->add_temporary_canvasitem(control_line, 0)); + + control_line = mgr.createControlLine(sp_desktop_tempgroup(desktop), + desktop->doc2dt(intersections[intersections.size() - 1]), + desktop->doc2dt(intersections[intersections.size() - 1]) + normal * 65); + measure_tmp_items.push_back(desktop->add_temporary_canvasitem(control_line, 0)); + } + + // call-out lines + for (std::vector::iterator it = placements.begin(); it != placements.end(); ++it) + { + LabelPlacement &place = *it; + + ControlManager &mgr = ControlManager::getManager(); + SPCtrlLine *control_line = mgr.createControlLine(sp_desktop_tempgroup(desktop), + place.start, + place.end, + CTLINE_SECONDARY); + measure_tmp_items.push_back(desktop->add_temporary_canvasitem(control_line, 0)); + } + + { + for (size_t idx = 1; idx < intersections.size(); ++idx) { + Geom::Point measure_text_pos = (intersections[idx - 1] + intersections[idx]) / 2; + + ControlManager &mgr = ControlManager::getManager(); + SPCtrlLine *control_line = mgr.createControlLine(sp_desktop_tempgroup(desktop), + desktop->doc2dt(measure_text_pos), + desktop->doc2dt(measure_text_pos) - (normal * DIMENSION_OFFSET), + CTLINE_SECONDARY); + measure_tmp_items.push_back(desktop->add_temporary_canvasitem(control_line, 0)); + } + } + + // Initial point + { + SPCanvasItem * canvasitem = sp_canvas_item_new(sp_desktop_tempgroup(desktop), + SP_TYPE_CTRL, + "anchor", SP_ANCHOR_CENTER, + "size", 8.0, + "stroked", TRUE, + "stroke_color", 0xff0000ff, + "mode", SP_KNOT_MODE_XOR, + "shape", SP_KNOT_SHAPE_CROSS, + NULL ); + + SP_CTRL(canvasitem)->moveto(start_point); + measure_tmp_items.push_back(desktop->add_temporary_canvasitem(canvasitem, 0)); + } + + lastEnd = end_point; // track in case we get a anchoring key-press later + + gobble_motion_events(GDK_BUTTON1_MASK); + } + break; + } + case GDK_BUTTON_RELEASE: { + sp_event_context_discard_delayed_snap_event(this); + explicitBase = boost::none; + lastEnd = boost::none; + + //clear all temporary canvas items related to the measurement tool. + for (size_t idx = 0; idx < measure_tmp_items.size(); ++idx) { + desktop->remove_temporary_canvasitem(measure_tmp_items[idx]); + } + + measure_tmp_items.clear(); + + if (this->grabbed) { + sp_canvas_item_ungrab(this->grabbed, event->button.time); + this->grabbed = NULL; + } + + xp = 0; + yp = 0; + break; + } + default: + break; + } + + if (!ret) { + ret = ToolBase::root_handler(event); + } + + return ret; +} + +} +} +} + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 : diff --git a/src/ui/tools/measure-tool.h b/src/ui/tools/measure-tool.h new file mode 100644 index 000000000..9701ba6ea --- /dev/null +++ b/src/ui/tools/measure-tool.h @@ -0,0 +1,50 @@ +#ifndef SEEN_SP_MEASURING_CONTEXT_H +#define SEEN_SP_MEASURING_CONTEXT_H + +/* + * Our fine measuring tool + * + * Authors: + * Felipe Correa da Silva Sanches + * + * Copyright (C) 2011 Authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include "ui/tools/tool-base.h" +#include <2geom/point.h> +#include + +#define SP_MEASURE_CONTEXT(obj) (dynamic_cast((Inkscape::UI::Tools::ToolBase*)obj)) +#define SP_IS_MEASURE_CONTEXT(obj) (dynamic_cast((const Inkscape::UI::Tools::ToolBase*)obj) != NULL) + +namespace Inkscape { +namespace UI { +namespace Tools { + +class MeasureTool : public ToolBase { +public: + MeasureTool(); + virtual ~MeasureTool(); + + static const std::string prefsPath; + + virtual void finish(); + virtual bool root_handler(GdkEvent* event); + + virtual const std::string& getPrefsPath(); + +private: + SPCanvasItem* grabbed; + + Geom::Point start_point; + boost::optional explicitBase; + boost::optional lastEnd; +}; + +} +} +} + +#endif // SEEN_SP_MEASURING_CONTEXT_H diff --git a/src/ui/tools/mesh-tool.cpp b/src/ui/tools/mesh-tool.cpp new file mode 100644 index 000000000..4e7617f44 --- /dev/null +++ b/src/ui/tools/mesh-tool.cpp @@ -0,0 +1,1017 @@ +/* + * Mesh drawing and editing tool + * + * Authors: + * bulia byak + * Johan Engelen + * Abhishek Sharma + * Tavmjong Bah + * + * Copyright (C) 2012 Tavmjong Bah + * Copyright (C) 2007 Johan Engelen + * Copyright (C) 2005 Authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +//#define DEBUG_MESH + + +// Libraries +#include +#include + +// General +#include "desktop.h" +#include "desktop-handles.h" +#include "document.h" +#include "document-undo.h" +#include "macros.h" +#include "message-context.h" +#include "message-stack.h" +#include "preferences.h" +#include "rubberband.h" +#include "selection.h" +#include "snap.h" +#include "sp-namedview.h" +#include "verbs.h" + +// Gradient specific +#include "gradient-drag.h" +#include "gradient-chemistry.h" +#include "pixmaps/cursor-gradient.xpm" +#include "pixmaps/cursor-gradient-add.xpm" + +// Mesh specific +#include "ui/tools/mesh-tool.h" +#include "sp-mesh-gradient.h" +#include "display/sp-ctrlcurve.h" + +using Inkscape::DocumentUndo; + +#include "tool-factory.h" + +namespace Inkscape { +namespace UI { +namespace Tools { + +static void sp_mesh_drag(MeshTool &rc, Geom::Point const pt, guint state, guint32 etime); + +namespace { + ToolBase* createMeshContext() { + return new MeshTool(); + } + + bool meshContextRegistered = ToolFactory::instance().registerObject("/tools/mesh", createMeshContext); +} + +const std::string& MeshTool::getPrefsPath() { + return MeshTool::prefsPath; +} + +const std::string MeshTool::prefsPath = "/tools/mesh"; + +MeshTool::MeshTool() : ToolBase() { + this->selcon = 0; + this->node_added = false; + this->subselcon = 0; + + this->cursor_addnode = false; + this->cursor_shape = cursor_gradient_xpm; + this->hot_x = 4; + this->hot_y = 4; + this->xp = 0; + this->yp = 0; + this->tolerance = 6; + this->within_tolerance = false; + this->item_to_select = NULL; +} + +MeshTool::~MeshTool() { + this->enableGrDrag(false); + + this->selcon->disconnect(); + delete this->selcon; + + this->subselcon->disconnect(); + delete this->subselcon; +} + +const gchar *ms_handle_descr [] = { + N_("Mesh gradient corner"), + N_("Mesh gradient handle"), + N_("Mesh gradient tensor") +}; + +void MeshTool::selection_changed(Inkscape::Selection* /*sel*/) { + GrDrag *drag = this->_grdrag; + Inkscape::Selection *selection = sp_desktop_selection(this->desktop); + + if (selection == NULL) { + return; + } + + guint n_obj = g_slist_length((GSList *) selection->itemList()); + + if (!drag->isNonEmpty() || selection->isEmpty()) { + return; + } + + guint n_tot = drag->numDraggers(); + guint n_sel = drag->numSelected(); + + //The use of ngettext in the following code is intentional even if the English singular form would never be used + if (n_sel == 1) { + if (drag->singleSelectedDraggerNumDraggables() == 1) { + gchar * message = g_strconcat( + //TRANSLATORS: %s will be substituted with the point name (see previous messages); This is part of a compound message + _("%s selected"), + //TRANSLATORS: Mind the space in front. This is part of a compound message + ngettext(" out of %d mesh handle"," out of %d mesh handles",n_tot), + ngettext(" on %d selected object"," on %d selected objects",n_obj),NULL); + this->message_context->setF(Inkscape::NORMAL_MESSAGE, + message,_(ms_handle_descr[drag->singleSelectedDraggerSingleDraggableType()]), n_tot, n_obj); + } else { + gchar * message = + g_strconcat( + //TRANSLATORS: This is a part of a compound message (out of two more indicating: grandint handle count & object count) + ngettext("One handle merging %d stop (drag with Shift to separate) selected", + "One handle merging %d stops (drag with Shift to separate) selected", + drag->singleSelectedDraggerNumDraggables()), + ngettext(" out of %d mesh handle"," out of %d mesh handles",n_tot), + ngettext(" on %d selected object"," on %d selected objects",n_obj),NULL); + this->message_context->setF(Inkscape::NORMAL_MESSAGE,message,drag->singleSelectedDraggerNumDraggables(), n_tot, n_obj); + } + } else if (n_sel > 1) { + //TRANSLATORS: The plural refers to number of selected mesh handles. This is part of a compound message (part two indicates selected object count) + gchar * message = + g_strconcat(ngettext("%d mesh handle selected out of %d","%d mesh handles selected out of %d",n_sel), + //TRANSLATORS: Mind the space in front. (Refers to gradient handles selected). This is part of a compound message + ngettext(" on %d selected object"," on %d selected objects",n_obj),NULL); + this->message_context->setF(Inkscape::NORMAL_MESSAGE,message, n_sel, n_tot, n_obj); + } else if (n_sel == 0) { + this->message_context->setF(Inkscape::NORMAL_MESSAGE, + //TRANSLATORS: The plural refers to number of selected objects + ngettext("No mesh handles selected out of %d on %d selected object", + "No mesh handles selected out of %d on %d selected objects",n_obj), n_tot, n_obj); + } + + // FIXME + // We need to update mesh gradient handles. + // Get gradient this drag belongs too.. + // std::cout << "mesh_selection_changed: selection: objects: " << n_obj << std::endl; + // GSList *itemList = (GSList *) selection->itemList(); + // while( itemList ) { + + // SPItem *item = SP_ITEM( itemList->data ); + // // std::cout << " item: " << SP_OBJECT(item)->getId() << std::endl; + + // SPStyle *style = item->style; + // if (style && (style->fill.isPaintserver())) { + + // SPPaintServer *server = item->style->getFillPaintServer(); + // if ( SP_IS_MESHGRADIENT(server) ) { + + // SPMeshGradient *mg = SP_MESHGRADIENT(server); + + // guint rows = 0;//mg->array.patches.size(); + // for ( guint i = 0; i < rows; ++i ) { + // guint columns = 0;//mg->array.patches[0].size(); + // for ( guint j = 0; j < columns; ++j ) { + // } + // } + // } + // } + // itemList = itemList->next; + // } + + // GList* dragger_ptr = drag->draggers; // Points to GrDragger class (group of GrDraggable) + // guint count = 0; + // while( dragger_ptr ) { + + // std::cout << "mesh_selection_changed: dragger: " << ++count << std::endl; + // GSList* draggable_ptr = ((GrDragger *) dragger_ptr->data)->draggables; + + // while( draggable_ptr ) { + + // std::cout << "mesh_selection_changed: draggable: " << draggable_ptr << std::endl; + // GrDraggable *draggable = (GrDraggable *) draggable_ptr->data; + + // gint point_type = draggable->point_type; + // gint point_i = draggable->point_i; + // bool fill_or_stroke = draggable->fill_or_stroke; + + // if( point_type == POINT_MG_CORNER ) { + + // //std::cout << "mesh_selection_changed: POINT_MG_CORNER: " << point_i << std::endl; + // // Now we must create or destroy corresponding handles. + + // if( g_list_find( drag->selected, dragger_ptr->data ) ) { + // //std::cout << "gradient_selection_changed: Selected: " << point_i << std::endl; + // // Which meshes does this point belong to? + + // } else { + // //std::cout << "mesh_selection_changed: Not Selected: " << point_i << std::endl; + // } + // } + + // draggable_ptr = draggable_ptr->next; + + // } + + // dragger_ptr = dragger_ptr->next; + // } +} + +void MeshTool::setup() { + ToolBase::setup(); + + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + if (prefs->getBool("/tools/mesh/selcue", true)) { + this->enableSelectionCue(); + } + + this->enableGrDrag(); + Inkscape::Selection *selection = sp_desktop_selection(this->desktop); + + this->selcon = new sigc::connection(selection->connectChanged( + sigc::mem_fun(this, &MeshTool::selection_changed) + )); + + this->subselcon = new sigc::connection(this->desktop->connectToolSubselectionChanged( + sigc::hide(sigc::bind( + sigc::mem_fun(*this, &MeshTool::selection_changed), + (Inkscape::Selection*)NULL) + ) + )); + + this->selection_changed(selection); +} + +void +sp_mesh_context_select_next (ToolBase *event_context) +{ + GrDrag *drag = event_context->_grdrag; + g_assert (drag); + + GrDragger *d = drag->select_next(); + + event_context->desktop->scroll_to_point(d->point, 1.0); +} + +void +sp_mesh_context_select_prev (ToolBase *event_context) +{ + GrDrag *drag = event_context->_grdrag; + g_assert (drag); + + GrDragger *d = drag->select_prev(); + + event_context->desktop->scroll_to_point(d->point, 1.0); +} + +/** +Returns true if mouse cursor over mesh edge. +*/ +static bool +sp_mesh_context_is_over_line (MeshTool *rc, SPItem *item, Geom::Point event_p) +{ + SPDesktop *desktop = SP_EVENT_CONTEXT (rc)->desktop; + + //Translate mouse point into proper coord system + rc->mousepoint_doc = desktop->w2d(event_p); + + SPCtrlCurve *curve = SP_CTRLCURVE(item); + Geom::BezierCurveN<3> b( curve->p0, curve->p1, curve->p2, curve->p3 ); + Geom::Coord coord = b.nearestPoint( rc->mousepoint_doc ); // Coord == double + Geom::Point nearest = b( coord ); + + double dist_screen = Geom::L2 (rc->mousepoint_doc - nearest) * desktop->current_zoom(); + + double tolerance = (double) SP_EVENT_CONTEXT(rc)->tolerance; + + bool close = (dist_screen < tolerance); + + return close; +} + + +/** +Split row/column near the mouse point. +*/ +static void sp_mesh_context_split_near_point(MeshTool *rc, SPItem *item, Geom::Point mouse_p, guint32 /*etime*/) +{ + +#ifdef DEBUG_MESH + std::cout << "sp_mesh_context_split_near_point: entrance: " << mouse_p << std::endl; +#endif + + // item is the selected item. mouse_p the location in doc coordinates of where to add the stop + + ToolBase *ec = SP_EVENT_CONTEXT(rc); + SPDesktop *desktop = SP_EVENT_CONTEXT (rc)->desktop; + + double tolerance = (double) ec->tolerance; + + ec->get_drag()->addStopNearPoint (item, mouse_p, tolerance/desktop->current_zoom()); + + DocumentUndo::done(sp_desktop_document (desktop), SP_VERB_CONTEXT_MESH, + _("Split mesh row/column")); + + ec->get_drag()->updateDraggers(); +} + +/** +Wrapper for various mesh operations that require a list of selected corner nodes. + */ +static void +sp_mesh_context_corner_operation (MeshTool *rc, MeshCornerOperation operation ) +{ + +#ifdef DEBUG_MESH + std::cout << "sp_mesh_corner_operation: entrance: " << operation << std::endl; +#endif + + SPDocument *doc = NULL; + GrDrag *drag = rc->_grdrag; + + std::map > points; + std::map items; + + // Get list of selected draggers for each mesh. + // For all selected draggers + for (GList *i = drag->selected; i != NULL; i = i->next) { + GrDragger *dragger = (GrDragger *) i->data; + // For all draggables of dragger + for (GSList const* j = dragger->draggables; j != NULL; j = j->next) { + GrDraggable *d = (GrDraggable *) j->data; + + // Only mesh corners + if( d->point_type != POINT_MG_CORNER ) continue; + + // Find the gradient + SPMeshGradient *gradient = SP_MESHGRADIENT( getGradient (d->item, d->fill_or_stroke) ); + + // Collect points together for same gradient + points[gradient].push_back( d->point_i ); + items[gradient] = d->item; + } + } + + // Loop over meshes. + for( std::map >::const_iterator iter = points.begin(); iter != points.end(); ++iter) { + SPMeshGradient *mg = SP_MESHGRADIENT( iter->first ); + if( iter->second.size() > 0 ) { + guint noperation = 0; + switch (operation) { + + case MG_CORNER_SIDE_TOGGLE: + // std::cout << "SIDE_TOGGLE" << std::endl; + noperation += mg->array.side_toggle( iter->second ); + break; + + case MG_CORNER_SIDE_ARC: + // std::cout << "SIDE_ARC" << std::endl; + noperation += mg->array.side_arc( iter->second ); + break; + + case MG_CORNER_TENSOR_TOGGLE: + // std::cout << "TENSOR_TOGGLE" << std::endl; + noperation += mg->array.tensor_toggle( iter->second ); + break; + + case MG_CORNER_COLOR_SMOOTH: + // std::cout << "COLOR_SMOOTH" << std::endl; + noperation += mg->array.color_smooth( iter->second ); + break; + + case MG_CORNER_COLOR_PICK: + // std::cout << "COLOR_PICK" << std::endl; + noperation += mg->array.color_pick( iter->second, items[iter->first] ); + break; + + default: + std::cout << "sp_mesh_corner_operation: unknown operation" << std::endl; + } + + if( noperation > 0 ) { + mg->array.write( mg ); + mg->requestModified(SP_OBJECT_MODIFIED_FLAG); + doc = mg->document; + + switch (operation) { + + case MG_CORNER_SIDE_TOGGLE: + DocumentUndo::done(doc, SP_VERB_CONTEXT_MESH, _("Toggled mesh path type.")); + break; + + case MG_CORNER_SIDE_ARC: + DocumentUndo::done(doc, SP_VERB_CONTEXT_MESH, _("Approximated arc for mesh side.")); + break; + + case MG_CORNER_TENSOR_TOGGLE: + DocumentUndo::done(doc, SP_VERB_CONTEXT_MESH, _("Toggled mesh tensors.")); + break; + + case MG_CORNER_COLOR_SMOOTH: + DocumentUndo::done(doc, SP_VERB_CONTEXT_MESH, _("Smoothed mesh corner color.")); + break; + + case MG_CORNER_COLOR_PICK: + DocumentUndo::done(doc, SP_VERB_CONTEXT_MESH, _("Picked mesh corner color.")); + break; + + default: + std::cout << "sp_mesh_corner_operation: unknown operation" << std::endl; + } + } + } + } + drag->updateDraggers(); + +} + + +/** +Handles all keyboard and mouse input for meshs. +*/ +bool MeshTool::root_handler(GdkEvent* event) { + static bool dragging; + + Inkscape::Selection *selection = sp_desktop_selection (desktop); + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + + this->tolerance = prefs->getIntLimited("/options/dragtolerance/value", 0, 0, 100); + double const nudge = prefs->getDoubleLimited("/options/nudgedistance/value", 2, 0, 1000, "px"); // in px + + GrDrag *drag = this->_grdrag; + g_assert (drag); + + gint ret = FALSE; + + switch (event->type) { + case GDK_2BUTTON_PRESS: + +#ifdef DEBUG_MESH + std::cout << "sp_mesh_context_root_handler: GDK_2BUTTON_PRESS" << std::endl; +#endif + + // Double click: + // If over a mesh line, divide mesh row/column + // If not over a line, create new gradients for selected objects. + + if ( event->button.button == 1 ) { + // Are we over a mesh line? + bool over_line = false; + SPCtrlCurve *line = NULL; + + if (drag->lines) { + for (GSList *l = drag->lines; (l != NULL) && (!over_line); l = l->next) { + line = (SPCtrlCurve*) l->data; + over_line |= sp_mesh_context_is_over_line (this, (SPItem*) line, Geom::Point(event->motion.x, event->motion.y)); + } + } + + if (over_line) { + // We take the first item in selection, because with doubleclick, the first click + // always resets selection to the single object under cursor + sp_mesh_context_split_near_point(this, SP_ITEM(selection->itemList()->data), this->mousepoint_doc, event->button.time); + } else { + // Create a new gradient with default coordinates. + for (GSList const* i = selection->itemList(); i != NULL; i = i->next) { + SPItem *item = SP_ITEM(i->data); + SPGradientType new_type = SP_GRADIENT_TYPE_MESH; + Inkscape::PaintTarget fsmode = (prefs->getInt("/tools/gradient/newfillorstroke", 1) != 0) ? Inkscape::FOR_FILL : Inkscape::FOR_STROKE; + +#ifdef DEBUG_MESH + std::cout << "sp_mesh_context_root_handler: creating new mesh on: " << (fsmode == Inkscape::FOR_FILL ? "Fill" : "Stroke") << std::endl; +#endif + SPGradient *vector = sp_gradient_vector_for_object(sp_desktop_document(desktop), desktop, item, fsmode); + + SPGradient *priv = sp_item_set_gradient(item, vector, new_type, fsmode); + sp_gradient_reset_to_userspace(priv, item); + } + + DocumentUndo::done(sp_desktop_document (desktop), SP_VERB_CONTEXT_MESH, + _("Create default mesh")); + } + + ret = TRUE; + } + break; + + case GDK_BUTTON_PRESS: + +#ifdef DEBUG_MESH + std::cout << "sp_mesh_context_root_handler: GDK_BUTTON_PRESS" << std::endl; +#endif + // Button down + // If Shift key down: do rubber band selection + // Else set origin for drag. A drag creates a new gradient if one does not exist + if ( event->button.button == 1 && !this->space_panning ) { + Geom::Point button_w(event->button.x, event->button.y); + + // save drag origin + this->xp = (gint) button_w[Geom::X]; + this->yp = (gint) button_w[Geom::Y]; + this->within_tolerance = true; + + dragging = true; + + Geom::Point button_dt = desktop->w2d(button_w); + if (event->button.state & GDK_SHIFT_MASK) { + Inkscape::Rubberband::get(desktop)->start(desktop, button_dt); + } else { + // remember clicked item, disregarding groups, honoring Alt; do nothing with Crtl to + // enable Ctrl+doubleclick of exactly the selected item(s) + if (!(event->button.state & GDK_CONTROL_MASK)) { + this->item_to_select = sp_event_context_find_item (desktop, button_w, event->button.state & GDK_MOD1_MASK, TRUE); + } + + if (!selection->isEmpty()) { + SnapManager &m = desktop->namedview->snap_manager; + m.setup(desktop); + m.freeSnapReturnByRef(button_dt, Inkscape::SNAPSOURCE_NODE_HANDLE); + m.unSetup(); + } + + this->origin = button_dt; + } + + ret = TRUE; + } + break; + + case GDK_MOTION_NOTIFY: + // Mouse move + if ( dragging && ( event->motion.state & GDK_BUTTON1_MASK ) && !this->space_panning ) { + +#ifdef DEBUG_MESH + std::cout << "sp_mesh_context_root_handler: GDK_MOTION_NOTIFY: Dragging" << std::endl; +#endif + if ( this->within_tolerance + && ( abs( (gint) event->motion.x - this->xp ) < this->tolerance ) + && ( abs( (gint) event->motion.y - this->yp ) < this->tolerance ) ) { + break; // do not drag if we're within tolerance from origin + } + // Once the user has moved farther than tolerance from the original location + // (indicating they intend to draw, not click), then always process the + // motion notify coordinates as given (no snapping back to origin) + this->within_tolerance = false; + + Geom::Point const motion_w(event->motion.x, + event->motion.y); + Geom::Point const motion_dt = this->desktop->w2d(motion_w); + + if (Inkscape::Rubberband::get(desktop)->is_started()) { + Inkscape::Rubberband::get(desktop)->move(motion_dt); + this->defaultMessageContext()->set(Inkscape::NORMAL_MESSAGE, _("Draw around handles to select them")); + } else { + // Create new gradient with coordinates determined by drag. + sp_mesh_drag(*this, motion_dt, event->motion.state, event->motion.time); + } + + gobble_motion_events(GDK_BUTTON1_MASK); + + ret = TRUE; + } else { + // Not dragging + + // Do snapping + if (!drag->mouseOver() && !selection->isEmpty()) { + SnapManager &m = desktop->namedview->snap_manager; + m.setup(desktop); + + Geom::Point const motion_w(event->motion.x, event->motion.y); + Geom::Point const motion_dt = this->desktop->w2d(motion_w); + + m.preSnap(Inkscape::SnapCandidatePoint(motion_dt, Inkscape::SNAPSOURCE_OTHER_HANDLE)); + m.unSetup(); + } + + // Highlight corner node corresponding to side or tensor node + if( drag->mouseOver() ) { + // MESH FIXME: Light up corresponding corner node corresponding to node we are over. + // See "pathflash" in ui/tools/node-tool.cpp for ideas. + // Use desktop->add_temporary_canvasitem( SPCanvasItem, milliseconds ); + } + + // Change cursor shape if over line + bool over_line = false; + + if (drag->lines) { + for (GSList *l = drag->lines; l != NULL; l = l->next) { + over_line |= sp_mesh_context_is_over_line (this, (SPItem*) l->data, Geom::Point(event->motion.x, event->motion.y)); + } + } + + if (this->cursor_addnode && !over_line) { + this->cursor_shape = cursor_gradient_xpm; + this->sp_event_context_update_cursor(); + this->cursor_addnode = false; + } else if (!this->cursor_addnode && over_line) { + this->cursor_shape = cursor_gradient_add_xpm; + this->sp_event_context_update_cursor(); + this->cursor_addnode = true; + } + } + break; + + case GDK_BUTTON_RELEASE: + +#ifdef DEBUG_MESH + std::cout << "sp_mesh_context_root_handler: GDK_BUTTON_RELEASE" << std::endl; +#endif + + this->xp = this->yp = 0; + + if ( event->button.button == 1 && !this->space_panning ) { + // Check if over line + bool over_line = false; + SPCtrlLine *line = NULL; + + if (drag->lines) { + for (GSList *l = drag->lines; (l != NULL) && (!over_line); l = l->next) { + line = (SPCtrlLine*) l->data; + over_line = sp_mesh_context_is_over_line (this, (SPItem*) line, Geom::Point(event->motion.x, event->motion.y)); + + if (over_line) { + break; + } + } + } + + if ( (event->button.state & GDK_CONTROL_MASK) && (event->button.state & GDK_MOD1_MASK ) ) { + if (over_line && line) { + sp_mesh_context_split_near_point(this, line->item, this->mousepoint_doc, 0); + ret = TRUE; + } + } else { + dragging = false; + + // unless clicked with Ctrl (to enable Ctrl+doubleclick). + if (event->button.state & GDK_CONTROL_MASK) { + ret = TRUE; + break; + } + + if (!this->within_tolerance) { + // we've been dragging, either do nothing (grdrag handles that), + // or rubberband-select if we have rubberband + Inkscape::Rubberband *r = Inkscape::Rubberband::get(desktop); + + if (r->is_started() && !this->within_tolerance) { + // this was a rubberband drag + if (r->getMode() == RUBBERBAND_MODE_RECT) { + Geom::OptRect const b = r->getRectangle(); + drag->selectRect(*b); + } + } + } else if (this->item_to_select) { + if (over_line && line) { + // Clicked on an existing mesh line, don't change selection. This stops + // possible change in selection during a double click with overlapping objects + } else { + // no dragging, select clicked item if any + if (event->button.state & GDK_SHIFT_MASK) { + selection->toggle(this->item_to_select); + } else { + selection->set(this->item_to_select); + } + } + } else { + // click in an empty space; do the same as Esc + if (drag->selected) { + drag->deselectAll(); + } else { + selection->clear(); + } + } + + this->item_to_select = NULL; + ret = TRUE; + } + + Inkscape::Rubberband::get(desktop)->stop(); + } + break; + + case GDK_KEY_PRESS: + +#ifdef DEBUG_MESH + std::cout << "sp_mesh_context_root_handler: GDK_KEY_PRESS" << std::endl; +#endif + + // FIXME: tip + switch (get_group0_keyval (&event->key)) { + case GDK_KEY_Alt_L: + case GDK_KEY_Alt_R: + case GDK_KEY_Control_L: + case GDK_KEY_Control_R: + case GDK_KEY_Shift_L: + case GDK_KEY_Shift_R: + case GDK_KEY_Meta_L: // Meta is when you press Shift+Alt (at least on my machine) + case GDK_KEY_Meta_R: + sp_event_show_modifier_tip (this->defaultMessageContext(), event, + _("FIXMECtrl: snap mesh angle"), + _("FIXMEShift: draw mesh around the starting point"), + NULL); + break; + + case GDK_KEY_A: + case GDK_KEY_a: + if (MOD__CTRL_ONLY(event) && drag->isNonEmpty()) { + drag->selectAll(); + ret = TRUE; + } + break; + + case GDK_KEY_Escape: + if (drag->selected) { + drag->deselectAll(); + } else { + selection->clear(); + } + + ret = TRUE; + //TODO: make dragging escapable by Esc + break; + + case GDK_KEY_Left: // move handle left + case GDK_KEY_KP_Left: + case GDK_KEY_KP_4: + if (!MOD__CTRL(event)) { // not ctrl + gint mul = 1 + gobble_key_events(get_group0_keyval(&event->key), 0); // with any mask + + if (MOD__ALT(event)) { // alt + if (MOD__SHIFT(event)) { + drag->selected_move_screen(mul*-10, 0); // shift + } else { + drag->selected_move_screen(mul*-1, 0); // no shift + } + } else { // no alt + if (MOD__SHIFT(event)) { + drag->selected_move(mul*-10*nudge, 0); // shift + } else { + drag->selected_move(mul*-nudge, 0); // no shift + } + } + + ret = TRUE; + } + break; + + case GDK_KEY_Up: // move handle up + case GDK_KEY_KP_Up: + case GDK_KEY_KP_8: + if (!MOD__CTRL(event)) { // not ctrl + gint mul = 1 + gobble_key_events(get_group0_keyval(&event->key), 0); // with any mask + + if (MOD__ALT(event)) { // alt + if (MOD__SHIFT(event)) { + drag->selected_move_screen(0, mul*10); // shift + } else { + drag->selected_move_screen(0, mul*1); // no shift + } + } else { // no alt + if (MOD__SHIFT(event)) { + drag->selected_move(0, mul*10*nudge); // shift + } else { + drag->selected_move(0, mul*nudge); // no shift + } + } + + ret = TRUE; + } + break; + + case GDK_KEY_Right: // move handle right + case GDK_KEY_KP_Right: + case GDK_KEY_KP_6: + if (!MOD__CTRL(event)) { // not ctrl + gint mul = 1 + gobble_key_events(get_group0_keyval(&event->key), 0); // with any mask + + if (MOD__ALT(event)) { // alt + if (MOD__SHIFT(event)) { + drag->selected_move_screen(mul*10, 0); // shift + } else { + drag->selected_move_screen(mul*1, 0); // no shift + } + } else { // no alt + if (MOD__SHIFT(event)) { + drag->selected_move(mul*10*nudge, 0); // shift + } else { + drag->selected_move(mul*nudge, 0); // no shift + } + } + + ret = TRUE; + } + break; + + case GDK_KEY_Down: // move handle down + case GDK_KEY_KP_Down: + case GDK_KEY_KP_2: + if (!MOD__CTRL(event)) { // not ctrl + gint mul = 1 + gobble_key_events(get_group0_keyval(&event->key), 0); // with any mask + + if (MOD__ALT(event)) { // alt + if (MOD__SHIFT(event)) { + drag->selected_move_screen(0, mul*-10); // shift + } else { + drag->selected_move_screen(0, mul*-1); // no shift + } + } else { // no alt + if (MOD__SHIFT(event)) { + drag->selected_move(0, mul*-10*nudge); // shift + } else { + drag->selected_move(0, mul*-nudge); // no shift + } + } + + ret = TRUE; + } + break; + + case GDK_KEY_Insert: + case GDK_KEY_KP_Insert: + // with any modifiers: + //sp_gradient_context_add_stops_between_selected_stops (rc); + std::cout << "Inserting stops between selected stops not implemented yet" << std::endl; + ret = TRUE; + break; + + case GDK_KEY_Delete: + case GDK_KEY_KP_Delete: + case GDK_KEY_BackSpace: + if ( drag->selected ) { + std::cout << "Deleting mesh stops not implemented yet" << std::endl; + ret = TRUE; + } + break; + + // Mesh Operations -------------------------------------------- + + case GDK_KEY_b: // Toggle mesh side between lineto and curveto. + case GDK_KEY_B: + if (MOD__ALT(event) && drag->isNonEmpty() && drag->hasSelection()) { + sp_mesh_context_corner_operation ( this, MG_CORNER_SIDE_TOGGLE ); + ret = TRUE; + } + break; + + case GDK_KEY_c: // Convert mesh side from generic Bezier to Bezier approximating arc, + case GDK_KEY_C: // preserving handle direction. + if (MOD__ALT(event) && drag->isNonEmpty() && drag->hasSelection()) { + sp_mesh_context_corner_operation ( this, MG_CORNER_SIDE_ARC ); + ret = TRUE; + } + break; + + case GDK_KEY_g: // Toggle mesh tensor points on/off + case GDK_KEY_G: + if (MOD__ALT(event) && drag->isNonEmpty() && drag->hasSelection()) { + sp_mesh_context_corner_operation ( this, MG_CORNER_TENSOR_TOGGLE ); + ret = TRUE; + } + break; + + case GDK_KEY_j: // Smooth corner color + case GDK_KEY_J: + if (MOD__ALT(event) && drag->isNonEmpty() && drag->hasSelection()) { + sp_mesh_context_corner_operation ( this, MG_CORNER_COLOR_SMOOTH ); + ret = TRUE; + } + break; + + case GDK_KEY_k: // Pick corner color + case GDK_KEY_K: + if (MOD__ALT(event) && drag->isNonEmpty() && drag->hasSelection()) { + sp_mesh_context_corner_operation ( this, MG_CORNER_COLOR_PICK ); + ret = TRUE; + } + break; + + default: + break; + } + + break; + + case GDK_KEY_RELEASE: + +#ifdef DEBUG_MESH + std::cout << "sp_mesh_context_root_handler: GDK_KEY_RELEASE" << std::endl; +#endif + switch (get_group0_keyval (&event->key)) { + case GDK_KEY_Alt_L: + case GDK_KEY_Alt_R: + case GDK_KEY_Control_L: + case GDK_KEY_Control_R: + case GDK_KEY_Shift_L: + case GDK_KEY_Shift_R: + case GDK_KEY_Meta_L: // Meta is when you press Shift+Alt + case GDK_KEY_Meta_R: + this->defaultMessageContext()->clear(); + break; + default: + break; + } + break; + default: + break; + } + + if (!ret) { + ret = ToolBase::root_handler(event); + } + + return ret; +} + +static void sp_mesh_drag(MeshTool &rc, Geom::Point const /*pt*/, guint /*state*/, guint32 /*etime*/) { + SPDesktop *desktop = SP_EVENT_CONTEXT(&rc)->desktop; + Inkscape::Selection *selection = sp_desktop_selection(desktop); + SPDocument *document = sp_desktop_document(desktop); + ToolBase *ec = SP_EVENT_CONTEXT(&rc); + + if (!selection->isEmpty()) { + + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + int type = SP_GRADIENT_TYPE_MESH; + Inkscape::PaintTarget fill_or_stroke = (prefs->getInt("/tools/gradient/newfillorstroke", 1) != 0) ? Inkscape::FOR_FILL : Inkscape::FOR_STROKE; + + SPGradient *vector; + if (ec->item_to_select) { + // pick color from the object where drag started + vector = sp_gradient_vector_for_object(document, desktop, ec->item_to_select, fill_or_stroke); + } else { + // Starting from empty space: + // Sort items so that the topmost comes last + GSList *items = g_slist_copy ((GSList *) selection->itemList()); + items = g_slist_sort(items, (GCompareFunc) sp_item_repr_compare_position); + // take topmost + vector = sp_gradient_vector_for_object(document, desktop, SP_ITEM(g_slist_last(items)->data), fill_or_stroke); + g_slist_free (items); + } + + // HACK: reset fill-opacity - that 0.75 is annoying; BUT remove this when we have an opacity slider for all tabs + SPCSSAttr *css = sp_repr_css_attr_new(); + sp_repr_css_set_property(css, "fill-opacity", "1.0"); + + for (GSList const *i = selection->itemList(); i != NULL; i = i->next) { + + //FIXME: see above + sp_repr_css_change_recursive(SP_OBJECT(i->data)->getRepr(), css, "style"); + + sp_item_set_gradient(SP_ITEM(i->data), vector, (SPGradientType) type, fill_or_stroke); + + // We don't need to do anything. Mesh is already sized appropriately. + + SP_OBJECT(i->data)->requestModified(SP_OBJECT_MODIFIED_FLAG); + } + // if (ec->_grdrag) { + // ec->_grdrag->updateDraggers(); + // // prevent regenerating draggers by selection modified signal, which sometimes + // // comes too late and thus destroys the knot which we will now grab: + // ec->_grdrag->local_change = true; + // // give the grab out-of-bounds values of xp/yp because we're already dragging + // // and therefore are already out of tolerance + // ec->_grdrag->grabKnot (SP_ITEM(selection->itemList()->data), + // type == SP_GRADIENT_TYPE_LINEAR? POINT_LG_END : POINT_RG_R1, + // -1, // ignore number (though it is always 1) + // fill_or_stroke, 99999, 99999, etime); + // } + // We did an undoable action, but SPDocumentUndo::done will be called by the knot when released + + // status text; we do not track coords because this branch is run once, not all the time + // during drag + int n_objects = g_slist_length((GSList *) selection->itemList()); + rc.message_context->setF(Inkscape::NORMAL_MESSAGE, + ngettext("Gradient for %d object; with Ctrl to snap angle", + "Gradient for %d objects; with Ctrl to snap angle", n_objects), + n_objects); + } else { + sp_desktop_message_stack(desktop)->flash(Inkscape::WARNING_MESSAGE, _("Select objects on which to create gradient.")); + } + +} + +} +} +} + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/ui/tools/mesh-tool.h b/src/ui/tools/mesh-tool.h new file mode 100644 index 000000000..d952c9010 --- /dev/null +++ b/src/ui/tools/mesh-tool.h @@ -0,0 +1,77 @@ +#ifndef SEEN_SP_MESH_CONTEXT_H +#define SEEN_SP_MESH_CONTEXT_H + +/* + * Mesh drawing and editing tool + * + * Authors: + * bulia byak + * Johan Engelen + * Jon A. Cruz + * + * Copyright (C) 2012 Tavmjong Bah + * Copyright (C) 2007 Johan Engelen + * Copyright (C) 2005,2010 Authors + * + * Released under GNU GPL + */ + +#include +#include +#include "ui/tools/tool-base.h" + +#define SP_MESH_CONTEXT(obj) (dynamic_cast((Inkscape::UI::Tools::ToolBase*)obj)) +#define SP_IS_MESH_CONTEXT(obj) (dynamic_cast((const Inkscape::UI::Tools::ToolBase*)obj) != NULL) + +namespace Inkscape { +namespace UI { +namespace Tools { + +class MeshTool : public ToolBase { +public: + MeshTool(); + virtual ~MeshTool(); + + Geom::Point origin; + + bool cursor_addnode; + + bool node_added; + + Geom::Point mousepoint_doc; // stores mousepoint when over_line in doc coords + + sigc::connection *selcon; + sigc::connection *subselcon; + + static const std::string prefsPath; + + virtual void setup(); + virtual bool root_handler(GdkEvent* event); + + virtual const std::string& getPrefsPath(); + +private: + void selection_changed(Inkscape::Selection* sel); +}; + +void sp_mesh_context_select_next(ToolBase *event_context); +void sp_mesh_context_select_prev(ToolBase *event_context); + +} +} +} + +#endif // SEEN_SP_MESH_CONTEXT_H + + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/ui/tools/node-tool.cpp b/src/ui/tools/node-tool.cpp new file mode 100644 index 000000000..8a950b528 --- /dev/null +++ b/src/ui/tools/node-tool.cpp @@ -0,0 +1,705 @@ +/** + * @file + * New node tool - implementation. + */ +/* Authors: + * Krzysztof KosiÅ„ski + * Abhishek Sharma + * + * Copyright (C) 2009 Authors + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include "ui/tool/curve-drag-point.h" +#include +#include "desktop.h" +#include "desktop-handles.h" +#include "display/sp-canvas-group.h" +#include "display/canvas-bpath.h" +#include "display/curve.h" +#include "display/sp-canvas.h" +#include "document.h" +#include "live_effects/lpeobject.h" +#include "message-context.h" +#include "selection.h" +#include "shape-editor.h" // temporary! +#include "sp-clippath.h" +#include "sp-item-group.h" +#include "sp-mask.h" +#include "sp-object-group.h" +#include "sp-path.h" +#include "sp-text.h" +#include "ui/control-manager.h" +#include "ui/tools/node-tool.h" +#include "ui/tool/control-point-selection.h" +#include "ui/tool/event-utils.h" +#include "ui/tool/manipulator.h" +#include "ui/tool/multi-path-manipulator.h" +#include "ui/tool/path-manipulator.h" +#include "ui/tool/selector.h" +#include "ui/tool/shape-record.h" + +#include "pixmaps/cursor-node.xpm" +#include "pixmaps/cursor-node-d.xpm" +#include "selection-chemistry.h" + +#include + +/** @struct NodeTool + * + * Node tool event context. + * + * @par Architectural overview of the tool + * @par + * Here's a breakdown of what each object does. + * - Handle: shows a handle and keeps the node type constraint (smooth / symmetric) by updating + * the other handle's position when dragged. Its move() method cannot violate the constraints. + * - Node: keeps node type constraints for auto nodes and smooth nodes at ends of linear segments. + * Its move() method cannot violate constraints. Handles linear grow and dispatches spatial grow + * to MultiPathManipulator. Keeps a reference to its NodeList. + * - NodeList: exposes an iterator-based interface to nodes. It is possible to obtain an iterator + * to a node from the node. Keeps a reference to its SubpathList. + * - SubpathList: list of NodeLists that represents an editable pathvector. Keeps a reference + * to its PathManipulator. + * - PathManipulator: performs most of the single-path actions like reverse subpaths, + * delete segment, shift selection, etc. Keeps a reference to MultiPathManipulator. + * - MultiPathManipulator: performs additional operations for actions that are not per-path, + * for example node joins and segment joins. Tracks the control transforms for PMs that edit + * clipping paths and masks. It is more or less equivalent to ShapeEditor and in the future + * it might handle all shapes. Handles XML commit of actions that affect all paths or + * the node selection and removes PathManipulators that have no nodes left after e.g. node + * deletes. + * - ControlPointSelection: keeps track of node selection and a set of nodes that can potentially + * be selected. There can be more than one selection. Performs actions that require no + * knowledge about the path, only about the nodes, like dragging and transforms. It is not + * specific to nodes and can accomodate any control point derived from SelectableControlPoint. + * Transforms nodes in response to transform handle events. + * - TransformHandleSet: displays nodeset transform handles and emits transform events. The aim + * is to eventually use a common class for object and control point transforms. + * - SelectableControlPoint: base for any type of selectable point. It can belong to only one + * selection. + * + * @par Functionality that resides in weird places + * @par + * + * This list is probably incomplete. + * - Curve dragging: CurveDragPoint, controlled by PathManipulator + * - Single handle shortcuts: MultiPathManipulator::event(), ModifierTracker + * - Linear and spatial grow: Node, spatial grow routed to ControlPointSelection + * - Committing handle actions performed with the mouse: PathManipulator + * - Sculpting: ControlPointSelection + * + * @par Plans for the future + * @par + * - MultiPathManipulator should become a generic shape editor that manages all active manipulator, + * more or less like the old ShapeEditor. + * - Knotholder should be rewritten into one manipulator class per shape, using the control point + * classes. Interesting features like dragging rectangle sides could be added along the way. + * - Better handling of clip and mask editing, particularly in response to undo. + * - High level refactoring of the event context hierarchy. All aspects of tools, like toolbox + * controls, icons, event handling should be collected in one class, though each aspect + * of a tool might be in an separate class for better modularity. The long term goal is to allow + * tools to be defined in extensions or shared library plugins. + */ + +using Inkscape::ControlManager; + +#include "tool-factory.h" + +namespace Inkscape { +namespace UI { +namespace Tools { + +namespace { + ToolBase* createNodesContext() { + return new NodeTool(); + } + + bool nodesContextRegistered = ToolFactory::instance().registerObject("/tools/nodes", createNodesContext); +} + +const std::string& NodeTool::getPrefsPath() { + return NodeTool::prefsPath; +} + +const std::string NodeTool::prefsPath = "/tools/nodes"; + +SPCanvasGroup *create_control_group(SPDesktop *d); + +NodeTool::NodeTool() : ToolBase() { + this->show_handles = false; + this->single_node_transform_handles = false; + this->show_transform_handles = false; + this->cursor_drag = false; + this->live_objects = false; + this->edit_clipping_paths = false; + this->live_outline = false; + this->flashed_item = 0; + this->_transform_handle_group = 0; + this->show_path_direction = false; + this->_last_over = 0; + this->edit_masks = false; + this->show_outline = false; + this->flash_tempitem = 0; + + this->cursor_shape = cursor_node_xpm; + this->hot_x = 1; + this->hot_y = 1; + + this->_selected_nodes = 0; + this->_multipath = 0; + this->_selector = 0; + this->_path_data = 0; +} + +SPCanvasGroup *create_control_group(SPDesktop *d) +{ + return reinterpret_cast(sp_canvas_item_new( + sp_desktop_controls(d), SP_TYPE_CANVAS_GROUP, NULL)); +} + +void destroy_group(SPCanvasGroup *g) +{ + sp_canvas_item_destroy(SP_CANVAS_ITEM(g)); +} + +NodeTool::~NodeTool() { + this->enableGrDrag(false); + + if (this->flash_tempitem) { + this->desktop->remove_temporary_canvasitem(this->flash_tempitem); + } + + this->_selection_changed_connection.disconnect(); + //this->_selection_modified_connection.disconnect(); + this->_mouseover_changed_connection.disconnect(); + this->_sizeUpdatedConn.disconnect(); + + delete this->_multipath; + delete this->_selected_nodes; + delete this->_selector; + + Inkscape::UI::PathSharedData &data = *this->_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(this->_transform_handle_group); +} + +void NodeTool::setup() { + ToolBase::setup(); + + this->_path_data = new Inkscape::UI::PathSharedData(); + + Inkscape::UI::PathSharedData &data = *this->_path_data; + data.node_data.desktop = this->desktop; + + // selector has to be created here, so that its hidden control point is on the bottom + this->_selector = new Inkscape::UI::Selector(this->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(this->desktop); + data.node_data.handle_line_group = create_control_group(this->desktop); + data.dragpoint_group = create_control_group(this->desktop); + this->_transform_handle_group = create_control_group(this->desktop); + data.node_data.node_group = create_control_group(this->desktop); + data.node_data.handle_group = create_control_group(this->desktop); + + Inkscape::Selection *selection = sp_desktop_selection (this->desktop); + + this->_selection_changed_connection.disconnect(); + this->_selection_changed_connection = + selection->connectChanged(sigc::mem_fun(this, &NodeTool::selection_changed)); + + this->_mouseover_changed_connection.disconnect(); + this->_mouseover_changed_connection = + Inkscape::UI::ControlPoint::signal_mouseover_change.connect(sigc::mem_fun(this, &NodeTool::mouseover_changed)); + + this->_sizeUpdatedConn = ControlManager::getManager().connectCtrlSizeChanged( + sigc::mem_fun(this, &NodeTool::handleControlUiStyleChange) + ); + + this->_selected_nodes = new Inkscape::UI::ControlPointSelection(this->desktop, this->_transform_handle_group); + + data.node_data.selection = this->_selected_nodes; + + this->_multipath = new Inkscape::UI::MultiPathManipulator(data, this->_selection_changed_connection); + + this->_selector->signal_point.connect(sigc::mem_fun(this, &NodeTool::select_point)); + this->_selector->signal_area.connect(sigc::mem_fun(this, &NodeTool::select_area)); + + this->_multipath->signal_coords_changed.connect( + sigc::bind( + sigc::mem_fun(*this->desktop, &SPDesktop::emitToolSubselectionChanged), + (void*)NULL + ) + ); + + this->_selected_nodes->signal_point_changed.connect( + // Hide both signal parameters and bind the function parameter to 0 + // sigc::signal + // <=> + // void update_tip(GdkEvent *event) + sigc::hide(sigc::hide(sigc::bind( + sigc::mem_fun(this, &NodeTool::update_tip), + (GdkEvent*)NULL + ))) + ); + + this->cursor_drag = false; + this->show_transform_handles = true; + this->single_node_transform_handles = false; + this->flash_tempitem = NULL; + this->flashed_item = NULL; + this->_last_over = NULL; + + // read prefs before adding items to selection to prevent momentarily showing the outline + sp_event_context_read(this, "show_handles"); + sp_event_context_read(this, "show_outline"); + sp_event_context_read(this, "live_outline"); + sp_event_context_read(this, "live_objects"); + sp_event_context_read(this, "show_path_direction"); + sp_event_context_read(this, "show_transform_handles"); + sp_event_context_read(this, "single_node_transform_handles"); + sp_event_context_read(this, "edit_clipping_paths"); + sp_event_context_read(this, "edit_masks"); + + this->selection_changed(selection); + this->update_tip(NULL); + + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + + if (prefs->getBool("/tools/nodes/selcue")) { + this->enableSelectionCue(); + } + + if (prefs->getBool("/tools/nodes/gradientdrag")) { + this->enableGrDrag(); + } + + this->desktop->emitToolSubselectionChanged(NULL); // sets the coord entry fields to inactive +} + +void NodeTool::set(const Inkscape::Preferences::Entry& value) { + Glib::ustring entry_name = value.getEntryName(); + + if (entry_name == "show_handles") { + this->show_handles = value.getBool(true); + this->_multipath->showHandles(this->show_handles); + } else if (entry_name == "show_outline") { + this->show_outline = value.getBool(); + this->_multipath->showOutline(this->show_outline); + } else if (entry_name == "live_outline") { + this->live_outline = value.getBool(); + this->_multipath->setLiveOutline(this->live_outline); + } else if (entry_name == "live_objects") { + this->live_objects = value.getBool(); + this->_multipath->setLiveObjects(this->live_objects); + } else if (entry_name == "show_path_direction") { + this->show_path_direction = value.getBool(); + this->_multipath->showPathDirection(this->show_path_direction); + } else if (entry_name == "show_transform_handles") { + this->show_transform_handles = value.getBool(true); + this->_selected_nodes->showTransformHandles( + this->show_transform_handles, this->single_node_transform_handles); + } else if (entry_name == "single_node_transform_handles") { + this->single_node_transform_handles = value.getBool(); + this->_selected_nodes->showTransformHandles( + this->show_transform_handles, this->single_node_transform_handles); + } else if (entry_name == "edit_clipping_paths") { + this->edit_clipping_paths = value.getBool(); + this->selection_changed(this->desktop->selection); + } else if (entry_name == "edit_masks") { + this->edit_masks = value.getBool(); + this->selection_changed(this->desktop->selection); + } else { + ToolBase::set(value); + } +} + +/** Recursively collect ShapeRecords */ +void gather_items(NodeTool *nt, SPItem *base, SPObject *obj, Inkscape::UI::ShapeRole role, + std::set &s) +{ + using namespace Inkscape::UI; + + if (!obj) { + return; + } + + //XML Tree being used directly here while it shouldn't be. + if (SP_IS_PATH(obj) && obj->getRepr()->attribute("inkscape:original-d") != NULL) { + ShapeRecord r; + r.item = static_cast(obj); + r.edit_transform = Geom::identity(); // TODO wrong? + r.role = role; + s.insert(r); + } else if (role != SHAPE_ROLE_NORMAL && (SP_IS_GROUP(obj) || SP_IS_OBJECTGROUP(obj))) { + for (SPObject *c = obj->children; c; c = c->next) { + gather_items(nt, base, c, role, s); + } + } else if (SP_IS_ITEM(obj)) { + SPItem *item = static_cast(obj); + ShapeRecord r; + r.item = item; + // TODO add support for objectBoundingBox + r.edit_transform = base ? base->i2doc_affine() : Geom::identity(); + r.role = role; + + if (s.insert(r).second) { + // this item was encountered the first time + if (nt->edit_clipping_paths && item->clip_ref) { + gather_items(nt, item, item->clip_ref->getObject(), SHAPE_ROLE_CLIPPING_PATH, s); + } + + if (nt->edit_masks && item->mask_ref) { + gather_items(nt, item, item->mask_ref->getObject(), SHAPE_ROLE_MASK, s); + } + } + } +} + +void NodeTool::selection_changed(Inkscape::Selection *sel) { + using namespace Inkscape::UI; + + std::set shapes; + + GSList const *ilist = sel->itemList(); + + for (GSList *i = const_cast(ilist); i; i = i->next) { + SPObject *obj = static_cast(i->data); + + if (SP_IS_ITEM(obj)) { + gather_items(this, NULL, static_cast(obj), SHAPE_ROLE_NORMAL, shapes); + } + } + + // use multiple ShapeEditors for now, to allow editing many shapes at once + // needs to be rethought + for (boost::ptr_map::iterator i = this->_shape_editors.begin(); + i != this->_shape_editors.end(); ) + { + ShapeRecord s; + s.item = i->first; + + if (shapes.find(s) == shapes.end()) { + this->_shape_editors.erase(i++); + } else { + ++i; + } + } + + for (std::set::iterator i = shapes.begin(); i != shapes.end(); ++i) { + ShapeRecord const &r = *i; + + if ((SP_IS_SHAPE(r.item) || SP_IS_TEXT(r.item)) && + this->_shape_editors.find(r.item) == this->_shape_editors.end()) + { + ShapeEditor *si = new ShapeEditor(this->desktop); + si->set_item(r.item, SH_KNOTHOLDER); + this->_shape_editors.insert(const_cast(r.item), si); + } + } + + this->_multipath->setItems(shapes); + this->update_tip(NULL); + this->desktop->updateNow(); +} + +bool NodeTool::root_handler(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 + + Inkscape::Selection *selection = desktop->selection; + static Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + + if (this->_multipath->event(this, event)) { + return true; + } + + if (this->_selector->event(this, event)) { + return true; + } + + if (this->_selected_nodes->event(this, event)) { + return true; + } + + switch (event->type) + { + case GDK_MOTION_NOTIFY: { + combine_motion_events(desktop->canvas, event->motion, 0); + SPItem *over_item = sp_event_context_find_item (desktop, event_point(event->button), + FALSE, TRUE); + + if (over_item != this->_last_over) { + this->_last_over = over_item; + //ink_node_tool_update_tip(nt, event); + this->update_tip(event); + } + + // create pathflash outline + if (prefs->getBool("/tools/nodes/pathflash_enabled")) { + if (over_item == this->flashed_item) { + break; + } + + if (!prefs->getBool("/tools/nodes/pathflash_selected") && selection->includes(over_item)) { + break; + } + + if (this->flash_tempitem) { + desktop->remove_temporary_canvasitem(this->flash_tempitem); + this->flash_tempitem = NULL; + this->flashed_item = NULL; + } + + if (!SP_IS_SHAPE(over_item)) { + break; // for now, handle only shapes + } + + this->flashed_item = over_item; + SPCurve *c = SP_SHAPE(over_item)->getCurveBeforeLPE(); + + if (!c) { + break; // break out when curve doesn't exist + } + + c->transform(over_item->i2dt_affine()); + 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); + + this->flash_tempitem = desktop->add_temporary_canvasitem(flash, + prefs->getInt("/tools/nodes/pathflash_timeout", 500)); + + c->unref(); + } + } break; // do not return true, because we need to pass this event to the parent context + // otherwise some features cease to work + + case GDK_KEY_PRESS: + switch (get_group0_keyval(&event->key)) + { + case GDK_KEY_Escape: // deselect everything + if (this->_selected_nodes->empty()) { + Inkscape::SelectionHelper::selectNone(desktop); + } else { + this->_selected_nodes->clear(); + } + //ink_node_tool_update_tip(nt, event); + this->update_tip(event); + return TRUE; + + case GDK_KEY_a: + case GDK_KEY_A: + if (held_control(event->key) && held_alt(event->key)) { + this->_selected_nodes->selectAll(); + // Ctrl+A is handled in selection-chemistry.cpp via verb + //ink_node_tool_update_tip(nt, event); + this->update_tip(event); + return TRUE; + } + break; + + case GDK_KEY_h: + case GDK_KEY_H: + if (held_only_control(event->key)) { + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + prefs->setBool("/tools/nodes/show_handles", !this->show_handles); + return TRUE; + } + break; + + default: + break; + } + //ink_node_tool_update_tip(nt, event); + this->update_tip(event); + break; + + case GDK_KEY_RELEASE: + //ink_node_tool_update_tip(nt, event); + this->update_tip(event); + break; + + default: + break; + } + + return ToolBase::root_handler(event); +} + +void NodeTool::update_tip(GdkEvent *event) { + using namespace Inkscape::UI; + + if (event && (event->type == GDK_KEY_PRESS || event->type == GDK_KEY_RELEASE)) { + unsigned new_state = state_after_event(event); + + if (new_state == event->key.state) { + return; + } + + if (state_held_shift(new_state)) { + if (this->_last_over) { + this->message_context->set(Inkscape::NORMAL_MESSAGE, + C_("Node tool tip", "Shift: drag to add nodes to the selection, " + "click to toggle object selection")); + } else { + this->message_context->set(Inkscape::NORMAL_MESSAGE, + C_("Node tool tip", "Shift: drag to add nodes to the selection")); + } + + return; + } + } + + unsigned sz = this->_selected_nodes->size(); + unsigned total = this->_selected_nodes->allPoints().size(); + + if (sz != 0) { + char *nodestring = g_strdup_printf( + ngettext("%u of %u node selected.", "%u of %u nodes selected.", total), + sz, total); + + if (this->_last_over) { + // TRANSLATORS: The %s below is where the "%u of %u nodes selected" sentence gets put + char *dyntip = g_strdup_printf(C_("Node tool tip", + "%s Drag to select nodes, click to edit only this object (more: Shift)"), + nodestring); + this->message_context->set(Inkscape::NORMAL_MESSAGE, dyntip); + g_free(dyntip); + } else { + char *dyntip = g_strdup_printf(C_("Node tool tip", + "%s Drag to select nodes, click clear the selection"), + nodestring); + this->message_context->set(Inkscape::NORMAL_MESSAGE, dyntip); + g_free(dyntip); + } + g_free(nodestring); + } else if (!this->_multipath->empty()) { + if (this->_last_over) { + this->message_context->set(Inkscape::NORMAL_MESSAGE, C_("Node tool tip", + "Drag to select nodes, click to edit only this object")); + } else { + this->message_context->set(Inkscape::NORMAL_MESSAGE, C_("Node tool tip", + "Drag to select nodes, click to clear the selection")); + } + } else { + if (this->_last_over) { + this->message_context->set(Inkscape::NORMAL_MESSAGE, C_("Node tool tip", + "Drag to select objects to edit, click to edit this object (more: Shift)")); + } else { + this->message_context->set(Inkscape::NORMAL_MESSAGE, C_("Node tool tip", + "Drag to select objects to edit")); + } + } +} + +void NodeTool::select_area(Geom::Rect const &sel, GdkEventButton *event) { + using namespace Inkscape::UI; + + if (this->_multipath->empty()) { + // if multipath is empty, select rubberbanded items rather than nodes + Inkscape::Selection *selection = this->desktop->selection; + GSList *items = sp_desktop_document(this->desktop)->getItemsInBox(this->desktop->dkey, sel); + selection->setList(items); + g_slist_free(items); + } else { + if (!held_shift(*event)) { + this->_selected_nodes->clear(); + } + + this->_selected_nodes->selectArea(sel); + } +} + +void NodeTool::select_point(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 = this->desktop->selection; + + SPItem *item_clicked = sp_event_context_find_item (this->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 there are nodes selected, the first click should deselect the nodes + // and the second should deselect the items + if (!state_held_shift(event->state)) { + if (this->_selected_nodes->empty()) { + selection->clear(); + } else { + this->_selected_nodes->clear(); + } + } + } else { + if (held_shift(*event)) { + selection->toggle(item_clicked); + } else { + selection->set(item_clicked); + } + + this->desktop->updateNow(); + } +} + +void NodeTool::mouseover_changed(Inkscape::UI::ControlPoint *p) { + using Inkscape::UI::CurveDragPoint; + + CurveDragPoint *cdp = dynamic_cast(p); + + if (cdp && !this->cursor_drag) { + this->cursor_shape = cursor_node_d_xpm; + this->hot_x = 1; + this->hot_y = 1; + this->sp_event_context_update_cursor(); + this->cursor_drag = true; + } else if (!cdp && this->cursor_drag) { + this->cursor_shape = cursor_node_xpm; + this->hot_x = 1; + this->hot_y = 1; + this->sp_event_context_update_cursor(); + this->cursor_drag = false; + } +} + +void NodeTool::handleControlUiStyleChange() { + this->_multipath->updateHandles(); +} + +} +} +} + +//} // 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:fileencoding=utf-8:textwidth=99 : diff --git a/src/ui/tools/node-tool.h b/src/ui/tools/node-tool.h new file mode 100644 index 000000000..4d15ab70e --- /dev/null +++ b/src/ui/tools/node-tool.h @@ -0,0 +1,105 @@ +/** @file + * @brief New node tool with support for multiple path editing + */ +/* Authors: + * Krzysztof KosiÅ„ski + * + * 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 +#include +#include "ui/tools/tool-base.h" + +namespace Inkscape { + namespace Display { + class TemporaryItem; + } + + namespace UI { + class MultiPathManipulator; + class ControlPointSelection; + class Selector; + class ControlPoint; + + struct PathSharedData; + } +} + +#define INK_NODE_TOOL(obj) (dynamic_cast((Inkscape::UI::Tools::ToolBase*)obj)) +#define INK_IS_NODE_TOOL(obj) (dynamic_cast((const Inkscape::UI::Tools::ToolBase*)obj)) + +namespace Inkscape { +namespace UI { +namespace Tools { + +class NodeTool : public ToolBase { +public: + NodeTool(); + virtual ~NodeTool(); + + Inkscape::UI::ControlPointSelection* _selected_nodes; + Inkscape::UI::MultiPathManipulator* _multipath; + + bool edit_clipping_paths; + bool edit_masks; + + static const std::string prefsPath; + + virtual void setup(); + virtual void set(const Inkscape::Preferences::Entry& val); + virtual bool root_handler(GdkEvent* event); + + virtual const std::string& getPrefsPath(); + +private: + sigc::connection _selection_changed_connection; + sigc::connection _mouseover_changed_connection; + sigc::connection _sizeUpdatedConn; + + SPItem *flashed_item; + Inkscape::Display::TemporaryItem *flash_tempitem; + Inkscape::UI::Selector* _selector; + Inkscape::UI::PathSharedData* _path_data; + SPCanvasGroup *_transform_handle_group; + SPItem *_last_over; + boost::ptr_map _shape_editors; + + bool cursor_drag; + bool show_handles; + bool show_outline; + bool live_outline; + bool live_objects; + bool show_path_direction; + bool show_transform_handles; + bool single_node_transform_handles; + + void selection_changed(Inkscape::Selection *sel); + + void select_area(Geom::Rect const &sel, GdkEventButton *event); + void select_point(Geom::Point const &sel, GdkEventButton *event); + void mouseover_changed(Inkscape::UI::ControlPoint *p); + void update_tip(GdkEvent *event); + void handleControlUiStyleChange(); +}; + +} +} +} + +#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:fileencoding=utf-8:textwidth=99 : diff --git a/src/ui/tools/pen-tool.cpp b/src/ui/tools/pen-tool.cpp new file mode 100644 index 000000000..4c573a244 --- /dev/null +++ b/src/ui/tools/pen-tool.cpp @@ -0,0 +1,1404 @@ +/** \file + * Pen event context implementation. + */ + +/* + * Authors: + * Lauris Kaplinski + * bulia byak + * Jon A. Cruz + * + * Copyright (C) 2000 Lauris Kaplinski + * Copyright (C) 2000-2001 Ximian, Inc. + * Copyright (C) 2002 Lauris Kaplinski + * Copyright (C) 2004 Monash University + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include +#include +#include + +#include "ui/tools/pen-tool.h" +#include "sp-namedview.h" +#include "desktop.h" +#include "desktop-handles.h" +#include "selection.h" +#include "selection-chemistry.h" +#include "draw-anchor.h" +#include "message-stack.h" +#include "message-context.h" +#include "preferences.h" +#include "sp-path.h" +#include "display/sp-canvas.h" +#include "display/curve.h" +#include "pixmaps/cursor-pen.xpm" +#include "display/canvas-bpath.h" +#include "display/sp-ctrlline.h" +#include "display/sodipodi-ctrl.h" +#include +#include "macros.h" +#include "context-fns.h" +#include "tools-switch.h" +#include "ui/control-manager.h" +#include "tool-factory.h" + +using Inkscape::ControlManager; + +namespace Inkscape { +namespace UI { +namespace Tools { + +static void spdc_pen_set_initial_point(PenTool *pc, Geom::Point const p); +static void spdc_pen_set_subsequent_point(PenTool *const pc, Geom::Point const p, bool statusbar, guint status = 0); +static void spdc_pen_set_ctrl(PenTool *pc, Geom::Point const p, guint state); +static void spdc_pen_finish_segment(PenTool *pc, Geom::Point p, guint state); + +static void spdc_pen_finish(PenTool *pc, gboolean closed); + +static gint pen_handle_button_press(PenTool *const pc, GdkEventButton const &bevent); +static gint pen_handle_motion_notify(PenTool *const pc, GdkEventMotion const &mevent); +static gint pen_handle_button_release(PenTool *const pc, GdkEventButton const &revent); +static gint pen_handle_2button_press(PenTool *const pc, GdkEventButton const &bevent); +static gint pen_handle_key_press(PenTool *const pc, GdkEvent *event); +static void spdc_reset_colors(PenTool *pc); + +static void pen_disable_events(PenTool *const pc); +static void pen_enable_events(PenTool *const pc); + +static Geom::Point pen_drag_origin_w(0, 0); +static bool pen_within_tolerance = false; + +static int pen_next_paraxial_direction(const PenTool *const pc, Geom::Point const &pt, Geom::Point const &origin, guint state); +static void pen_set_to_nearest_horiz_vert(const PenTool *const pc, Geom::Point &pt, guint const state, bool snap); + +static int pen_last_paraxial_dir = 0; // last used direction in horizontal/vertical mode; 0 = horizontal, 1 = vertical + +namespace { + ToolBase* createPenContext() { + return new PenTool(); + } + + bool penContextRegistered = ToolFactory::instance().registerObject("/tools/freehand/pen", createPenContext); +} + +const std::string& PenTool::getPrefsPath() { + return PenTool::prefsPath; +} + +const std::string PenTool::prefsPath = "/tools/freehand/pen"; + +PenTool::PenTool() : FreehandBase() { + this->polylines_only = false; + this->polylines_paraxial = false; + this->expecting_clicks_for_LPE = 0; + + this->cursor_shape = cursor_pen_xpm; + this->hot_x = 4; + this->hot_y = 4; + + this->npoints = 0; + this->mode = MODE_CLICK; + this->state = POINT; + + this->c0 = NULL; + this->c1 = NULL; + this->cl0 = NULL; + this->cl1 = NULL; + + this->events_disabled = 0; + + this->num_clicks = 0; + this->waiting_LPE = NULL; + this->waiting_item = NULL; +} + +PenTool::~PenTool() { + if (this->c0) { + sp_canvas_item_destroy(this->c0); + this->c0 = NULL; + } + if (this->c1) { + sp_canvas_item_destroy(this->c1); + this->c1 = NULL; + } + if (this->cl0) { + sp_canvas_item_destroy(this->cl0); + this->cl0 = NULL; + } + if (this->cl1) { + sp_canvas_item_destroy(this->cl1); + this->cl1 = NULL; + } + + if (this->expecting_clicks_for_LPE > 0) { + // we received too few clicks to sanely set the parameter path so we remove the LPE from the item + this->waiting_item->removeCurrentPathEffect(false); + } +} + +void sp_pen_context_set_polyline_mode(PenTool *const pc) { + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + guint mode = prefs->getInt("/tools/freehand/pen/freehand-mode", 0); + pc->polylines_only = (mode == 2 || mode == 3); + pc->polylines_paraxial = (mode == 3); +} + +/** + * Callback to initialize PenTool object. + */ +void PenTool::setup() { + FreehandBase::setup(); + + ControlManager &mgr = ControlManager::getManager(); + + // Pen indicators + this->c0 = mgr.createControl(sp_desktop_controls(SP_EVENT_CONTEXT_DESKTOP(this)), Inkscape::CTRL_TYPE_ADJ_HANDLE); + mgr.track(this->c0); + + this->c1 = mgr.createControl(sp_desktop_controls(SP_EVENT_CONTEXT_DESKTOP(this)), Inkscape::CTRL_TYPE_ADJ_HANDLE); + mgr.track(this->c1); + + this->cl0 = mgr.createControlLine(sp_desktop_controls(SP_EVENT_CONTEXT_DESKTOP(this))); + this->cl1 = mgr.createControlLine(sp_desktop_controls(SP_EVENT_CONTEXT_DESKTOP(this))); + + sp_canvas_item_hide(this->c0); + sp_canvas_item_hide(this->c1); + sp_canvas_item_hide(this->cl0); + sp_canvas_item_hide(this->cl1); + + sp_event_context_read(this, "mode"); + + this->anchor_statusbar = false; + + sp_pen_context_set_polyline_mode(this); + + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + if (prefs->getBool("/tools/freehand/pen/selcue")) { + this->enableSelectionCue(); + } +} + +static void pen_cancel (PenTool *const pc) +{ + pc->num_clicks = 0; + pc->state = PenTool::STOP; + spdc_reset_colors(pc); + sp_canvas_item_hide(pc->c0); + sp_canvas_item_hide(pc->c1); + sp_canvas_item_hide(pc->cl0); + sp_canvas_item_hide(pc->cl1); + pc->message_context->clear(); + pc->message_context->flash(Inkscape::NORMAL_MESSAGE, _("Drawing cancelled")); + + pc->desktop->canvas->endForcedFullRedraws(); +} + +/** + * Finalization callback. + */ +void PenTool::finish() { + sp_event_context_discard_delayed_snap_event(this); + + if (this->npoints != 0) { + pen_cancel(this); + } + + FreehandBase::finish(); +} + +/** + * Callback that sets key to value in pen context. + */ +void PenTool::set(const Inkscape::Preferences::Entry& val) { + Glib::ustring name = val.getEntryName(); + + if (name == "mode") { + if ( val.getString() == "drag" ) { + this->mode = MODE_DRAG; + } else { + this->mode = MODE_CLICK; + } + } +} + +/** + * Snaps new node relative to the previous node. + */ +static void spdc_endpoint_snap(PenTool const *const pc, Geom::Point &p, guint const state) +{ + if ((state & GDK_CONTROL_MASK) && !pc->polylines_paraxial) { //CTRL enables angular snapping + if (pc->npoints > 0) { + spdc_endpoint_snap_rotation(pc, p, pc->p[0], state); + } + } else { + // We cannot use shift here to disable snapping because the shift-key is already used + // to toggle the paraxial direction; if the user wants to disable snapping (s)he will + // have to use the %-key, the menu, or the snap toolbar + if ((pc->npoints > 0) && pc->polylines_paraxial) { + // snap constrained + pen_set_to_nearest_horiz_vert(pc, p, state, true); + } else { + // snap freely + boost::optional origin = pc->npoints > 0 ? pc->p[0] : boost::optional(); + spdc_endpoint_snap_free(pc, p, origin, state); // pass the origin, to allow for perpendicular / tangential snapping + } + } +} + +/** + * Snaps new node's handle relative to the new node. + */ +static void spdc_endpoint_snap_handle(PenTool const *const pc, Geom::Point &p, guint const state) +{ + g_return_if_fail(( pc->npoints == 2 || + pc->npoints == 5 )); + + if ((state & GDK_CONTROL_MASK)) { //CTRL enables angular snapping + spdc_endpoint_snap_rotation(pc, p, pc->p[pc->npoints - 2], state); + } else { + if (!(state & GDK_SHIFT_MASK)) { //SHIFT disables all snapping, except the angular snapping above + boost::optional origin = pc->p[pc->npoints - 2]; + spdc_endpoint_snap_free(pc, p, origin, state); + } + } +} + +bool PenTool::item_handler(SPItem* item, GdkEvent* event) { + gint ret = FALSE; + + switch (event->type) { + case GDK_BUTTON_PRESS: + ret = pen_handle_button_press(this, event->button); + break; + case GDK_BUTTON_RELEASE: + ret = pen_handle_button_release(this, event->button); + break; + default: + break; + } + + if (!ret) { + ret = FreehandBase::item_handler(item, event); + } + + return ret; +} + +/** + * Callback to handle all pen events. + */ +bool PenTool::root_handler(GdkEvent* event) { + gint ret = FALSE; + + switch (event->type) { + case GDK_BUTTON_PRESS: + ret = pen_handle_button_press(this, event->button); + break; + + case GDK_MOTION_NOTIFY: + ret = pen_handle_motion_notify(this, event->motion); + break; + + case GDK_BUTTON_RELEASE: + ret = pen_handle_button_release(this, event->button); + break; + + case GDK_2BUTTON_PRESS: + ret = pen_handle_2button_press(this, event->button); + break; + + case GDK_KEY_PRESS: + ret = pen_handle_key_press(this, event); + break; + + default: + break; + } + + if (!ret) { + ret = FreehandBase::root_handler(event); + } + + return ret; +} + +/** + * Handle mouse button press event. + */ +static gint pen_handle_button_press(PenTool *const pc, GdkEventButton const &bevent) +{ + if (pc->events_disabled) { + // skip event processing if events are disabled + return FALSE; + } + + FreehandBase * const dc = SP_DRAW_CONTEXT(pc); + SPDesktop * const desktop = SP_EVENT_CONTEXT_DESKTOP(dc); + Geom::Point const event_w(bevent.x, bevent.y); + Geom::Point event_dt(desktop->w2d(event_w)); + ToolBase *event_context = SP_EVENT_CONTEXT(pc); + + gint ret = FALSE; + if (bevent.button == 1 && !event_context->space_panning + // make sure this is not the last click for a waiting LPE (otherwise we want to finish the path) + && pc->expecting_clicks_for_LPE != 1) { + + if (Inkscape::have_viable_layer(desktop, dc->message_context) == false) { + return TRUE; + } + + if (!pc->grab ) { + // Grab mouse, so release will not pass unnoticed + pc->grab = SP_CANVAS_ITEM(desktop->acetate); + sp_canvas_item_grab(pc->grab, ( GDK_KEY_PRESS_MASK | GDK_BUTTON_PRESS_MASK | + GDK_BUTTON_RELEASE_MASK | + GDK_POINTER_MOTION_MASK ), + NULL, bevent.time); + } + + pen_drag_origin_w = event_w; + pen_within_tolerance = true; + + // Test whether we hit any anchor. + SPDrawAnchor * const anchor = spdc_test_inside(pc, event_w); + + switch (pc->mode) { + case PenTool::MODE_CLICK: + // In click mode we add point on release + switch (pc->state) { + case PenTool::POINT: + case PenTool::CONTROL: + case PenTool::CLOSE: + break; + case PenTool::STOP: + // This is allowed, if we just canceled curve + pc->state = PenTool::POINT; + break; + default: + break; + } + break; + case PenTool::MODE_DRAG: + switch (pc->state) { + case PenTool::STOP: + // This is allowed, if we just canceled curve + case PenTool::POINT: + if (pc->npoints == 0) { + + Geom::Point p; + if ((bevent.state & GDK_CONTROL_MASK) && (pc->polylines_only || pc->polylines_paraxial)) { + p = event_dt; + if (!(bevent.state & GDK_SHIFT_MASK)) { + SnapManager &m = desktop->namedview->snap_manager; + m.setup(desktop); + m.freeSnapReturnByRef(p, Inkscape::SNAPSOURCE_NODE_HANDLE); + m.unSetup(); + } + spdc_create_single_dot(event_context, p, "/tools/freehand/pen", bevent.state); + ret = TRUE; + break; + } + + // TODO: Perhaps it would be nicer to rearrange the following case + // distinction so that the case of a waiting LPE is treated separately + + // Set start anchor + pc->sa = anchor; + if (anchor && !sp_pen_context_has_waiting_LPE(pc)) { + // Adjust point to anchor if needed; if we have a waiting LPE, we need + // a fresh path to be created so don't continue an existing one + p = anchor->dp; + desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("Continuing selected path")); + } else { + // This is the first click of a new curve; deselect item so that + // this curve is not combined with it (unless it is drawn from its + // anchor, which is handled by the sibling branch above) + Inkscape::Selection * const selection = sp_desktop_selection(desktop); + if (!(bevent.state & GDK_SHIFT_MASK) || sp_pen_context_has_waiting_LPE(pc)) { + // if we have a waiting LPE, we need a fresh path to be created + // so don't append to an existing one + selection->clear(); + desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("Creating new path")); + } else if (selection->singleItem() && SP_IS_PATH(selection->singleItem())) { + desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("Appending to selected path")); + } + + // Create green anchor + p = event_dt; + spdc_endpoint_snap(pc, p, bevent.state); + pc->green_anchor = sp_draw_anchor_new(pc, pc->green_curve, TRUE, p); + } + spdc_pen_set_initial_point(pc, p); + } else { + + // Set end anchor + pc->ea = anchor; + Geom::Point p; + if (anchor) { + p = anchor->dp; + // we hit an anchor, will finish the curve (either with or without closing) + // in release handler + pc->state = PenTool::CLOSE; + + if (pc->green_anchor && pc->green_anchor->active) { + // we clicked on the current curve start, so close it even if + // we drag a handle away from it + dc->green_closed = TRUE; + } + ret = TRUE; + break; + + } else { + p = event_dt; + spdc_endpoint_snap(pc, p, bevent.state); // Snap node only if not hitting anchor. + spdc_pen_set_subsequent_point(pc, p, true); + } + } + + pc->state = pc->polylines_only ? PenTool::POINT : PenTool::CONTROL; + ret = TRUE; + break; + case PenTool::CONTROL: + g_warning("Button down in CONTROL state"); + break; + case PenTool::CLOSE: + g_warning("Button down in CLOSE state"); + break; + default: + break; + } + break; + default: + break; + } + } else if (pc->expecting_clicks_for_LPE == 1 && pc->npoints != 0) { + // when the last click for a waiting LPE occurs we want to finish the path + spdc_pen_finish_segment(pc, event_dt, bevent.state); + if (pc->green_closed) { + // finishing at the start anchor, close curve + spdc_pen_finish(pc, TRUE); + } else { + // finishing at some other anchor, finish curve but not close + spdc_pen_finish(pc, FALSE); + } + + ret = TRUE; + } else if (bevent.button == 3 && pc->npoints != 0) { + // right click - finish path + spdc_pen_finish(pc, FALSE); + ret = TRUE; + } + + if (pc->expecting_clicks_for_LPE > 0) { + --pc->expecting_clicks_for_LPE; + } + + return ret; +} + +/** + * Handle motion_notify event. + */ +static gint pen_handle_motion_notify(PenTool *const pc, GdkEventMotion const &mevent) +{ + gint ret = FALSE; + + ToolBase *event_context = SP_EVENT_CONTEXT(pc); + SPDesktop * const dt = SP_EVENT_CONTEXT_DESKTOP(event_context); + + if (event_context->space_panning || mevent.state & GDK_BUTTON2_MASK || mevent.state & GDK_BUTTON3_MASK) { + // allow scrolling + return FALSE; + } + + if (pc->events_disabled) { + // skip motion events if pen events are disabled + return FALSE; + } + + Geom::Point const event_w(mevent.x, + mevent.y); + if (pen_within_tolerance) { + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + gint const tolerance = prefs->getIntLimited("/options/dragtolerance/value", 0, 0, 100); + if ( Geom::LInfty( event_w - pen_drag_origin_w ) < tolerance ) { + return FALSE; // Do not drag if we're within tolerance from origin. + } + } + // Once the user has moved farther than tolerance from the original location + // (indicating they intend to move the object, not click), then always process the + // motion notify coordinates as given (no snapping back to origin) + pen_within_tolerance = false; + + // Find desktop coordinates + Geom::Point p = dt->w2d(event_w); + + // Test, whether we hit any anchor + SPDrawAnchor *anchor = spdc_test_inside(pc, event_w); + + switch (pc->mode) { + case PenTool::MODE_CLICK: + switch (pc->state) { + case PenTool::POINT: + if ( pc->npoints != 0 ) { + // Only set point, if we are already appending + spdc_endpoint_snap(pc, p, mevent.state); + spdc_pen_set_subsequent_point(pc, p, true); + ret = TRUE; + } else if (!sp_event_context_knot_mouseover(pc)) { + SnapManager &m = dt->namedview->snap_manager; + m.setup(dt); + m.preSnap(Inkscape::SnapCandidatePoint(p, Inkscape::SNAPSOURCE_NODE_HANDLE)); + m.unSetup(); + } + break; + case PenTool::CONTROL: + case PenTool::CLOSE: + // Placing controls is last operation in CLOSE state + spdc_endpoint_snap(pc, p, mevent.state); + spdc_pen_set_ctrl(pc, p, mevent.state); + ret = TRUE; + break; + case PenTool::STOP: + // This is perfectly valid + break; + default: + break; + } + break; + case PenTool::MODE_DRAG: + switch (pc->state) { + case PenTool::POINT: + if ( pc->npoints > 0 ) { + // Only set point, if we are already appending + + if (!anchor) { // Snap node only if not hitting anchor + spdc_endpoint_snap(pc, p, mevent.state); + spdc_pen_set_subsequent_point(pc, p, true, mevent.state); + } else { + spdc_pen_set_subsequent_point(pc, anchor->dp, false, mevent.state); + } + + if (anchor && !pc->anchor_statusbar) { + pc->message_context->set(Inkscape::NORMAL_MESSAGE, _("Click or click and drag to close and finish the path.")); + pc->anchor_statusbar = true; + } else if (!anchor && pc->anchor_statusbar) { + pc->message_context->clear(); + pc->anchor_statusbar = false; + } + + ret = TRUE; + } else { + if (anchor && !pc->anchor_statusbar) { + pc->message_context->set(Inkscape::NORMAL_MESSAGE, _("Click or click and drag to continue the path from this point.")); + pc->anchor_statusbar = true; + } else if (!anchor && pc->anchor_statusbar) { + pc->message_context->clear(); + pc->anchor_statusbar = false; + } + if (!sp_event_context_knot_mouseover(pc)) { + SnapManager &m = dt->namedview->snap_manager; + m.setup(dt); + m.preSnap(Inkscape::SnapCandidatePoint(p, Inkscape::SNAPSOURCE_NODE_HANDLE)); + m.unSetup(); + } + } + break; + case PenTool::CONTROL: + case PenTool::CLOSE: + // Placing controls is last operation in CLOSE state + + // snap the handle + spdc_endpoint_snap_handle(pc, p, mevent.state); + + if (!pc->polylines_only) { + spdc_pen_set_ctrl(pc, p, mevent.state); + } else { + spdc_pen_set_ctrl(pc, pc->p[1], mevent.state); + } + gobble_motion_events(GDK_BUTTON1_MASK); + ret = TRUE; + break; + case PenTool::STOP: + // This is perfectly valid + break; + default: + if (!sp_event_context_knot_mouseover(pc)) { + SnapManager &m = dt->namedview->snap_manager; + m.setup(dt); + m.preSnap(Inkscape::SnapCandidatePoint(p, Inkscape::SNAPSOURCE_NODE_HANDLE)); + m.unSetup(); + } + break; + } + break; + default: + break; + } + return ret; +} + +/** + * Handle mouse button release event. + */ +static gint pen_handle_button_release(PenTool *const pc, GdkEventButton const &revent) +{ + if (pc->events_disabled) { + // skip event processing if events are disabled + return FALSE; + } + + gint ret = FALSE; + ToolBase *event_context = SP_EVENT_CONTEXT(pc); + if ( revent.button == 1 && !event_context->space_panning) { + + FreehandBase *dc = SP_DRAW_CONTEXT (pc); + + Geom::Point const event_w(revent.x, + revent.y); + // Find desktop coordinates + Geom::Point p = pc->desktop->w2d(event_w); + + // Test whether we hit any anchor. + SPDrawAnchor *anchor = spdc_test_inside(pc, event_w); + + switch (pc->mode) { + case PenTool::MODE_CLICK: + switch (pc->state) { + case PenTool::POINT: + if ( pc->npoints == 0 ) { + // Start new thread only with button release + if (anchor) { + p = anchor->dp; + } + pc->sa = anchor; + spdc_pen_set_initial_point(pc, p); + } else { + // Set end anchor here + pc->ea = anchor; + if (anchor) { + p = anchor->dp; + } + } + pc->state = PenTool::CONTROL; + ret = TRUE; + break; + case PenTool::CONTROL: + // End current segment + spdc_endpoint_snap(pc, p, revent.state); + spdc_pen_finish_segment(pc, p, revent.state); + pc->state = PenTool::POINT; + ret = TRUE; + break; + case PenTool::CLOSE: + // End current segment + if (!anchor) { // Snap node only if not hitting anchor + spdc_endpoint_snap(pc, p, revent.state); + } + spdc_pen_finish_segment(pc, p, revent.state); + spdc_pen_finish(pc, TRUE); + pc->state = PenTool::POINT; + ret = TRUE; + break; + case PenTool::STOP: + // This is allowed, if we just canceled curve + pc->state = PenTool::POINT; + ret = TRUE; + break; + default: + break; + } + break; + case PenTool::MODE_DRAG: + switch (pc->state) { + case PenTool::POINT: + case PenTool::CONTROL: + spdc_endpoint_snap(pc, p, revent.state); + spdc_pen_finish_segment(pc, p, revent.state); + break; + case PenTool::CLOSE: + spdc_endpoint_snap(pc, p, revent.state); + spdc_pen_finish_segment(pc, p, revent.state); + if (pc->green_closed) { + // finishing at the start anchor, close curve + spdc_pen_finish(pc, TRUE); + } else { + // finishing at some other anchor, finish curve but not close + spdc_pen_finish(pc, FALSE); + } + break; + case PenTool::STOP: + // This is allowed, if we just cancelled curve + break; + default: + break; + } + pc->state = PenTool::POINT; + ret = TRUE; + break; + default: + break; + } + + if (pc->grab) { + // Release grab now + sp_canvas_item_ungrab(pc->grab, revent.time); + pc->grab = NULL; + } + + ret = TRUE; + + dc->green_closed = FALSE; + } + + // TODO: can we be sure that the path was created correctly? + // TODO: should we offer an option to collect the clicks in a list? + if (pc->expecting_clicks_for_LPE == 0 && sp_pen_context_has_waiting_LPE(pc)) { + sp_pen_context_set_polyline_mode(pc); + + ToolBase *ec = SP_EVENT_CONTEXT(pc); + Inkscape::Selection *selection = sp_desktop_selection (ec->desktop); + + if (pc->waiting_LPE) { + // we have an already created LPE waiting for a path + pc->waiting_LPE->acceptParamPath(SP_PATH(selection->singleItem())); + selection->add(SP_OBJECT(pc->waiting_item)); + pc->waiting_LPE = NULL; + } else { + // the case that we need to create a new LPE and apply it to the just-drawn path is + // handled in spdc_check_for_and_apply_waiting_LPE() in draw-context.cpp + } + } + + return ret; +} + +static gint pen_handle_2button_press(PenTool *const pc, GdkEventButton const &bevent) +{ + gint ret = FALSE; + // only end on LMB double click. Otherwise horizontal scrolling causes ending of the path + if (pc->npoints != 0 && bevent.button == 1) { + spdc_pen_finish(pc, FALSE); + ret = TRUE; + } + return ret; +} + +static void pen_redraw_all (PenTool *const pc) +{ + // green + if (pc->green_bpaths) { + // remove old piecewise green canvasitems + while (pc->green_bpaths) { + sp_canvas_item_destroy(SP_CANVAS_ITEM(pc->green_bpaths->data)); + pc->green_bpaths = g_slist_remove(pc->green_bpaths, pc->green_bpaths->data); + } + // one canvas bpath for all of green_curve + SPCanvasItem *cshape = sp_canvas_bpath_new(sp_desktop_sketch(pc->desktop), pc->green_curve); + sp_canvas_bpath_set_stroke(SP_CANVAS_BPATH(cshape), pc->green_color, 1.0, SP_STROKE_LINEJOIN_MITER, SP_STROKE_LINECAP_BUTT); + sp_canvas_bpath_set_fill(SP_CANVAS_BPATH(cshape), 0, SP_WIND_RULE_NONZERO); + + pc->green_bpaths = g_slist_prepend(pc->green_bpaths, cshape); + } + + if (pc->green_anchor) + SP_CTRL(pc->green_anchor->ctrl)->moveto(pc->green_anchor->dp); + + pc->red_curve->reset(); + pc->red_curve->moveto(pc->p[0]); + pc->red_curve->curveto(pc->p[1], pc->p[2], pc->p[3]); + sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(pc->red_bpath), pc->red_curve); + + // handles + if (pc->p[0] != pc->p[1]) { + SP_CTRL(pc->c1)->moveto(pc->p[1]); + pc->cl1->setCoords(pc->p[0], pc->p[1]); + sp_canvas_item_show(pc->c1); + sp_canvas_item_show(pc->cl1); + } else { + sp_canvas_item_hide(pc->c1); + sp_canvas_item_hide(pc->cl1); + } + + Geom::Curve const * last_seg = pc->green_curve->last_segment(); + if (last_seg) { + Geom::CubicBezier const * cubic = dynamic_cast( last_seg ); + if ( cubic && + (*cubic)[2] != pc->p[0] ) + { + Geom::Point p2 = (*cubic)[2]; + SP_CTRL(pc->c0)->moveto(p2); + pc->cl0->setCoords(p2, pc->p[0]); + sp_canvas_item_show(pc->c0); + sp_canvas_item_show(pc->cl0); + } else { + sp_canvas_item_hide(pc->c0); + sp_canvas_item_hide(pc->cl0); + } + } +} + +static void pen_lastpoint_move (PenTool *const pc, gdouble x, gdouble y) +{ + if (pc->npoints != 5) + return; + + // green + if (!pc->green_curve->is_empty()) { + pc->green_curve->last_point_additive_move( Geom::Point(x,y) ); + } else { + // start anchor too + if (pc->green_anchor) { + pc->green_anchor->dp += Geom::Point(x, y); + } + } + + // red + pc->p[0] += Geom::Point(x, y); + pc->p[1] += Geom::Point(x, y); + pen_redraw_all(pc); +} + +static void pen_lastpoint_move_screen (PenTool *const pc, gdouble x, gdouble y) +{ + pen_lastpoint_move (pc, x / pc->desktop->current_zoom(), y / pc->desktop->current_zoom()); +} + +static void pen_lastpoint_tocurve (PenTool *const pc) +{ + if (pc->npoints != 5) + return; + + Geom::CubicBezier const * cubic = dynamic_cast( pc->green_curve->last_segment() ); + if ( cubic ) { + pc->p[1] = pc->p[0] + (Geom::Point)( (*cubic)[3] - (*cubic)[2] ); + } else { + pc->p[1] = pc->p[0] + (1./3)*(pc->p[3] - pc->p[0]); + } + + pen_redraw_all(pc); +} + +static void pen_lastpoint_toline (PenTool *const pc) +{ + if (pc->npoints != 5) + return; + + pc->p[1] = pc->p[0]; + + pen_redraw_all(pc); +} + + +static gint pen_handle_key_press(PenTool *const pc, GdkEvent *event) +{ + + gint ret = FALSE; + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + gdouble const nudge = prefs->getDoubleLimited("/options/nudgedistance/value", 2, 0, 1000, "px"); // in px + + switch (get_group0_keyval (&event->key)) { + + case GDK_KEY_Left: // move last point left + case GDK_KEY_KP_Left: + if (!MOD__CTRL(event)) { // not ctrl + if (MOD__ALT(event)) { // alt + if (MOD__SHIFT(event)) pen_lastpoint_move_screen(pc, -10, 0); // shift + else pen_lastpoint_move_screen(pc, -1, 0); // no shift + } + else { // no alt + if (MOD__SHIFT(event)) pen_lastpoint_move(pc, -10*nudge, 0); // shift + else pen_lastpoint_move(pc, -nudge, 0); // no shift + } + ret = TRUE; + } + break; + case GDK_KEY_Up: // move last point up + case GDK_KEY_KP_Up: + if (!MOD__CTRL(event)) { // not ctrl + if (MOD__ALT(event)) { // alt + if (MOD__SHIFT(event)) pen_lastpoint_move_screen(pc, 0, 10); // shift + else pen_lastpoint_move_screen(pc, 0, 1); // no shift + } + else { // no alt + if (MOD__SHIFT(event)) pen_lastpoint_move(pc, 0, 10*nudge); // shift + else pen_lastpoint_move(pc, 0, nudge); // no shift + } + ret = TRUE; + } + break; + case GDK_KEY_Right: // move last point right + case GDK_KEY_KP_Right: + if (!MOD__CTRL(event)) { // not ctrl + if (MOD__ALT(event)) { // alt + if (MOD__SHIFT(event)) pen_lastpoint_move_screen(pc, 10, 0); // shift + else pen_lastpoint_move_screen(pc, 1, 0); // no shift + } + else { // no alt + if (MOD__SHIFT(event)) pen_lastpoint_move(pc, 10*nudge, 0); // shift + else pen_lastpoint_move(pc, nudge, 0); // no shift + } + ret = TRUE; + } + break; + case GDK_KEY_Down: // move last point down + case GDK_KEY_KP_Down: + if (!MOD__CTRL(event)) { // not ctrl + if (MOD__ALT(event)) { // alt + if (MOD__SHIFT(event)) pen_lastpoint_move_screen(pc, 0, -10); // shift + else pen_lastpoint_move_screen(pc, 0, -1); // no shift + } + else { // no alt + if (MOD__SHIFT(event)) pen_lastpoint_move(pc, 0, -10*nudge); // shift + else pen_lastpoint_move(pc, 0, -nudge); // no shift + } + ret = TRUE; + } + break; + +/*TODO: this is not yet enabled?? looks like some traces of the Geometry tool + case GDK_KEY_P: + case GDK_KEY_p: + if (MOD__SHIFT_ONLY(event)) { + sp_pen_context_wait_for_LPE_mouse_clicks(pc, Inkscape::LivePathEffect::PARALLEL, 2); + ret = TRUE; + } + break; + + case GDK_KEY_C: + case GDK_KEY_c: + if (MOD__SHIFT_ONLY(event)) { + sp_pen_context_wait_for_LPE_mouse_clicks(pc, Inkscape::LivePathEffect::CIRCLE_3PTS, 3); + ret = TRUE; + } + break; + + case GDK_KEY_B: + case GDK_KEY_b: + if (MOD__SHIFT_ONLY(event)) { + sp_pen_context_wait_for_LPE_mouse_clicks(pc, Inkscape::LivePathEffect::PERP_BISECTOR, 2); + ret = TRUE; + } + break; + + case GDK_KEY_A: + case GDK_KEY_a: + if (MOD__SHIFT_ONLY(event)) { + sp_pen_context_wait_for_LPE_mouse_clicks(pc, Inkscape::LivePathEffect::ANGLE_BISECTOR, 3); + ret = TRUE; + } + break; +*/ + + case GDK_KEY_U: + case GDK_KEY_u: + if (MOD__SHIFT_ONLY(event)) { + pen_lastpoint_tocurve(pc); + ret = TRUE; + } + break; + case GDK_KEY_L: + case GDK_KEY_l: + if (MOD__SHIFT_ONLY(event)) { + pen_lastpoint_toline(pc); + ret = TRUE; + } + break; + + case GDK_KEY_Return: + case GDK_KEY_KP_Enter: + if (pc->npoints != 0) { + spdc_pen_finish(pc, FALSE); + ret = TRUE; + } + break; + case GDK_KEY_Escape: + if (pc->npoints != 0) { + // if drawing, cancel, otherwise pass it up for deselecting + pen_cancel (pc); + ret = TRUE; + } + break; + case GDK_KEY_z: + case GDK_KEY_Z: + if (MOD__CTRL_ONLY(event) && pc->npoints != 0) { + // if drawing, cancel, otherwise pass it up for undo + pen_cancel (pc); + ret = TRUE; + } + break; + case GDK_KEY_g: + case GDK_KEY_G: + if (MOD__SHIFT_ONLY(event)) { + sp_selection_to_guides(SP_EVENT_CONTEXT(pc)->desktop); + ret = true; + } + break; + case GDK_KEY_BackSpace: + case GDK_KEY_Delete: + case GDK_KEY_KP_Delete: + if ( pc->green_curve->is_empty() || (pc->green_curve->last_segment() == NULL) ) { + if (!pc->red_curve->is_empty()) { + pen_cancel (pc); + ret = TRUE; + } else { + // do nothing; this event should be handled upstream + } + } else { + // Reset red curve + pc->red_curve->reset(); + // Destroy topmost green bpath + if (pc->green_bpaths) { + if (pc->green_bpaths->data) + sp_canvas_item_destroy(SP_CANVAS_ITEM(pc->green_bpaths->data)); + pc->green_bpaths = g_slist_remove(pc->green_bpaths, pc->green_bpaths->data); + } + // Get last segment + if ( pc->green_curve->is_empty() ) { + g_warning("pen_handle_key_press, case GDK_KP_Delete: Green curve is empty"); + break; + } + // The code below assumes that pc->green_curve has only ONE path ! + Geom::Curve const * crv = pc->green_curve->last_segment(); + pc->p[0] = crv->initialPoint(); + if ( Geom::CubicBezier const * cubic = dynamic_cast(crv)) { + pc->p[1] = (*cubic)[1]; + } else { + pc->p[1] = pc->p[0]; + } + Geom::Point const pt(( pc->npoints < 4 + ? (Geom::Point)(crv->finalPoint()) + : pc->p[3] )); + pc->npoints = 2; + pc->green_curve->backspace(); + sp_canvas_item_hide(pc->c0); + sp_canvas_item_hide(pc->c1); + sp_canvas_item_hide(pc->cl0); + sp_canvas_item_hide(pc->cl1); + pc->state = PenTool::POINT; + spdc_pen_set_subsequent_point(pc, pt, true); + pen_last_paraxial_dir = !pen_last_paraxial_dir; + ret = TRUE; + } + break; + default: + break; + } + return ret; +} + +static void spdc_reset_colors(PenTool *pc) +{ + // Red + pc->red_curve->reset(); + sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(pc->red_bpath), NULL); + // Blue + pc->blue_curve->reset(); + sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(pc->blue_bpath), NULL); + // Green + while (pc->green_bpaths) { + sp_canvas_item_destroy(SP_CANVAS_ITEM(pc->green_bpaths->data)); + pc->green_bpaths = g_slist_remove(pc->green_bpaths, pc->green_bpaths->data); + } + pc->green_curve->reset(); + if (pc->green_anchor) { + pc->green_anchor = sp_draw_anchor_destroy(pc->green_anchor); + } + pc->sa = NULL; + pc->ea = NULL; + pc->npoints = 0; + pc->red_curve_is_valid = false; +} + + +static void spdc_pen_set_initial_point(PenTool *const pc, Geom::Point const p) +{ + g_assert( pc->npoints == 0 ); + + pc->p[0] = p; + pc->p[1] = p; + pc->npoints = 2; + sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(pc->red_bpath), NULL); + + pc->desktop->canvas->forceFullRedrawAfterInterruptions(5); +} + +/** + * Show the status message for the current line/curve segment. + * This type of message always shows angle/distance as the last + * two parameters ("angle %3.2f°, distance %s"). + */ +static void spdc_pen_set_angle_distance_status_message(PenTool *const pc, Geom::Point const p, int pc_point_to_compare, gchar const *message) +{ + g_assert(pc != NULL); + g_assert((pc_point_to_compare == 0) || (pc_point_to_compare == 3)); // exclude control handles + g_assert(message != NULL); + + SPDesktop *desktop = SP_EVENT_CONTEXT(pc)->desktop; + Geom::Point rel = p - pc->p[pc_point_to_compare]; + Inkscape::Util::Quantity q = Inkscape::Util::Quantity(Geom::L2(rel), "px"); + GString *dist = g_string_new(q.string(desktop->namedview->doc_units).c_str()); + double angle = atan2(rel[Geom::Y], rel[Geom::X]) * 180 / M_PI; + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + if (prefs->getBool("/options/compassangledisplay/value", 0) != 0) { + angle = 90 - angle; + if (angle < 0) { + angle += 360; + } + } + + pc->message_context->setF(Inkscape::IMMEDIATE_MESSAGE, message, angle, dist->str); + g_string_free(dist, FALSE); +} + +static void spdc_pen_set_subsequent_point(PenTool *const pc, Geom::Point const p, bool statusbar, guint status) +{ + g_assert( pc->npoints != 0 ); + // todo: Check callers to see whether 2 <= npoints is guaranteed. + + pc->p[2] = p; + pc->p[3] = p; + pc->p[4] = p; + pc->npoints = 5; + pc->red_curve->reset(); + bool is_curve; + pc->red_curve->moveto(pc->p[0]); + if (pc->polylines_paraxial && !statusbar) { + // we are drawing horizontal/vertical lines and hit an anchor; + Geom::Point const origin = pc->p[0]; + // if the previous point and the anchor are not aligned either horizontally or vertically... + if ((abs(p[Geom::X] - origin[Geom::X]) > 1e-9) && (abs(p[Geom::Y] - origin[Geom::Y]) > 1e-9)) { + // ...then we should draw an L-shaped path, consisting of two paraxial segments + Geom::Point intermed = p; + pen_set_to_nearest_horiz_vert(pc, intermed, status, false); + pc->red_curve->lineto(intermed); + } + pc->red_curve->lineto(p); + is_curve = false; + } else { + // one of the 'regular' modes + if (pc->p[1] != pc->p[0]) { + pc->red_curve->curveto(pc->p[1], p, p); + is_curve = true; + } else { + pc->red_curve->lineto(p); + is_curve = false; + } + } + + sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(pc->red_bpath), pc->red_curve); + + if (statusbar) { + gchar *message = is_curve ? + _("Curve segment: angle %3.2f°, distance %s; with Ctrl to snap angle, Enter to finish the path" ): + _("Line segment: angle %3.2f°, distance %s; with Ctrl to snap angle, Enter to finish the path"); + spdc_pen_set_angle_distance_status_message(pc, p, 0, message); + } +} + +static void spdc_pen_set_ctrl(PenTool *const pc, Geom::Point const p, guint const state) +{ + sp_canvas_item_show(pc->c1); + sp_canvas_item_show(pc->cl1); + + if ( pc->npoints == 2 ) { + pc->p[1] = p; + sp_canvas_item_hide(pc->c0); + sp_canvas_item_hide(pc->cl0); + SP_CTRL(pc->c1)->moveto(pc->p[1]); + pc->cl1->setCoords(pc->p[0], pc->p[1]); + + spdc_pen_set_angle_distance_status_message(pc, p, 0, _("Curve handle: angle %3.2f°, length %s; with Ctrl to snap angle")); + } else if ( pc->npoints == 5 ) { + pc->p[4] = p; + sp_canvas_item_show(pc->c0); + sp_canvas_item_show(pc->cl0); + bool is_symm = false; + if ( ( ( pc->mode == PenTool::MODE_CLICK ) && ( state & GDK_CONTROL_MASK ) ) || + ( ( pc->mode == PenTool::MODE_DRAG ) && !( state & GDK_SHIFT_MASK ) ) ) { + Geom::Point delta = p - pc->p[3]; + pc->p[2] = pc->p[3] - delta; + is_symm = true; + pc->red_curve->reset(); + pc->red_curve->moveto(pc->p[0]); + pc->red_curve->curveto(pc->p[1], pc->p[2], pc->p[3]); + sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(pc->red_bpath), pc->red_curve); + } + SP_CTRL(pc->c0)->moveto(pc->p[2]); + pc->cl0 ->setCoords(pc->p[3], pc->p[2]); + SP_CTRL(pc->c1)->moveto(pc->p[4]); + pc->cl1->setCoords(pc->p[3], pc->p[4]); + + gchar *message = is_symm ? + _("Curve handle, symmetric: angle %3.2f°, length %s; with Ctrl to snap angle, with Shift to move this handle only") : + _("Curve handle: angle %3.2f°, length %s; with Ctrl to snap angle, with Shift to move this handle only"); + spdc_pen_set_angle_distance_status_message(pc, p, 3, message); + } else { + g_warning("Something bad happened - npoints is %d", pc->npoints); + } +} + +static void spdc_pen_finish_segment(PenTool *const pc, Geom::Point const p, guint const state) +{ + if (pc->polylines_paraxial) { + pen_last_paraxial_dir = pen_next_paraxial_direction(pc, p, pc->p[0], state); + } + + ++pc->num_clicks; + + if (!pc->red_curve->is_empty()) { + pc->green_curve->append_continuous(pc->red_curve, 0.0625); + SPCurve *curve = pc->red_curve->copy(); + /// \todo fixme: + SPCanvasItem *cshape = sp_canvas_bpath_new(sp_desktop_sketch(pc->desktop), curve); + curve->unref(); + sp_canvas_bpath_set_stroke(SP_CANVAS_BPATH(cshape), pc->green_color, 1.0, SP_STROKE_LINEJOIN_MITER, SP_STROKE_LINECAP_BUTT); + + pc->green_bpaths = g_slist_prepend(pc->green_bpaths, cshape); + + pc->p[0] = pc->p[3]; + pc->p[1] = pc->p[4]; + pc->npoints = 2; + + pc->red_curve->reset(); + } +} + +static void spdc_pen_finish(PenTool *const pc, gboolean const closed) +{ + if (pc->expecting_clicks_for_LPE > 1) { + // don't let the path be finished before we have collected the required number of mouse clicks + return; + } + + pc->num_clicks = 0; + + pen_disable_events(pc); + + SPDesktop *const desktop = pc->desktop; + pc->message_context->clear(); + desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("Drawing finished")); + + pc->red_curve->reset(); + spdc_concat_colors_and_flush(pc, closed); + pc->sa = NULL; + pc->ea = NULL; + + pc->npoints = 0; + pc->state = PenTool::POINT; + + sp_canvas_item_hide(pc->c0); + sp_canvas_item_hide(pc->c1); + sp_canvas_item_hide(pc->cl0); + sp_canvas_item_hide(pc->cl1); + + if (pc->green_anchor) { + pc->green_anchor = sp_draw_anchor_destroy(pc->green_anchor); + } + + + pc->desktop->canvas->endForcedFullRedraws(); + + pen_enable_events(pc); +} + +static void pen_disable_events(PenTool *const pc) { + pc->events_disabled++; +} + +static void pen_enable_events(PenTool *const pc) { + g_return_if_fail(pc->events_disabled != 0); + + pc->events_disabled--; +} + +void sp_pen_context_wait_for_LPE_mouse_clicks(PenTool *pc, Inkscape::LivePathEffect::EffectType effect_type, + unsigned int num_clicks, bool use_polylines) +{ + if (effect_type == Inkscape::LivePathEffect::INVALID_LPE) + return; + + pc->waiting_LPE_type = effect_type; + pc->expecting_clicks_for_LPE = num_clicks; + pc->polylines_only = use_polylines; + pc->polylines_paraxial = false; // TODO: think if this is correct for all cases +} + +void sp_pen_context_cancel_waiting_for_LPE(PenTool *pc) +{ + pc->waiting_LPE_type = Inkscape::LivePathEffect::INVALID_LPE; + pc->expecting_clicks_for_LPE = 0; + sp_pen_context_set_polyline_mode(pc); +} + +static int pen_next_paraxial_direction(const PenTool *const pc, + Geom::Point const &pt, Geom::Point const &origin, guint state) { + // + // after the first mouse click we determine whether the mouse pointer is closest to a + // horizontal or vertical segment; for all subsequent mouse clicks, we use the direction + // orthogonal to the last one; pressing Shift toggles the direction + // + // num_clicks is not reliable because spdc_pen_finish_segment is sometimes called too early + // (on first mouse release), in which case num_clicks immediately becomes 1. + // if (pc->num_clicks == 0) { + + if (pc->green_curve->is_empty()) { + // first mouse click + double dist_h = fabs(pt[Geom::X] - origin[Geom::X]); + double dist_v = fabs(pt[Geom::Y] - origin[Geom::Y]); + int ret = (dist_h < dist_v) ? 1 : 0; // 0 = horizontal, 1 = vertical + pen_last_paraxial_dir = (state & GDK_SHIFT_MASK) ? 1 - ret : ret; + return pen_last_paraxial_dir; + } else { + // subsequent mouse click + return (state & GDK_SHIFT_MASK) ? pen_last_paraxial_dir : 1 - pen_last_paraxial_dir; + } +} + +void pen_set_to_nearest_horiz_vert(const PenTool *const pc, Geom::Point &pt, guint const state, bool snap) +{ + Geom::Point const origin = pc->p[0]; + + int next_dir = pen_next_paraxial_direction(pc, pt, origin, state); + + if (!snap) { + if (next_dir == 0) { + // line is forced to be horizontal + pt[Geom::Y] = origin[Geom::Y]; + } else { + // line is forced to be vertical + pt[Geom::X] = origin[Geom::X]; + } + } else { + // Create a horizontal or vertical constraint line + Inkscape::Snapper::SnapConstraint cl(origin, next_dir ? Geom::Point(0, 1) : Geom::Point(1, 0)); + + // Snap along the constraint line; if we didn't snap then still the constraint will be applied + SnapManager &m = pc->desktop->namedview->snap_manager; + + Inkscape::Selection *selection = sp_desktop_selection (pc->desktop); + // selection->singleItem() is the item that is currently being drawn. This item will not be snapped to (to avoid self-snapping) + // TODO: Allow snapping to the stationary parts of the item, and only ignore the last segment + + m.setup(pc->desktop, true, selection->singleItem()); + m.constrainedSnapReturnByRef(pt, Inkscape::SNAPSOURCE_NODE_HANDLE, cl); + m.unSetup(); + } +} + +} +} +} + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/ui/tools/pen-tool.h b/src/ui/tools/pen-tool.h new file mode 100644 index 000000000..4452dbd68 --- /dev/null +++ b/src/ui/tools/pen-tool.h @@ -0,0 +1,105 @@ +#ifndef SEEN_PEN_CONTEXT_H +#define SEEN_PEN_CONTEXT_H + +/** \file + * PenTool: a context for pen tool events. + */ + +#include "ui/tools/freehand-base.h" +#include "live_effects/effect.h" + +#define SP_PEN_CONTEXT(obj) (dynamic_cast((Inkscape::UI::Tools::ToolBase*)obj)) +#define SP_IS_PEN_CONTEXT(obj) (dynamic_cast((const Inkscape::UI::Tools::ToolBase*)obj) != NULL) + +struct SPCtrlLine; + +namespace Inkscape { +namespace UI { +namespace Tools { + +/** + * PenTool: a context for pen tool events. + */ +class PenTool : public FreehandBase { +public: + PenTool(); + virtual ~PenTool(); + + enum Mode { + MODE_CLICK, + MODE_DRAG + }; + + enum State { + POINT, + CONTROL, + CLOSE, + STOP + }; + + Geom::Point p[5]; + + /** \invar npoints in {0, 2, 5}. */ + // npoints somehow determines the type of the node (what does it mean, exactly? the number of Bezier handles?) + gint npoints; + + Mode mode; + State state; + + bool polylines_only; + bool polylines_paraxial; + int num_clicks; + + unsigned int expecting_clicks_for_LPE; // if positive, finish the path after this many clicks + Inkscape::LivePathEffect::Effect *waiting_LPE; // if NULL, waiting_LPE_type in SPDrawContext is taken into account + SPLPEItem *waiting_item; + + SPCanvasItem *c0; + SPCanvasItem *c1; + + SPCtrlLine *cl0; + SPCtrlLine *cl1; + + unsigned int events_disabled : 1; + + static const std::string prefsPath; + + virtual const std::string& getPrefsPath(); + +protected: + virtual void setup(); + virtual void finish(); + virtual void set(const Inkscape::Preferences::Entry& val); + virtual bool root_handler(GdkEvent* event); + virtual bool item_handler(SPItem* item, GdkEvent* event); +}; + +inline bool sp_pen_context_has_waiting_LPE(PenTool *pc) { + // note: waiting_LPE_type is defined in SPDrawContext + return (pc->waiting_LPE != NULL || + pc->waiting_LPE_type != Inkscape::LivePathEffect::INVALID_LPE); +} + +void sp_pen_context_set_polyline_mode(PenTool *const pc); +void sp_pen_context_wait_for_LPE_mouse_clicks(PenTool *pc, Inkscape::LivePathEffect::EffectType effect_type, + unsigned int num_clicks, bool use_polylines = true); +void sp_pen_context_cancel_waiting_for_LPE(PenTool *pc); +void sp_pen_context_put_into_waiting_mode(SPDesktop *desktop, Inkscape::LivePathEffect::EffectType effect_type, + unsigned int num_clicks, bool use_polylines = true); + +} +} +} + +#endif /* !SEEN_PEN_CONTEXT_H */ + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/ui/tools/pencil-tool.cpp b/src/ui/tools/pencil-tool.cpp new file mode 100644 index 000000000..7edf5d8ff --- /dev/null +++ b/src/ui/tools/pencil-tool.cpp @@ -0,0 +1,916 @@ +/** \file + * Pencil event context implementation. + */ + +/* + * Authors: + * Lauris Kaplinski + * bulia byak + * Jon A. Cruz + * + * Copyright (C) 2000 Lauris Kaplinski + * Copyright (C) 2000-2001 Ximian, Inc. + * Copyright (C) 2002 Lauris Kaplinski + * Copyright (C) 2004 Monash University + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include + +#include "ui/tools/pencil-tool.h" +#include "desktop.h" +#include "desktop-handles.h" +#include "selection.h" +#include "selection-chemistry.h" +#include "draw-anchor.h" +#include "message-stack.h" +#include "message-context.h" +#include "modifier-fns.h" +#include "sp-path.h" +#include "preferences.h" +#include "snap.h" +#include "pixmaps/cursor-pencil.xpm" +#include <2geom/sbasis-to-bezier.h> +#include <2geom/bezier-utils.h> +#include "display/canvas-bpath.h" +#include +#include "context-fns.h" +#include "sp-namedview.h" +#include "xml/repr.h" +#include "document.h" +#include "desktop-style.h" +#include "macros.h" +#include "display/sp-canvas.h" +#include "display/curve.h" +#include "livarot/Path.h" +#include "tool-factory.h" + +namespace Inkscape { +namespace UI { +namespace Tools { + +static gint pencil_handle_button_press(PencilTool *const pc, GdkEventButton const &bevent); +static gint pencil_handle_motion_notify(PencilTool *const pc, GdkEventMotion const &mevent); +static gint pencil_handle_button_release(PencilTool *const pc, GdkEventButton const &revent); +static gint pencil_handle_key_press(PencilTool *const pc, guint const keyval, guint const state); +static gint pencil_handle_key_release(PencilTool *const pc, guint const keyval, guint const state); + +static void spdc_set_startpoint(PencilTool *pc, Geom::Point const &p); +static void spdc_set_endpoint(PencilTool *pc, Geom::Point const &p); +static void spdc_finish_endpoint(PencilTool *pc); +static void spdc_add_freehand_point(PencilTool *pc, Geom::Point const &p, guint state); +static void fit_and_split(PencilTool *pc); +static void interpolate(PencilTool *pc); +static void sketch_interpolate(PencilTool *pc); + +static Geom::Point pencil_drag_origin_w(0, 0); +static bool pencil_within_tolerance = false; + +static bool in_svg_plane(Geom::Point const &p) { return Geom::LInfty(p) < 1e18; } + +namespace { + ToolBase* createPencilContext() { + return new PencilTool(); + } + + bool pencilContextRegistered = ToolFactory::instance().registerObject("/tools/freehand/pencil", createPencilContext); +} + +const std::string& PencilTool::getPrefsPath() { + return PencilTool::prefsPath; +} + +const std::string PencilTool::prefsPath = "/tools/freehand/pencil"; + +PencilTool::PencilTool() : + FreehandBase(), + p(), + npoints(0), + state(SP_PENCIL_CONTEXT_IDLE), + req_tangent(0,0), + is_drawing(false), + ps(), + sketch_interpolation(Geom::Piecewise >())// since PencilTool is not properly constructed... +{ + this->cursor_shape = cursor_pencil_xpm; + this->hot_x = 4; + this->hot_y = 4; + this->sketch_n = 0; +} + +void PencilTool::setup() { + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + if (prefs->getBool("/tools/freehand/pencil/selcue")) { + this->enableSelectionCue(); + } + + FreehandBase::setup(); + + this->is_drawing = false; + this->anchor_statusbar = false; +} + +PencilTool::~PencilTool() { +} + +/** Snaps new node relative to the previous node. */ +static void +spdc_endpoint_snap(PencilTool const *pc, Geom::Point &p, guint const state) +{ + if ((state & GDK_CONTROL_MASK)) { //CTRL enables constrained snapping + if (pc->npoints > 0) { + spdc_endpoint_snap_rotation(pc, p, pc->p[0], state); + } + } else { + if (!(state & GDK_SHIFT_MASK)) { //SHIFT disables all snapping, except the angular snapping above + //After all, the user explicitely asked for angular snapping by + //pressing CTRL + boost::optional origin = pc->npoints > 0 ? pc->p[0] : boost::optional(); + spdc_endpoint_snap_free(pc, p, origin, state); + } + } +} + +/** + * Callback for handling all pencil context events. + */ +bool PencilTool::root_handler(GdkEvent* event) { + gint ret = FALSE; + + switch (event->type) { + case GDK_BUTTON_PRESS: + ret = pencil_handle_button_press(this, event->button); + break; + + case GDK_MOTION_NOTIFY: + ret = pencil_handle_motion_notify(this, event->motion); + break; + + case GDK_BUTTON_RELEASE: + ret = pencil_handle_button_release(this, event->button); + break; + + case GDK_KEY_PRESS: + ret = pencil_handle_key_press(this, get_group0_keyval (&event->key), event->key.state); + break; + + case GDK_KEY_RELEASE: + ret = pencil_handle_key_release(this, get_group0_keyval (&event->key), event->key.state); + break; + + default: + break; + } + + if (!ret) { + ret = FreehandBase::root_handler(event); + } + + return ret; +} + +static gint +pencil_handle_button_press(PencilTool *const pc, GdkEventButton const &bevent) +{ + gint ret = FALSE; + ToolBase *event_context = SP_EVENT_CONTEXT(pc); + if ( bevent.button == 1 && !event_context->space_panning) { + + FreehandBase *dc = SP_DRAW_CONTEXT (pc); + SPDesktop *desktop = SP_EVENT_CONTEXT_DESKTOP(dc); + Inkscape::Selection *selection = sp_desktop_selection(desktop); + + if (Inkscape::have_viable_layer(desktop, dc->message_context) == false) { + return TRUE; + } + + if (!pc->grab) { + /* Grab mouse, so release will not pass unnoticed */ + pc->grab = SP_CANVAS_ITEM(desktop->acetate); + sp_canvas_item_grab(pc->grab, ( GDK_KEY_PRESS_MASK | GDK_BUTTON_PRESS_MASK | + GDK_BUTTON_RELEASE_MASK | + GDK_POINTER_MOTION_MASK ), + NULL, bevent.time); + } + + Geom::Point const button_w(bevent.x, bevent.y); + + /* Find desktop coordinates */ + Geom::Point p = pc->desktop->w2d(button_w); + + /* Test whether we hit any anchor. */ + SPDrawAnchor *anchor = spdc_test_inside(pc, button_w); + + pencil_drag_origin_w = Geom::Point(bevent.x,bevent.y); + pencil_within_tolerance = true; + + switch (pc->state) { + case SP_PENCIL_CONTEXT_ADDLINE: + /* Current segment will be finished with release */ + ret = TRUE; + break; + default: + /* Set first point of sequence */ + SnapManager &m = desktop->namedview->snap_manager; + + if (bevent.state & GDK_CONTROL_MASK) { + m.setup(desktop); + if (!(bevent.state & GDK_SHIFT_MASK)) { + m.freeSnapReturnByRef(p, Inkscape::SNAPSOURCE_NODE_HANDLE); + } + spdc_create_single_dot(event_context, p, "/tools/freehand/pencil", bevent.state); + m.unSetup(); + ret = true; + break; + } + if (anchor) { + p = anchor->dp; + desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("Continuing selected path")); + } else { + m.setup(desktop); + if (!(bevent.state & GDK_SHIFT_MASK)) { + // This is the first click of a new curve; deselect item so that + // this curve is not combined with it (unless it is drawn from its + // anchor, which is handled by the sibling branch above) + selection->clear(); + desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("Creating new path")); + m.freeSnapReturnByRef(p, Inkscape::SNAPSOURCE_NODE_HANDLE); + } else if (selection->singleItem() && SP_IS_PATH(selection->singleItem())) { + desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("Appending to selected path")); + m.freeSnapReturnByRef(p, Inkscape::SNAPSOURCE_NODE_HANDLE); + } + m.unSetup(); + } + pc->sa = anchor; + spdc_set_startpoint(pc, p); + ret = TRUE; + break; + } + + pc->is_drawing = true; + } + return ret; +} + +static gint +pencil_handle_motion_notify(PencilTool *const pc, GdkEventMotion const &mevent) +{ + SPDesktop *const dt = pc->desktop; + + if ((mevent.state & GDK_CONTROL_MASK) && (mevent.state & GDK_BUTTON1_MASK)) { + // mouse was accidentally moved during Ctrl+click; + // ignore the motion and create a single point + pc->is_drawing = false; + return TRUE; + } + gint ret = FALSE; + + ToolBase *event_context = SP_EVENT_CONTEXT(pc); + if (event_context->space_panning || mevent.state & GDK_BUTTON2_MASK || mevent.state & GDK_BUTTON3_MASK) { + // allow scrolling + return FALSE; + } + + if ( ( mevent.state & GDK_BUTTON1_MASK ) && !pc->grab && pc->is_drawing) { + /* Grab mouse, so release will not pass unnoticed */ + pc->grab = SP_CANVAS_ITEM(dt->acetate); + sp_canvas_item_grab(pc->grab, ( GDK_KEY_PRESS_MASK | GDK_BUTTON_PRESS_MASK | + GDK_BUTTON_RELEASE_MASK | + GDK_POINTER_MOTION_MASK ), + NULL, mevent.time); + } + + /* Find desktop coordinates */ + Geom::Point p = dt->w2d(Geom::Point(mevent.x, mevent.y)); + + /* Test whether we hit any anchor. */ + SPDrawAnchor *anchor = spdc_test_inside(pc, Geom::Point(mevent.x, mevent.y)); + + if (pencil_within_tolerance) { + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + gint const tolerance = prefs->getIntLimited("/options/dragtolerance/value", 0, 0, 100); + if ( Geom::LInfty( Geom::Point(mevent.x,mevent.y) - pencil_drag_origin_w ) < tolerance ) { + return FALSE; // Do not drag if we're within tolerance from origin. + } + } + + // Once the user has moved farther than tolerance from the original location + // (indicating they intend to move the object, not click), then always process the + // motion notify coordinates as given (no snapping back to origin) + pencil_within_tolerance = false; + + switch (pc->state) { + case SP_PENCIL_CONTEXT_ADDLINE: + /* Set red endpoint */ + if (anchor) { + p = anchor->dp; + } else { + Geom::Point ptnr(p); + spdc_endpoint_snap(pc, ptnr, mevent.state); + p = ptnr; + } + spdc_set_endpoint(pc, p); + ret = TRUE; + break; + default: + /* We may be idle or already freehand */ + if ( mevent.state & GDK_BUTTON1_MASK && pc->is_drawing ) { + if (pc->state == SP_PENCIL_CONTEXT_IDLE) { + sp_event_context_discard_delayed_snap_event(event_context); + } + pc->state = SP_PENCIL_CONTEXT_FREEHAND; + + if ( !pc->sa && !pc->green_anchor ) { + /* Create green anchor */ + pc->green_anchor = sp_draw_anchor_new(pc, pc->green_curve, TRUE, pc->p[0]); + } + if (anchor) { + p = anchor->dp; + } + + if ( pc->npoints != 0) { // buttonpress may have happened before we entered draw context! + if (pc->ps.empty()) { + // Only in freehand mode we have to add the first point also to pc->ps (apparently) + // - We cannot add this point in spdc_set_startpoint, because we only need it for freehand + // - We cannot do this in the button press handler because at that point we don't know yet + // wheter we're going into freehand mode or not + pc->ps.push_back(pc->p[0]); + } + spdc_add_freehand_point(pc, p, mevent.state); + ret = TRUE; + } + + if (anchor && !pc->anchor_statusbar) { + pc->message_context->set(Inkscape::NORMAL_MESSAGE, _("Release here to close and finish the path.")); + pc->anchor_statusbar = true; + } else if (!anchor && pc->anchor_statusbar) { + pc->message_context->clear(); + pc->anchor_statusbar = false; + } else if (!anchor) { + pc->message_context->set(Inkscape::NORMAL_MESSAGE, _("Drawing a freehand path")); + } + + } else { + if (anchor && !pc->anchor_statusbar) { + pc->message_context->set(Inkscape::NORMAL_MESSAGE, _("Drag to continue the path from this point.")); + pc->anchor_statusbar = true; + } else if (!anchor && pc->anchor_statusbar) { + pc->message_context->clear(); + pc->anchor_statusbar = false; + } + } + + // Show the pre-snap indicator to communicate to the user where we would snap to if he/she were to + // a) press the mousebutton to start a freehand drawing, or + // b) release the mousebutton to finish a freehand drawing + if (!sp_event_context_knot_mouseover(pc)) { + SnapManager &m = dt->namedview->snap_manager; + m.setup(dt); + m.preSnap(Inkscape::SnapCandidatePoint(p, Inkscape::SNAPSOURCE_NODE_HANDLE)); + m.unSetup(); + } + break; + } + return ret; +} + +static gint +pencil_handle_button_release(PencilTool *const pc, GdkEventButton const &revent) +{ + gint ret = FALSE; + + ToolBase *event_context = SP_EVENT_CONTEXT(pc); + if ( revent.button == 1 && pc->is_drawing && !event_context->space_panning) { + SPDesktop *const dt = pc->desktop; + + pc->is_drawing = false; + + /* Find desktop coordinates */ + Geom::Point p = dt->w2d(Geom::Point(revent.x, revent.y)); + + /* Test whether we hit any anchor. */ + SPDrawAnchor *anchor = spdc_test_inside(pc, Geom::Point(revent.x, + revent.y)); + + switch (pc->state) { + case SP_PENCIL_CONTEXT_IDLE: + /* Releasing button in idle mode means single click */ + /* We have already set up start point/anchor in button_press */ + if (!(revent.state & GDK_CONTROL_MASK)) { + // Ctrl+click creates a single point so only set context in ADDLINE mode when Ctrl isn't pressed + pc->state = SP_PENCIL_CONTEXT_ADDLINE; + } + ret = TRUE; + break; + case SP_PENCIL_CONTEXT_ADDLINE: + /* Finish segment now */ + if (anchor) { + p = anchor->dp; + } else { + spdc_endpoint_snap(pc, p, revent.state); + } + pc->ea = anchor; + spdc_set_endpoint(pc, p); + spdc_finish_endpoint(pc); + pc->state = SP_PENCIL_CONTEXT_IDLE; + sp_event_context_discard_delayed_snap_event(event_context); + ret = TRUE; + break; + case SP_PENCIL_CONTEXT_FREEHAND: + if (revent.state & GDK_MOD1_MASK) { + /* sketch mode: interpolate the sketched path and improve the current output path with the new interpolation. don't finish sketch */ + + sketch_interpolate(pc); + + if (pc->green_anchor) { + pc->green_anchor = sp_draw_anchor_destroy(pc->green_anchor); + } + + pc->state = SP_PENCIL_CONTEXT_SKETCH; + } else { + /* Finish segment now */ + /// \todo fixme: Clean up what follows (Lauris) + if (anchor) { + p = anchor->dp; + } else { + Geom::Point p_end = p; + spdc_endpoint_snap(pc, p_end, revent.state); + if (p_end != p) { + // then we must have snapped! + spdc_add_freehand_point(pc, p_end, revent.state); + } + } + + pc->ea = anchor; + /* Write curves to object */ + + dt->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("Finishing freehand")); + + interpolate(pc); + spdc_concat_colors_and_flush(pc, FALSE); + pc->sa = NULL; + pc->ea = NULL; + if (pc->green_anchor) { + pc->green_anchor = sp_draw_anchor_destroy(pc->green_anchor); + } + pc->state = SP_PENCIL_CONTEXT_IDLE; + // reset sketch mode too + pc->sketch_n = 0; + } + ret = TRUE; + break; + case SP_PENCIL_CONTEXT_SKETCH: + default: + break; + } + + if (pc->grab) { + /* Release grab now */ + sp_canvas_item_ungrab(pc->grab, revent.time); + pc->grab = NULL; + } + + ret = TRUE; + } + return ret; +} + +static void +pencil_cancel (PencilTool *const pc) +{ + if (pc->grab) { + /* Release grab now */ + sp_canvas_item_ungrab(pc->grab, 0); + pc->grab = NULL; + } + + pc->is_drawing = false; + pc->state = SP_PENCIL_CONTEXT_IDLE; + sp_event_context_discard_delayed_snap_event(SP_EVENT_CONTEXT(pc)); + + pc->red_curve->reset(); + sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(pc->red_bpath), NULL); + while (pc->green_bpaths) { + sp_canvas_item_destroy(SP_CANVAS_ITEM(pc->green_bpaths->data)); + pc->green_bpaths = g_slist_remove(pc->green_bpaths, pc->green_bpaths->data); + } + pc->green_curve->reset(); + if (pc->green_anchor) { + pc->green_anchor = sp_draw_anchor_destroy(pc->green_anchor); + } + + pc->message_context->clear(); + pc->message_context->flash(Inkscape::NORMAL_MESSAGE, _("Drawing cancelled")); + + pc->desktop->canvas->endForcedFullRedraws(); +} + +static gint +pencil_handle_key_press(PencilTool *const pc, guint const keyval, guint const state) +{ + gint ret = FALSE; + switch (keyval) { + case GDK_KEY_Up: + case GDK_KEY_Down: + case GDK_KEY_KP_Up: + case GDK_KEY_KP_Down: + // Prevent the zoom field from activation. + if (!mod_ctrl_only(state)) { + ret = TRUE; + } + break; + case GDK_KEY_Escape: + if (pc->npoints != 0) { + // if drawing, cancel, otherwise pass it up for deselecting + if (pc->state != SP_PENCIL_CONTEXT_IDLE) { + pencil_cancel (pc); + ret = TRUE; + } + } + break; + case GDK_KEY_z: + case GDK_KEY_Z: + if (mod_ctrl_only(state) && pc->npoints != 0) { + // if drawing, cancel, otherwise pass it up for undo + if (pc->state != SP_PENCIL_CONTEXT_IDLE) { + pencil_cancel (pc); + ret = TRUE; + } + } + break; + case GDK_KEY_g: + case GDK_KEY_G: + if (mod_shift_only(state)) { + sp_selection_to_guides(SP_EVENT_CONTEXT(pc)->desktop); + ret = true; + } + break; + case GDK_KEY_Alt_L: + case GDK_KEY_Alt_R: + case GDK_KEY_Meta_L: + case GDK_KEY_Meta_R: + if (pc->state == SP_PENCIL_CONTEXT_IDLE) { + pc->desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("Sketch mode: holding Alt interpolates between sketched paths. Release Alt to finalize.")); + } + break; + default: + break; + } + return ret; +} + +static gint +pencil_handle_key_release(PencilTool *const pc, guint const keyval, guint const /*state*/) +{ + gint ret = FALSE; + switch (keyval) { + case GDK_KEY_Alt_L: + case GDK_KEY_Alt_R: + case GDK_KEY_Meta_L: + case GDK_KEY_Meta_R: + if (pc->state == SP_PENCIL_CONTEXT_SKETCH) { + spdc_concat_colors_and_flush(pc, FALSE); + pc->sketch_n = 0; + pc->sa = NULL; + pc->ea = NULL; + if (pc->green_anchor) { + pc->green_anchor = sp_draw_anchor_destroy(pc->green_anchor); + } + pc->state = SP_PENCIL_CONTEXT_IDLE; + sp_event_context_discard_delayed_snap_event(SP_EVENT_CONTEXT(pc)); + pc->desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("Finishing freehand sketch")); + ret = TRUE; + } + break; + default: + break; + } + return ret; +} + +/** + * Reset points and set new starting point. + */ +static void +spdc_set_startpoint(PencilTool *const pc, Geom::Point const &p) +{ + pc->npoints = 0; + pc->red_curve_is_valid = false; + if (in_svg_plane(p)) { + pc->p[pc->npoints++] = p; + } +} + +/** + * Change moving endpoint position. + *
    + *
  • Ctrl constrains to moving to H/V direction, snapping in given direction. + *
  • Otherwise we snap freely to whatever attractors are available. + *
+ * + * Number of points is (re)set to 2 always, 2nd point is modified. + * We change RED curve. + */ +static void +spdc_set_endpoint(PencilTool *const pc, Geom::Point const &p) +{ + if (pc->npoints == 0) { + return; + /* May occur if first point wasn't in SVG plane (e.g. weird w2d transform, perhaps from bad + * zoom setting). + */ + } + g_return_if_fail( pc->npoints > 0 ); + + pc->red_curve->reset(); + if ( ( p == pc->p[0] ) + || !in_svg_plane(p) ) + { + pc->npoints = 1; + } else { + pc->p[1] = p; + pc->npoints = 2; + + pc->red_curve->moveto(pc->p[0]); + pc->red_curve->lineto(pc->p[1]); + pc->red_curve_is_valid = true; + + sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(pc->red_bpath), pc->red_curve); + } +} + +/** + * Finalize addline. + * + * \todo + * fixme: I'd like remove red reset from concat colors (lauris). + * Still not sure, how it will make most sense. + */ +static void +spdc_finish_endpoint(PencilTool *const pc) +{ + if ( ( pc->red_curve->is_empty() ) + || ( *(pc->red_curve->first_point()) == *(pc->red_curve->second_point()) ) ) + { + pc->red_curve->reset(); + sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(pc->red_bpath), NULL); + } else { + /* Write curves to object. */ + spdc_concat_colors_and_flush(pc, FALSE); + pc->sa = NULL; + pc->ea = NULL; + } +} + + +static void +spdc_add_freehand_point(PencilTool *pc, Geom::Point const &p, guint /*state*/) +{ + g_assert( pc->npoints > 0 ); + g_return_if_fail(unsigned(pc->npoints) < G_N_ELEMENTS(pc->p)); + + if ( ( p != pc->p[ pc->npoints - 1 ] ) + && in_svg_plane(p) ) + { + pc->ps.push_back(p); + pc->p[pc->npoints++] = p; + fit_and_split(pc); + } +} + +static inline double +square(double const x) +{ + return x * x; +} + +static void +interpolate(PencilTool *pc) +{ + if ( pc->ps.size() <= 1 ) { + return; + } + + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + double const tol = prefs->getDoubleLimited("/tools/freehand/pencil/tolerance", 10.0, 1.0, 100.0) * 0.4; + double const tolerance_sq = 0.02 * square( pc->desktop->w2d().descrim() * + tol) * exp(0.2*tol - 2); + + g_assert(is_zero(pc->req_tangent) + || is_unit_vector(pc->req_tangent)); + Geom::Point const tHatEnd(0, 0); + + guint n_points = pc->ps.size(); + pc->green_curve->reset(); + pc->red_curve->reset(); + pc->red_curve_is_valid = false; + + Geom::Point * b = g_new(Geom::Point, 4*n_points); + Geom::Point * points = g_new(Geom::Point, 4*n_points); + for (unsigned int i = 0; i < pc->ps.size(); i++) { + points[i] = pc->ps[i]; + } + + // worst case gives us a segment per point + int max_segs = 4*n_points; + + int const n_segs = Geom::bezier_fit_cubic_r(b, points, n_points, + tolerance_sq, max_segs); + + if ( n_segs > 0) + { + /* Fit and draw and reset state */ + pc->green_curve->moveto(b[0]); + for (int c = 0; c < n_segs; c++) { + pc->green_curve->curveto(b[4*c+1], b[4*c+2], b[4*c+3]); + } + sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(pc->red_bpath), pc->green_curve); + + /* Fit and draw and copy last point */ + g_assert(!pc->green_curve->is_empty()); + + /* Set up direction of next curve. */ + { + Geom::Curve const * last_seg = pc->green_curve->last_segment(); + g_assert( last_seg ); // Relevance: validity of (*last_seg) + pc->p[0] = last_seg->finalPoint(); + pc->npoints = 1; + Geom::Curve *last_seg_reverse = last_seg->reverse(); + Geom::Point const req_vec( -last_seg_reverse->unitTangentAt(0) ); + delete last_seg_reverse; + pc->req_tangent = ( ( Geom::is_zero(req_vec) || !in_svg_plane(req_vec) ) + ? Geom::Point(0, 0) + : Geom::unit_vector(req_vec) ); + } + } + g_free(b); + g_free(points); + pc->ps.clear(); +} + + +/* interpolates the sketched curve and tweaks the current sketch interpolation*/ +static void +sketch_interpolate(PencilTool *pc) +{ + if ( pc->ps.size() <= 1 ) { + return; + } + + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + double const tol = prefs->getDoubleLimited("/tools/freehand/pencil/tolerance", 10.0, 1.0, 100.0) * 0.4; + double const tolerance_sq = 0.02 * square( pc->desktop->w2d().descrim() * + tol) * exp(0.2*tol - 2); + + bool average_all_sketches = prefs->getBool("/tools/freehand/pencil/average_all_sketches", true); + + g_assert(is_zero(pc->req_tangent) + || is_unit_vector(pc->req_tangent)); + Geom::Point const tHatEnd(0, 0); + + guint n_points = pc->ps.size(); + pc->red_curve->reset(); + pc->red_curve_is_valid = false; + + Geom::Point * b = g_new(Geom::Point, 4*n_points); + Geom::Point * points = g_new(Geom::Point, 4*n_points); + for (unsigned i = 0; i < pc->ps.size(); i++) { + points[i] = pc->ps[i]; + } + + // worst case gives us a segment per point + int max_segs = 4*n_points; + + int const n_segs = Geom::bezier_fit_cubic_r(b, points, n_points, + tolerance_sq, max_segs); + + if ( n_segs > 0) + { + Geom::Path fit(b[0]); + for (int c = 0; c < n_segs; c++) { + fit.appendNew(b[4*c+1], b[4*c+2], b[4*c+3]); + } + Geom::Piecewise > fit_pwd2 = fit.toPwSb(); + + if ( pc->sketch_n > 0 ) { + double t =0.; + if (average_all_sketches) { + // Average = (sum of all) / n + // = (sum of all + new one) / n+1 + // = ((old average)*n + new one) / n+1 + t = pc->sketch_n / (pc->sketch_n + 1.); + } else { + t = 0.5; + } + pc->sketch_interpolation = Geom::lerp(t, fit_pwd2, pc->sketch_interpolation); + // simplify path, to eliminate small segments + Path *path = new Path; + path->LoadPathVector(Geom::path_from_piecewise(pc->sketch_interpolation, 0.01)); + path->Simplify(0.5); + Geom::PathVector *pathv = path->MakePathVector(); + pc->sketch_interpolation = (*pathv)[0].toPwSb(); + delete path; + delete pathv; + } else { + pc->sketch_interpolation = fit_pwd2; + } + pc->sketch_n++; + + pc->green_curve->reset(); + pc->green_curve->set_pathvector(Geom::path_from_piecewise(pc->sketch_interpolation, 0.01)); + sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(pc->red_bpath), pc->green_curve); + + /* Fit and draw and copy last point */ + g_assert(!pc->green_curve->is_empty()); + + /* Set up direction of next curve. */ + { + Geom::Curve const * last_seg = pc->green_curve->last_segment(); + g_assert( last_seg ); // Relevance: validity of (*last_seg) + pc->p[0] = last_seg->finalPoint(); + pc->npoints = 1; + Geom::Curve *last_seg_reverse = last_seg->reverse(); + Geom::Point const req_vec( -last_seg_reverse->unitTangentAt(0) ); + delete last_seg_reverse; + pc->req_tangent = ( ( Geom::is_zero(req_vec) || !in_svg_plane(req_vec) ) + ? Geom::Point(0, 0) + : Geom::unit_vector(req_vec) ); + } + } + g_free(b); + g_free(points); + pc->ps.clear(); +} + +static void +fit_and_split(PencilTool *pc) +{ + g_assert( pc->npoints > 1 ); + + double const tolerance_sq = 0; + + Geom::Point b[4]; + g_assert(is_zero(pc->req_tangent) + || is_unit_vector(pc->req_tangent)); + Geom::Point const tHatEnd(0, 0); + int const n_segs = Geom::bezier_fit_cubic_full(b, NULL, pc->p, pc->npoints, + pc->req_tangent, tHatEnd, + tolerance_sq, 1); + if ( n_segs > 0 + && unsigned(pc->npoints) < G_N_ELEMENTS(pc->p) ) + { + /* Fit and draw and reset state */ + pc->red_curve->reset(); + pc->red_curve->moveto(b[0]); + pc->red_curve->curveto(b[1], b[2], b[3]); + sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(pc->red_bpath), pc->red_curve); + pc->red_curve_is_valid = true; + } else { + /* Fit and draw and copy last point */ + + g_assert(!pc->red_curve->is_empty()); + + /* Set up direction of next curve. */ + { + Geom::Curve const * last_seg = pc->red_curve->last_segment(); + g_assert( last_seg ); // Relevance: validity of (*last_seg) + pc->p[0] = last_seg->finalPoint(); + pc->npoints = 1; + Geom::Curve *last_seg_reverse = last_seg->reverse(); + Geom::Point const req_vec( -last_seg_reverse->unitTangentAt(0) ); + delete last_seg_reverse; + pc->req_tangent = ( ( Geom::is_zero(req_vec) || !in_svg_plane(req_vec) ) + ? Geom::Point(0, 0) + : Geom::unit_vector(req_vec) ); + } + + + pc->green_curve->append_continuous(pc->red_curve, 0.0625); + SPCurve *curve = pc->red_curve->copy(); + + /// \todo fixme: + SPCanvasItem *cshape = sp_canvas_bpath_new(sp_desktop_sketch(pc->desktop), curve); + curve->unref(); + sp_canvas_bpath_set_stroke(SP_CANVAS_BPATH(cshape), pc->green_color, 1.0, SP_STROKE_LINEJOIN_MITER, SP_STROKE_LINECAP_BUTT); + + pc->green_bpaths = g_slist_prepend(pc->green_bpaths, cshape); + + pc->red_curve_is_valid = false; + } +} + +} +} +} + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/ui/tools/pencil-tool.h b/src/ui/tools/pencil-tool.h new file mode 100644 index 000000000..6ced9eb56 --- /dev/null +++ b/src/ui/tools/pencil-tool.h @@ -0,0 +1,68 @@ +#ifndef SEEN_PENCIL_CONTEXT_H +#define SEEN_PENCIL_CONTEXT_H + +/** \file + * PencilTool: a context for pencil tool events + */ + +#include "ui/tools/freehand-base.h" + +#define SP_PENCIL_CONTEXT(obj) (dynamic_cast((ToolBase*)obj)) +#define SP_IS_PENCIL_CONTEXT(obj) (dynamic_cast((const ToolBase*)obj) != NULL) + +namespace Inkscape { +namespace UI { +namespace Tools { + +enum PencilState { + SP_PENCIL_CONTEXT_IDLE, + SP_PENCIL_CONTEXT_ADDLINE, + SP_PENCIL_CONTEXT_FREEHAND, + SP_PENCIL_CONTEXT_SKETCH +}; + +/** + * PencilTool: a context for pencil tool events + */ +class PencilTool : public FreehandBase { +public: + PencilTool(); + virtual ~PencilTool(); + + Geom::Point p[16]; + gint npoints; + PencilState state; + Geom::Point req_tangent; + + bool is_drawing; + + std::vector ps; + + Geom::Piecewise > sketch_interpolation; // the current proposal from the sketched paths + unsigned sketch_n; // number of sketches done + + static const std::string prefsPath; + + virtual const std::string& getPrefsPath(); + +protected: + virtual void setup(); + virtual bool root_handler(GdkEvent* event); +}; + +} +} +} + +#endif /* !SEEN_PENCIL_CONTEXT_H */ + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/ui/tools/rect-tool.cpp b/src/ui/tools/rect-tool.cpp new file mode 100644 index 000000000..263fdea84 --- /dev/null +++ b/src/ui/tools/rect-tool.cpp @@ -0,0 +1,527 @@ +/* + * Rectangle drawing context + * + * Author: + * Lauris Kaplinski + * bulia byak + * Jon A. Cruz + * Abhishek Sharma + * + * Copyright (C) 2006 Johan Engelen + * Copyright (C) 2000-2005 authors + * Copyright (C) 2000-2001 Ximian, Inc. + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include "config.h" + +#include +#include +#include + +#include "macros.h" +#include "display/sp-canvas.h" +#include "sp-rect.h" +#include "document.h" +#include "document-undo.h" +#include "sp-namedview.h" +#include "selection.h" +#include "selection-chemistry.h" +#include "desktop-handles.h" +#include "snap.h" +#include "desktop.h" +#include "desktop-style.h" +#include "message-context.h" +#include "pixmaps/cursor-rect.xpm" +#include "ui/tools/rect-tool.h" +#include +#include "xml/repr.h" +#include "xml/node-event-vector.h" +#include "preferences.h" +#include "context-fns.h" +#include "shape-editor.h" +#include "verbs.h" +#include "display/sp-canvas-item.h" + +using Inkscape::DocumentUndo; + +#include "tool-factory.h" + +namespace Inkscape { +namespace UI { +namespace Tools { + +namespace { + ToolBase* createRectContext() { + return new RectTool(); + } + + bool rectContextRegistered = ToolFactory::instance().registerObject("/tools/shapes/rect", createRectContext); +} + +const std::string& RectTool::getPrefsPath() { + return RectTool::prefsPath; +} + +const std::string RectTool::prefsPath = "/tools/shapes/rect"; + +RectTool::RectTool() : ToolBase() { + this->cursor_shape = cursor_rect_xpm; + this->hot_x = 4; + this->hot_y = 4; + this->xp = 0; + this->yp = 0; + this->tolerance = 0; + this->within_tolerance = false; + this->item_to_select = NULL; + + this->rect = NULL; + + this->rx = 0.0; + this->ry = 0.0; +} + +void RectTool::finish() { + sp_canvas_item_ungrab(SP_CANVAS_ITEM(this->desktop->acetate), GDK_CURRENT_TIME); + + this->finishItem(); + this->sel_changed_connection.disconnect(); + + ToolBase::finish(); +} + +RectTool::~RectTool() { + this->enableGrDrag(false); + + this->sel_changed_connection.disconnect(); + + delete this->shape_editor; + this->shape_editor = NULL; + + /* fixme: This is necessary because we do not grab */ + if (this->rect) { + this->finishItem(); + } +} + +/** + * Callback that processes the "changed" signal on the selection; + * destroys old and creates new knotholder. + */ +void RectTool::selection_changed(Inkscape::Selection* selection) { + this->shape_editor->unset_item(SH_KNOTHOLDER); + this->shape_editor->set_item(selection->singleItem(), SH_KNOTHOLDER); +} + +void RectTool::setup() { + ToolBase::setup(); + + this->shape_editor = new ShapeEditor(this->desktop); + + SPItem *item = sp_desktop_selection(this->desktop)->singleItem(); + if (item) { + this->shape_editor->set_item(item, SH_KNOTHOLDER); + } + + this->sel_changed_connection.disconnect(); + this->sel_changed_connection = sp_desktop_selection(this->desktop)->connectChanged( + sigc::mem_fun(this, &RectTool::selection_changed) + ); + + sp_event_context_read(this, "rx"); + sp_event_context_read(this, "ry"); + + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + if (prefs->getBool("/tools/shapes/selcue")) { + this->enableSelectionCue(); + } + + if (prefs->getBool("/tools/shapes/gradientdrag")) { + this->enableGrDrag(); + } +} + +void RectTool::set(const Inkscape::Preferences::Entry& val) { + /* fixme: Proper error handling for non-numeric data. Use a locale-independent function like + * g_ascii_strtod (or a thin wrapper that does the right thing for invalid values inf/nan). */ + Glib::ustring name = val.getEntryName(); + + if ( name == "rx" ) { + this->rx = val.getDoubleLimited(); // prevents NaN and +/-Inf from messing up + } else if ( name == "ry" ) { + this->ry = val.getDoubleLimited(); + } +} + +bool RectTool::item_handler(SPItem* item, GdkEvent* event) { + gint ret = FALSE; + + switch (event->type) { + case GDK_BUTTON_PRESS: + if ( event->button.button == 1 && !this->space_panning) { + Inkscape::setup_for_drag_start(desktop, this, event); + ret = TRUE; + } + break; + // motion and release are always on root (why?) + default: + break; + } + + ret = ToolBase::item_handler(item, event); + + return ret; +} + +bool RectTool::root_handler(GdkEvent* event) { + static bool dragging; + + SPDesktop *desktop = this->desktop; + Inkscape::Selection *selection = sp_desktop_selection (desktop); + + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + + this->tolerance = prefs->getIntLimited("/options/dragtolerance/value", 0, 0, 100); + + gint ret = FALSE; + + switch (event->type) { + case GDK_BUTTON_PRESS: + if (event->button.button == 1 && !this->space_panning) { + Geom::Point const button_w(event->button.x, event->button.y); + + // save drag origin + this->xp = (gint) button_w[Geom::X]; + this->yp = (gint) button_w[Geom::Y]; + this->within_tolerance = true; + + // remember clicked item, disregarding groups, honoring Alt + this->item_to_select = sp_event_context_find_item (desktop, button_w, event->button.state & GDK_MOD1_MASK, TRUE); + + dragging = true; + + /* Position center */ + Geom::Point button_dt(desktop->w2d(button_w)); + this->center = button_dt; + + /* Snap center */ + SnapManager &m = desktop->namedview->snap_manager; + m.setup(desktop); + m.freeSnapReturnByRef(button_dt, Inkscape::SNAPSOURCE_NODE_HANDLE); + m.unSetup(); + this->center = button_dt; + + sp_canvas_item_grab(SP_CANVAS_ITEM(desktop->acetate), + ( GDK_KEY_PRESS_MASK | + GDK_BUTTON_RELEASE_MASK | + GDK_POINTER_MOTION_MASK | + GDK_POINTER_MOTION_HINT_MASK | + GDK_BUTTON_PRESS_MASK ), + NULL, event->button.time); + + ret = TRUE; + } + break; + case GDK_MOTION_NOTIFY: + if ( dragging + && (event->motion.state & GDK_BUTTON1_MASK) && !this->space_panning) + { + if ( this->within_tolerance + && ( abs( (gint) event->motion.x - this->xp ) < this->tolerance ) + && ( abs( (gint) event->motion.y - this->yp ) < this->tolerance ) ) { + break; // do not drag if we're within tolerance from origin + } + // Once the user has moved farther than tolerance from the original location + // (indicating they intend to draw, not click), then always process the + // motion notify coordinates as given (no snapping back to origin) + this->within_tolerance = false; + + Geom::Point const motion_w(event->motion.x, event->motion.y); + Geom::Point motion_dt(desktop->w2d(motion_w)); + + this->drag(motion_dt, event->motion.state); // this will also handle the snapping + gobble_motion_events(GDK_BUTTON1_MASK); + ret = TRUE; + } else if (!sp_event_context_knot_mouseover(this)) { + SnapManager &m = desktop->namedview->snap_manager; + m.setup(desktop); + + Geom::Point const motion_w(event->motion.x, event->motion.y); + Geom::Point motion_dt(desktop->w2d(motion_w)); + + m.preSnap(Inkscape::SnapCandidatePoint(motion_dt, Inkscape::SNAPSOURCE_NODE_HANDLE)); + m.unSetup(); + } + break; + case GDK_BUTTON_RELEASE: + this->xp = this->yp = 0; + if (event->button.button == 1 && !this->space_panning) { + dragging = false; + sp_event_context_discard_delayed_snap_event(this); + + if (!this->within_tolerance) { + // we've been dragging, finish the rect + this->finishItem(); + } else if (this->item_to_select) { + // no dragging, select clicked item if any + if (event->button.state & GDK_SHIFT_MASK) { + selection->toggle(this->item_to_select); + } else { + selection->set(this->item_to_select); + } + } else { + // click in an empty space + selection->clear(); + } + + this->item_to_select = NULL; + ret = TRUE; + sp_canvas_item_ungrab(SP_CANVAS_ITEM(desktop->acetate), + event->button.time); + } + break; + case GDK_KEY_PRESS: + switch (get_group0_keyval (&event->key)) { + case GDK_KEY_Alt_L: + case GDK_KEY_Alt_R: + case GDK_KEY_Control_L: + case GDK_KEY_Control_R: + case GDK_KEY_Shift_L: + case GDK_KEY_Shift_R: + case GDK_KEY_Meta_L: // Meta is when you press Shift+Alt (at least on my machine) + case GDK_KEY_Meta_R: + if (!dragging){ + sp_event_show_modifier_tip (this->defaultMessageContext(), event, + _("Ctrl: make square or integer-ratio rect, lock a rounded corner circular"), + _("Shift: draw around the starting point"), + NULL); + } + break; + case GDK_KEY_Up: + case GDK_KEY_Down: + case GDK_KEY_KP_Up: + case GDK_KEY_KP_Down: + // prevent the zoom field from activation + if (!MOD__CTRL_ONLY(event)) + ret = TRUE; + break; + + case GDK_KEY_x: + case GDK_KEY_X: + if (MOD__ALT_ONLY(event)) { + desktop->setToolboxFocusTo ("altx-rect"); + ret = TRUE; + } + break; + + case GDK_KEY_g: + case GDK_KEY_G: + if (MOD__SHIFT_ONLY(event)) { + sp_selection_to_guides(desktop); + ret = true; + } + break; + + case GDK_KEY_Escape: + if (dragging) { + dragging = false; + sp_event_context_discard_delayed_snap_event(this); + // if drawing, cancel, otherwise pass it up for deselecting + this->cancel(); + ret = TRUE; + } + break; + + case GDK_KEY_space: + if (dragging) { + sp_canvas_item_ungrab(SP_CANVAS_ITEM(desktop->acetate), + event->button.time); + dragging = false; + sp_event_context_discard_delayed_snap_event(this); + + if (!this->within_tolerance) { + // we've been dragging, finish the rect + this->finishItem(); + } + // do not return true, so that space would work switching to selector + } + break; + + case GDK_KEY_Delete: + case GDK_KEY_KP_Delete: + case GDK_KEY_BackSpace: + ret = this->deleteSelectedDrag(MOD__CTRL_ONLY(event)); + break; + + default: + break; + } + break; + case GDK_KEY_RELEASE: + switch (get_group0_keyval (&event->key)) { + case GDK_KEY_Alt_L: + case GDK_KEY_Alt_R: + case GDK_KEY_Control_L: + case GDK_KEY_Control_R: + case GDK_KEY_Shift_L: + case GDK_KEY_Shift_R: + case GDK_KEY_Meta_L: // Meta is when you press Shift+Alt + case GDK_KEY_Meta_R: + this->defaultMessageContext()->clear(); + break; + default: + break; + } + break; + default: + break; + } + + if (!ret) { + ret = ToolBase::root_handler(event); + } + + return ret; +} + +void RectTool::drag(Geom::Point const pt, guint state) { + SPDesktop *desktop = this->desktop; + + if (!this->rect) { + if (Inkscape::have_viable_layer(desktop, this->message_context) == false) { + return; + } + + // Create object + Inkscape::XML::Document *xml_doc = this->desktop->doc()->getReprDoc(); + Inkscape::XML::Node *repr = xml_doc->createElement("svg:rect"); + + // Set style + sp_desktop_apply_style_tool (desktop, repr, "/tools/shapes/rect", false); + + this->rect = SP_RECT(desktop->currentLayer()->appendChildRepr(repr)); + Inkscape::GC::release(repr); + + this->rect->transform = SP_ITEM(desktop->currentLayer())->i2doc_affine().inverse(); + this->rect->updateRepr(); + + desktop->canvas->forceFullRedrawAfterInterruptions(5); + } + + Geom::Rect const r = Inkscape::snap_rectangular_box(desktop, this->rect, pt, this->center, state); + + this->rect->setPosition(r.min()[Geom::X], r.min()[Geom::Y], r.dimensions()[Geom::X], r.dimensions()[Geom::Y]); + + if (this->rx != 0.0) { + this->rect->setRx(true, this->rx); + } + + if (this->ry != 0.0) { + if (this->rx == 0.0) + this->rect->setRy(true, CLAMP(this->ry, 0, MIN(r.dimensions()[Geom::X], r.dimensions()[Geom::Y])/2)); + else + this->rect->setRy(true, CLAMP(this->ry, 0, r.dimensions()[Geom::Y])); + } + + // status text + double rdimx = r.dimensions()[Geom::X]; + double rdimy = r.dimensions()[Geom::Y]; + + Inkscape::Util::Quantity rdimx_q = Inkscape::Util::Quantity(rdimx, "px"); + Inkscape::Util::Quantity rdimy_q = Inkscape::Util::Quantity(rdimy, "px"); + GString *xs = g_string_new(rdimx_q.string(desktop->namedview->doc_units).c_str()); + GString *ys = g_string_new(rdimy_q.string(desktop->namedview->doc_units).c_str()); + + if (state & GDK_CONTROL_MASK) { + int ratio_x, ratio_y; + bool is_golden_ratio = false; + + if (fabs (rdimx) > fabs (rdimy)) { + if (fabs(rdimx / rdimy - goldenratio) < 1e-6) { + is_golden_ratio = true; + } + + ratio_x = (int) rint (rdimx / rdimy); + ratio_y = 1; + } else { + if (fabs(rdimy / rdimx - goldenratio) < 1e-6) { + is_golden_ratio = true; + } + + ratio_x = 1; + ratio_y = (int) rint (rdimy / rdimx); + } + + if (!is_golden_ratio) { + this->message_context->setF(Inkscape::IMMEDIATE_MESSAGE, _("Rectangle: %s × %s (constrained to ratio %d:%d); with Shift to draw around the starting point"), xs->str, ys->str, ratio_x, ratio_y); + } else { + if (ratio_y == 1) { + this->message_context->setF(Inkscape::IMMEDIATE_MESSAGE, _("Rectangle: %s × %s (constrained to golden ratio 1.618 : 1); with Shift to draw around the starting point"), xs->str, ys->str); + } else { + this->message_context->setF(Inkscape::IMMEDIATE_MESSAGE, _("Rectangle: %s × %s (constrained to golden ratio 1 : 1.618); with Shift to draw around the starting point"), xs->str, ys->str); + } + } + } else { + this->message_context->setF(Inkscape::IMMEDIATE_MESSAGE, _("Rectangle: %s × %s; with Ctrl to make square or integer-ratio rectangle; with Shift to draw around the starting point"), xs->str, ys->str); + } + + g_string_free(xs, FALSE); + g_string_free(ys, FALSE); +} + +void RectTool::finishItem() { + this->message_context->clear(); + + if (this->rect != NULL) { + if (this->rect->width.computed == 0 || this->rect->height.computed == 0) { + this->cancel(); // Don't allow the creating of zero sized rectangle, for example when the start and and point snap to the snap grid point + return; + } + + this->rect->updateRepr(); + this->rect->doWriteTransform(this->rect->getRepr(), this->rect->transform, NULL, true); + + this->desktop->canvas->endForcedFullRedraws(); + + sp_desktop_selection(this->desktop)->set(this->rect); + + DocumentUndo::done(sp_desktop_document(this->desktop), SP_VERB_CONTEXT_RECT, _("Create rectangle")); + + this->rect = NULL; + } +} + +void RectTool::cancel(){ + sp_desktop_selection(this->desktop)->clear(); + sp_canvas_item_ungrab(SP_CANVAS_ITEM(this->desktop->acetate), 0); + + if (this->rect != NULL) { + this->rect->deleteObject(); + this->rect = NULL; + } + + this->within_tolerance = false; + this->xp = 0; + this->yp = 0; + this->item_to_select = NULL; + + this->desktop->canvas->endForcedFullRedraws(); + + DocumentUndo::cancel(sp_desktop_document(this->desktop)); +} + +} +} +} + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/ui/tools/rect-tool.h b/src/ui/tools/rect-tool.h new file mode 100644 index 000000000..a50fd7b24 --- /dev/null +++ b/src/ui/tools/rect-tool.h @@ -0,0 +1,65 @@ +#ifndef __SP_RECT_CONTEXT_H__ +#define __SP_RECT_CONTEXT_H__ + +/* + * Rectangle drawing context + * + * Author: + * Lauris Kaplinski + * + * Copyright (C) 2000 Lauris Kaplinski + * Copyright (C) 2000-2001 Ximian, Inc. + * Copyright (C) 2002 Lauris Kaplinski + * + * Released under GNU GPL + */ + +#include +#include +#include <2geom/point.h> +#include "ui/tools/tool-base.h" + +#include "sp-rect.h" + +#define SP_RECT_CONTEXT(obj) (dynamic_cast((Inkscape::UI::Tools::ToolBase*)obj)) +#define SP_IS_RECT_CONTEXT(obj) (dynamic_cast((const Inkscape::UI::Tools::ToolBase*)obj) != NULL) + +namespace Inkscape { +namespace UI { +namespace Tools { + +class RectTool : public ToolBase { +public: + RectTool(); + virtual ~RectTool(); + + static const std::string prefsPath; + + virtual void setup(); + virtual void finish(); + virtual void set(const Inkscape::Preferences::Entry& val); + virtual bool root_handler(GdkEvent* event); + virtual bool item_handler(SPItem* item, GdkEvent* event); + + virtual const std::string& getPrefsPath(); + +private: + SPRect *rect; + Geom::Point center; + + gdouble rx; /* roundness radius (x direction) */ + gdouble ry; /* roundness radius (y direction) */ + + sigc::connection sel_changed_connection; + + void drag(Geom::Point const pt, guint state); + void finishItem(); + void cancel(); + void selection_changed(Inkscape::Selection* selection); +}; + +} +} +} + +#endif diff --git a/src/ui/tools/select-tool.cpp b/src/ui/tools/select-tool.cpp new file mode 100644 index 000000000..498882417 --- /dev/null +++ b/src/ui/tools/select-tool.cpp @@ -0,0 +1,1252 @@ +/* + * Selection and transformation context + * + * Authors: + * Lauris Kaplinski + * bulia byak + * Abhishek Sharma + * Jon A. Cruz + * + * Copyright (C) 2010 authors + * Copyright (C) 2006 Johan Engelen + * Copyright (C) 1999-2005 Authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif +#include +#include +#include +#include "macros.h" +#include "rubberband.h" +#include "document.h" +#include "document-undo.h" +#include "selection.h" +#include "sp-cursor.h" +#include "style.h" +#include "pixmaps/cursor-select-m.xpm" +#include "pixmaps/cursor-select-d.xpm" +#include "pixmaps/handles.xpm" +#include + +#include "ui/tools/select-tool.h" +#include "selection-chemistry.h" +#ifdef WITH_DBUS +#include "extension/dbus/document-interface.h" +#endif +#include "desktop.h" +#include "desktop-handles.h" +#include "sp-root.h" +#include "preferences.h" +#include "tools-switch.h" +#include "message-stack.h" +#include "selection-describer.h" +#include "seltrans.h" +#include "box3d.h" +#include "display/sp-canvas.h" +#include "display/sp-canvas-item.h" +#include "display/drawing-item.h" +#include "tool-factory.h" + +using Inkscape::DocumentUndo; + +GdkPixbuf *handles[13]; + +namespace Inkscape { +namespace UI { +namespace Tools { + +static GdkCursor *CursorSelectMouseover = NULL; +static GdkCursor *CursorSelectDragging = NULL; + +static gint rb_escaped = 0; // if non-zero, rubberband was canceled by esc, so the next button release should not deselect +static gint drag_escaped = 0; // if non-zero, drag was canceled by esc + +namespace { + ToolBase* createSelectContext() { + return new SelectTool(); + } + + bool selectContextRegistered = ToolFactory::instance().registerObject("/tools/select", createSelectContext); +} + +const std::string& SelectTool::getPrefsPath() { + return SelectTool::prefsPath; +} + +const std::string SelectTool::prefsPath = "/tools/select"; + + +//Creates rotated variations for handles +static void +sp_load_handles(int start, int count, char const **xpm) { + handles[start] = gdk_pixbuf_new_from_xpm_data((gchar const **)xpm); + for(int i = start + 1; i < start + count; i++) { + // We use either the original at *start or previous loop item to rotate + handles[i] = gdk_pixbuf_rotate_simple(handles[i-1], GDK_PIXBUF_ROTATE_CLOCKWISE); + } +} + +SelectTool::SelectTool() : ToolBase() { + this->grabbed = 0; + this->item = 0; + + this->dragging = FALSE; + this->moved = FALSE; + this->button_press_shift = false; + this->button_press_ctrl = false; + this->button_press_alt = false; + this->cycling_items = NULL; + this->cycling_items_cmp = NULL; + this->cycling_items_selected_before = NULL; + this->cycling_cur_item = NULL; + this->cycling_wrap = true; + this->_seltrans = NULL; + this->_describer = NULL; + + + // cursors in select context + CursorSelectMouseover = sp_cursor_new_from_xpm(cursor_select_m_xpm , 1, 1); + CursorSelectDragging = sp_cursor_new_from_xpm(cursor_select_d_xpm , 1, 1); + + // selection handles + sp_load_handles(0, 2, handle_scale_xpm); + sp_load_handles(2, 2, handle_stretch_xpm); + sp_load_handles(4, 4, handle_rotate_xpm); + sp_load_handles(8, 4, handle_skew_xpm); + sp_load_handles(12, 1, handle_center_xpm); +} + +//static gint xp = 0, yp = 0; // where drag started +//static gint tolerance = 0; +//static bool within_tolerance = false; +static bool is_cycling = false; +static bool moved_while_cycling = false; +ToolBase *prev_event_context = NULL; + + +SelectTool::~SelectTool() { + this->enableGrDrag(false); + + if (this->grabbed) { + sp_canvas_item_ungrab(this->grabbed, GDK_CURRENT_TIME); + this->grabbed = NULL; + } + + delete this->_seltrans; + this->_seltrans = NULL; + + delete this->_describer; + this->_describer = NULL; + + if (CursorSelectDragging) { +#if GTK_CHECK_VERSION(3,0,0) + g_object_unref(CursorSelectDragging); +#else + gdk_cursor_unref (CursorSelectDragging); +#endif + CursorSelectDragging = NULL; + } + + if (CursorSelectMouseover) { +#if GTK_CHECK_VERSION(3,0,0) + g_object_unref(CursorSelectMouseover); +#else + gdk_cursor_unref (CursorSelectMouseover); +#endif + CursorSelectMouseover = NULL; + } +} + +void SelectTool::setup() { + ToolBase::setup(); + + this->_describer = new Inkscape::SelectionDescriber( + desktop->selection, + desktop->messageStack(), + _("Click selection to toggle scale/rotation handles"), + _("No objects selected. Click, Shift+click, Alt+scroll mouse on top of objects, or drag around objects to select.") + ); + + this->_seltrans = new Inkscape::SelTrans(desktop); + + sp_event_context_read(this, "show"); + sp_event_context_read(this, "transform"); + + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + + if (prefs->getBool("/tools/select/gradientdrag")) { + this->enableGrDrag(); + } +} + +void SelectTool::set(const Inkscape::Preferences::Entry& val) { + Glib::ustring path = val.getEntryName(); + + if (path == "show") { + if (val.getString() == "outline") { + this->_seltrans->setShow(Inkscape::SelTrans::SHOW_OUTLINE); + } else { + this->_seltrans->setShow(Inkscape::SelTrans::SHOW_CONTENT); + } + } +} + +bool SelectTool::sp_select_context_abort() { + Inkscape::SelTrans *seltrans = this->_seltrans; + + if (this->dragging) { + if (this->moved) { // cancel dragging an object + seltrans->ungrab(); + this->moved = FALSE; + this->dragging = FALSE; + sp_event_context_discard_delayed_snap_event(this); + drag_escaped = 1; + + if (this->item) { + // only undo if the item is still valid + if (this->item->document) { + DocumentUndo::undo(sp_desktop_document(desktop)); + } + + sp_object_unref( this->item, NULL); + } else if (this->button_press_ctrl) { + // NOTE: This is a workaround to a bug. + // When the ctrl key is held, sc->item is not defined + // so in this case (only), we skip the object doc check + DocumentUndo::undo(sp_desktop_document(desktop)); + } + this->item = NULL; + + SP_EVENT_CONTEXT(this)->desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("Move canceled.")); + return true; + } + } else { + if (Inkscape::Rubberband::get(desktop)->is_started()) { + Inkscape::Rubberband::get(desktop)->stop(); + rb_escaped = 1; + SP_EVENT_CONTEXT(this)->defaultMessageContext()->clear(); + SP_EVENT_CONTEXT(this)->desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("Selection canceled.")); + return true; + } + } + return false; +} + +static bool +key_is_a_modifier (guint key) { + return (key == GDK_KEY_Alt_L || + key == GDK_KEY_Alt_R || + key == GDK_KEY_Control_L || + key == GDK_KEY_Control_R || + key == GDK_KEY_Shift_L || + key == GDK_KEY_Shift_R || + key == GDK_KEY_Meta_L || // Meta is when you press Shift+Alt (at least on my machine) + key == GDK_KEY_Meta_R); +} + +static void +sp_select_context_up_one_layer(SPDesktop *desktop) +{ + /* Click in empty place, go up one level -- but don't leave a layer to root. + * + * (Rationale: we don't usually allow users to go to the root, since that + * detracts from the layer metaphor: objects at the root level can in front + * of or behind layers. Whereas it's fine to go to the root if editing + * a document that has no layers (e.g. a non-Inkscape document).) + * + * Once we support editing SVG "islands" (e.g. embedded in an xhtml + * document), we might consider further restricting the below to disallow + * leaving a layer to go to a non-layer. + */ + SPObject *const current_layer = desktop->currentLayer(); + if (current_layer) { + SPObject *const parent = current_layer->parent; + if ( parent + && ( parent->parent + || !( SP_IS_GROUP(current_layer) + && ( SPGroup::LAYER + == SP_GROUP(current_layer)->layerMode() ) ) ) ) + { + desktop->setCurrentLayer(parent); + if (SP_IS_GROUP(current_layer) && SPGroup::LAYER != SP_GROUP(current_layer)->layerMode()) + sp_desktop_selection(desktop)->set(current_layer); + } + } +} + +bool SelectTool::item_handler(SPItem* item, GdkEvent* event) { + gint ret = FALSE; + + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + tolerance = prefs->getIntLimited("/options/dragtolerance/value", 0, 0, 100); + + // make sure we still have valid objects to move around + if (this->item && this->item->document == NULL) { + this->sp_select_context_abort(); + } + + switch (event->type) { + case GDK_BUTTON_PRESS: + if (event->button.button == 1 && !this->space_panning) { + /* Left mousebutton */ + + // save drag origin + xp = (gint) event->button.x; + yp = (gint) event->button.y; + within_tolerance = true; + + // remember what modifiers were on before button press + this->button_press_shift = (event->button.state & GDK_SHIFT_MASK) ? true : false; + this->button_press_ctrl = (event->button.state & GDK_CONTROL_MASK) ? true : false; + this->button_press_alt = (event->button.state & GDK_MOD1_MASK) ? true : false; + + if (event->button.state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK | GDK_MOD1_MASK)) { + // if shift or ctrl was pressed, do not move objects; + // pass the event to root handler which will perform rubberband, shift-click, ctrl-click, ctrl-drag + } else { + GdkWindow* window = gtk_widget_get_window (GTK_WIDGET (sp_desktop_canvas(desktop))); + + this->dragging = TRUE; + this->moved = FALSE; + + gdk_window_set_cursor(window, CursorSelectDragging); + + desktop->canvas->forceFullRedrawAfterInterruptions(5); + + // remember the clicked item in this->item: + if (this->item) { + sp_object_unref(this->item, NULL); + this->item = NULL; + } + + this->item = sp_event_context_find_item (desktop, + Geom::Point(event->button.x, event->button.y), event->button.state & GDK_MOD1_MASK, FALSE); + sp_object_ref(this->item, NULL); + + rb_escaped = drag_escaped = 0; + + if (this->grabbed) { + sp_canvas_item_ungrab(this->grabbed, event->button.time); + this->grabbed = NULL; + } + + sp_canvas_item_grab(SP_CANVAS_ITEM(desktop->drawing), + GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK | GDK_BUTTON_RELEASE_MASK | GDK_BUTTON_PRESS_MASK | + GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK, + NULL, event->button.time); + + this->grabbed = SP_CANVAS_ITEM(desktop->drawing); + + desktop->canvas->forceFullRedrawAfterInterruptions(5); + + ret = TRUE; + } + } else if (event->button.button == 3) { + // right click; do not eat it so that right-click menu can appear, but cancel dragging & rubberband + this->sp_select_context_abort(); + } + break; + + case GDK_ENTER_NOTIFY: { + if (!desktop->isWaitingCursor() && !this->dragging) { + GdkWindow* window = gtk_widget_get_window (GTK_WIDGET (sp_desktop_canvas(desktop))); + + gdk_window_set_cursor(window, CursorSelectMouseover); + } + break; + } + case GDK_LEAVE_NOTIFY: + if (!desktop->isWaitingCursor() && !this->dragging) { + GdkWindow* window = gtk_widget_get_window (GTK_WIDGET (sp_desktop_canvas(desktop))); + + gdk_window_set_cursor(window, this->cursor); + } + break; + + case GDK_KEY_PRESS: + if (get_group0_keyval (&event->key) == GDK_KEY_space) { + if (this->dragging && this->grabbed) { + /* stamping mode: show content mode moving */ + _seltrans->stamp(); + ret = TRUE; + } + } else if (get_group0_keyval (&event->key) == GDK_KEY_Tab) { + if (this->dragging && this->grabbed) { + _seltrans->getNextClosestPoint(false); + ret = TRUE; + } + } else if (get_group0_keyval (&event->key) == GDK_KEY_ISO_Left_Tab) { + if (this->dragging && this->grabbed) { + _seltrans->getNextClosestPoint(true); + ret = TRUE; + } + } + break; + + default: + break; + } + + if (!ret) { + ret = ToolBase::item_handler(item, event); + } + + return ret; +} + +void SelectTool::sp_select_context_cycle_through_items(Inkscape::Selection *selection, GdkEventScroll *scroll_event, bool shift_pressed) { + if (!this->cycling_cur_item) { + return; + } + + Inkscape::DrawingItem *arenaitem; + SPItem *item = SP_ITEM(this->cycling_cur_item->data); + + // Deactivate current item + if (!g_list_find(this->cycling_items_selected_before, item) && selection->includes(item)) { + selection->remove(item); + } + + arenaitem = item->get_arenaitem(desktop->dkey); + arenaitem->setOpacity(0.3); + + // Find next item and activate it + GList *next; + if (scroll_event->direction == GDK_SCROLL_UP) { + next = this->cycling_cur_item->next; + if (next == NULL && this->cycling_wrap) + next = this->cycling_items; + } else { + next = this->cycling_cur_item->prev; + if (next == NULL && this->cycling_wrap) + next = g_list_last(this->cycling_items); + } + + if (next) { + this->cycling_cur_item = next; + item = SP_ITEM(this->cycling_cur_item->data); + } + + arenaitem = item->get_arenaitem(desktop->dkey); + arenaitem->setOpacity(1.0); + + if (shift_pressed) { + selection->add(item); + } else { + selection->set(item); + } +} + + +static void +sp_select_context_reset_opacities(ToolBase *event_context) +{ + // SPDesktop *desktop = event_context->desktop; + SelectTool *sc = SP_SELECT_CONTEXT(event_context); + Inkscape::DrawingItem *arenaitem; + for (GList *l = sc->cycling_items; l != NULL; l = g_list_next(l)) { + arenaitem = SP_ITEM(l->data)->get_arenaitem(event_context->desktop->dkey); + arenaitem->setOpacity(SP_SCALE24_TO_FLOAT(SP_ITEM(l->data)->style->opacity.value)); + } + g_list_free(sc->cycling_items); + g_list_free(sc->cycling_items_selected_before); + g_list_free(sc->cycling_items_cmp); + sc->cycling_items = NULL; + sc->cycling_items_selected_before = NULL; + sc->cycling_cur_item = NULL; + sc->cycling_items_cmp = NULL; +} + +bool SelectTool::root_handler(GdkEvent* event) { + SPItem *item = NULL; + SPItem *item_at_point = NULL, *group_at_point = NULL, *item_in_group = NULL; + gint ret = FALSE; + + Inkscape::Selection *selection = sp_desktop_selection(desktop); + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + + // make sure we still have valid objects to move around + if (this->item && this->item->document == NULL) { + this->sp_select_context_abort(); + } + + switch (event->type) { + case GDK_2BUTTON_PRESS: + if (event->button.button == 1) { + if (!selection->isEmpty()) { + SPItem *clicked_item = static_cast(selection->itemList()->data); + + if (SP_IS_GROUP(clicked_item) && !SP_IS_BOX3D(clicked_item)) { // enter group if it's not a 3D box + desktop->setCurrentLayer(reinterpret_cast(clicked_item)); + sp_desktop_selection(desktop)->clear(); + this->dragging = false; + sp_event_context_discard_delayed_snap_event(this); + + desktop->canvas->endForcedFullRedraws(); + } else { // switch tool + Geom::Point const button_pt(event->button.x, event->button.y); + Geom::Point const p(desktop->w2d(button_pt)); + tools_switch_by_item (desktop, clicked_item, p); + } + } else { + sp_select_context_up_one_layer(desktop); + } + + ret = TRUE; + } + break; + + case GDK_BUTTON_PRESS: + if (event->button.button == 1 && !this->space_panning) { + // save drag origin + xp = (gint) event->button.x; + yp = (gint) event->button.y; + within_tolerance = true; + + Geom::Point const button_pt(event->button.x, event->button.y); + Geom::Point const p(desktop->w2d(button_pt)); + + if (event->button.state & GDK_MOD1_MASK) { + Inkscape::Rubberband::get(desktop)->setMode(RUBBERBAND_MODE_TOUCHPATH); + } + + Inkscape::Rubberband::get(desktop)->start(desktop, p); + + if (this->grabbed) { + sp_canvas_item_ungrab(this->grabbed, event->button.time); + this->grabbed = NULL; + } + + sp_canvas_item_grab(SP_CANVAS_ITEM(desktop->acetate), + GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK | GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK, + NULL, event->button.time); + + this->grabbed = SP_CANVAS_ITEM(desktop->acetate); + + // remember what modifiers were on before button press + this->button_press_shift = (event->button.state & GDK_SHIFT_MASK) ? true : false; + this->button_press_ctrl = (event->button.state & GDK_CONTROL_MASK) ? true : false; + this->button_press_alt = (event->button.state & GDK_MOD1_MASK) ? true : false; + + this->moved = FALSE; + + rb_escaped = drag_escaped = 0; + + ret = TRUE; + } else if (event->button.button == 3) { + // right click; do not eat it so that right-click menu can appear, but cancel dragging & rubberband + this->sp_select_context_abort(); + } + break; + + case GDK_MOTION_NOTIFY: + { + if (is_cycling) + { + moved_while_cycling = true; + prev_event_context = this; + } + + tolerance = prefs->getIntLimited("/options/dragtolerance/value", 0, 0, 100); + + if ((event->motion.state & GDK_BUTTON1_MASK) && !this->space_panning) { + Geom::Point const motion_pt(event->motion.x, event->motion.y); + Geom::Point const p(desktop->w2d(motion_pt)); + + if ( within_tolerance + && ( abs( (gint) event->motion.x - xp ) < tolerance ) + && ( abs( (gint) event->motion.y - yp ) < tolerance ) ) { + break; // do not drag if we're within tolerance from origin + } + // Once the user has moved farther than tolerance from the original location + // (indicating they intend to move the object, not click), then always process the + // motion notify coordinates as given (no snapping back to origin) + within_tolerance = false; + + if (this->button_press_ctrl || (this->button_press_alt && !this->button_press_shift && !selection->isEmpty())) { + // if it's not click and ctrl or alt was pressed (the latter with some selection + // but not with shift) we want to drag rather than rubberband + this->dragging = TRUE; + + GdkWindow* window = gtk_widget_get_window (GTK_WIDGET (sp_desktop_canvas(desktop))); + + gdk_window_set_cursor(window, CursorSelectDragging); + + desktop->canvas->forceFullRedrawAfterInterruptions(5); + } + + if (this->dragging) { + /* User has dragged fast, so we get events on root (lauris)*/ + // not only that; we will end up here when ctrl-dragging as well + // and also when we started within tolerance, but trespassed tolerance outside of item + Inkscape::Rubberband::get(desktop)->stop(); + this->defaultMessageContext()->clear(); + + item_at_point = desktop->getItemAtPoint(Geom::Point(event->button.x, event->button.y), FALSE); + + if (!item_at_point) { // if no item at this point, try at the click point (bug 1012200) + item_at_point = desktop->getItemAtPoint(Geom::Point(xp, yp), FALSE); + } + + if (item_at_point || this->moved || this->button_press_alt) { + // drag only if starting from an item, or if something is already grabbed, or if alt-dragging + if (!this->moved) { + item_in_group = desktop->getItemAtPoint(Geom::Point(event->button.x, event->button.y), TRUE); + group_at_point = desktop->getGroupAtPoint(Geom::Point(event->button.x, event->button.y)); + + if (SP_IS_LAYER(selection->single())) { + group_at_point = SP_GROUP(selection->single()); + } + + // group-at-point is meant to be topmost item if it's a group, + // not topmost group of all items at point + if (group_at_point != item_in_group && + !(group_at_point && item_at_point && + group_at_point->isAncestorOf(item_at_point))) { + group_at_point = NULL; + } + + // if neither a group nor an item (possibly in a group) at point are selected, set selection to the item at point + if ((!item_in_group || !selection->includes(item_in_group)) && + (!group_at_point || !selection->includes(group_at_point)) + && !this->button_press_alt) { + // select what is under cursor + if (!_seltrans->isEmpty()) { + _seltrans->resetState(); + } + + // when simply ctrl-dragging, we don't want to go into groups + if (item_at_point && !selection->includes(item_at_point)) { + selection->set(item_at_point); + } + } // otherwise, do not change selection so that dragging selected-within-group items, as well as alt-dragging, is possible + + _seltrans->grab(p, -1, -1, FALSE, TRUE); + this->moved = TRUE; + } + + if (!_seltrans->isEmpty()) { + _seltrans->moveTo(p, event->button.state); + } + + desktop->scroll_to_point(p); + gobble_motion_events(GDK_BUTTON1_MASK); + ret = TRUE; + } else { + this->dragging = FALSE; + sp_event_context_discard_delayed_snap_event(this); + desktop->canvas->endForcedFullRedraws(); + } + } else { + if (Inkscape::Rubberband::get(desktop)->is_started()) { + Inkscape::Rubberband::get(desktop)->move(p); + + if (Inkscape::Rubberband::get(desktop)->getMode() == RUBBERBAND_MODE_TOUCHPATH) { + this->defaultMessageContext()->set(Inkscape::NORMAL_MESSAGE, _("Draw over objects to select them; release Alt to switch to rubberband selection")); + } else { + this->defaultMessageContext()->set(Inkscape::NORMAL_MESSAGE, _("Drag around objects to select them; press Alt to switch to touch selection")); + } + + gobble_motion_events(GDK_BUTTON1_MASK); + } + } + } + break; + } + case GDK_BUTTON_RELEASE: + xp = yp = 0; + + if ((event->button.button == 1) && (this->grabbed) && !this->space_panning) { + if (this->dragging) { + GdkWindow* window; + + if (this->moved) { + // item has been moved + _seltrans->ungrab(); + this->moved = FALSE; +#ifdef WITH_DBUS + dbus_send_ping(desktop, this->item); +#endif + } else if (this->item && !drag_escaped) { + // item has not been moved -> simply a click, do selecting + if (!selection->isEmpty()) { + if (event->button.state & GDK_SHIFT_MASK) { + // with shift, toggle selection + _seltrans->resetState(); + selection->toggle(this->item); + } else { + SPObject* single = selection->single(); + // without shift, increase state (i.e. toggle scale/rotation handles) + if (selection->includes(this->item)) { + _seltrans->increaseState(); + } else if (SP_IS_LAYER(single) && single->isAncestorOf(this->item)) { + _seltrans->increaseState(); + } else { + _seltrans->resetState(); + selection->set(this->item); + } + } + } else { // simple or shift click, no previous selection + _seltrans->resetState(); + selection->set(this->item); + } + } + + this->dragging = FALSE; + window = gtk_widget_get_window (GTK_WIDGET (sp_desktop_canvas(desktop))); + + gdk_window_set_cursor(window, CursorSelectMouseover); + sp_event_context_discard_delayed_snap_event(this); + desktop->canvas->endForcedFullRedraws(); + + if (this->item) { + sp_object_unref( this->item, NULL); + } + + this->item = NULL; + } else { + Inkscape::Rubberband *r = Inkscape::Rubberband::get(desktop); + + if (r->is_started() && !within_tolerance) { + // this was a rubberband drag + GSList *items = NULL; + + if (r->getMode() == RUBBERBAND_MODE_RECT) { + Geom::OptRect const b = r->getRectangle(); + items = sp_desktop_document(desktop)->getItemsInBox(desktop->dkey, *b); + } else if (r->getMode() == RUBBERBAND_MODE_TOUCHPATH) { + items = sp_desktop_document(desktop)->getItemsAtPoints(desktop->dkey, r->getPoints()); + } + + _seltrans->resetState(); + r->stop(); + this->defaultMessageContext()->clear(); + + if (event->button.state & GDK_SHIFT_MASK) { + // with shift, add to selection + selection->addList (items); + } else { + // without shift, simply select anew + selection->setList (items); + } + + g_slist_free (items); + } else { // it was just a click, or a too small rubberband + r->stop(); + + if (this->button_press_shift && !rb_escaped && !drag_escaped) { + // this was a shift+click or alt+shift+click, select what was clicked upon + this->button_press_shift = false; + + if (this->button_press_ctrl) { + // go into groups, honoring Alt + item = sp_event_context_find_item (desktop, + Geom::Point(event->button.x, event->button.y), event->button.state & GDK_MOD1_MASK, TRUE); + this->button_press_ctrl = FALSE; + } else { + // don't go into groups, honoring Alt + item = sp_event_context_find_item (desktop, + Geom::Point(event->button.x, event->button.y), event->button.state & GDK_MOD1_MASK, FALSE); + } + + if (item) { + selection->toggle(item); + item = NULL; + } + + } else if ((this->button_press_ctrl || this->button_press_alt) && !rb_escaped && !drag_escaped) { // ctrl+click, alt+click + item = sp_event_context_find_item (desktop, + Geom::Point(event->button.x, event->button.y), this->button_press_alt, this->button_press_ctrl); + + this->button_press_ctrl = FALSE; + this->button_press_alt = FALSE; + + if (item) { + if (selection->includes(item)) { + _seltrans->increaseState(); + } else { + _seltrans->resetState(); + selection->set(item); + } + + item = NULL; + } + } else { // click without shift, simply deselect, unless with Alt or something was cancelled + if (!selection->isEmpty()) { + if (!(rb_escaped) && !(drag_escaped) && !(event->button.state & GDK_MOD1_MASK)) { + selection->clear(); + } + + rb_escaped = 0; + ret = TRUE; + } + } + } + + ret = TRUE; + } + + if (this->grabbed) { + sp_canvas_item_ungrab(this->grabbed, event->button.time); + this->grabbed = NULL; + } + + desktop->updateNow(); + } + + if (event->button.button == 1) { + Inkscape::Rubberband::get(desktop)->stop(); // might have been started in another tool! + } + + this->button_press_shift = false; + this->button_press_ctrl = false; + this->button_press_alt = false; + break; + + case GDK_SCROLL: { + GdkEventScroll *scroll_event = (GdkEventScroll*) event; + + if (scroll_event->state & GDK_MOD1_MASK) { // alt modified pressed + if (moved_while_cycling) + { + moved_while_cycling = false; + sp_select_context_reset_opacities(prev_event_context); + prev_event_context = NULL; + } + + is_cycling = true; + + bool shift_pressed = scroll_event->state & GDK_SHIFT_MASK; + + /* Rebuild list of items underneath the mouse pointer */ + Geom::Point p = desktop->d2w(desktop->point()); + SPItem *item = desktop->getItemAtPoint(p, true, NULL); + + // Save pointer to current cycle-item so that we can find it again later, in the freshly built list + SPItem *tmp_cur_item = this->cycling_cur_item ? SP_ITEM(this->cycling_cur_item->data) : NULL; + g_list_free(this->cycling_items); + this->cycling_items = NULL; + this->cycling_cur_item = NULL; + + while(item != NULL) { + this->cycling_items = g_list_append(this->cycling_items, item); + item = desktop->getItemAtPoint(p, true, item); + } + + /* Compare current item list with item list during previous scroll ... */ + GList *l1, *l2; + bool item_lists_differ = false; + + // Note that we can do an 'or' comparison in the loop because it is safe to call g_list_next with a NULL pointer. + for (l1 = this->cycling_items, l2 = this->cycling_items_cmp; l1 != NULL || l2 != NULL; l1 = g_list_next(l1), l2 = g_list_next(l2)) { + if ((l1 !=NULL && l2 == NULL) || (l1 == NULL && l2 != NULL) || (l1->data != l2->data)) { + item_lists_differ = true; + break; + } + } + + /* If list of items under mouse pointer hasn't changed ... */ + if (!item_lists_differ) { + // ... find current item in the freshly built list and continue cycling ... + // TODO: This wouldn't be necessary if cycling_cur_item pointed to an element of cycling_items_cmp instead + this->cycling_cur_item = g_list_find(this->cycling_items, tmp_cur_item); + g_assert(this->cycling_cur_item != NULL || this->cycling_items == NULL); + } else { + // ... otherwise reset opacities for outdated items ... + Inkscape::DrawingItem *arenaitem; + + for(GList *l = this->cycling_items_cmp; l != NULL; l = l->next) { + arenaitem = SP_ITEM(l->data)->get_arenaitem(desktop->dkey); + arenaitem->setOpacity(1.0); + //if (!shift_pressed && !g_list_find(this->cycling_items_selected_before, SP_ITEM(l->data)) && selection->includes(SP_ITEM(l->data))) + if (!g_list_find(this->cycling_items_selected_before, SP_ITEM(l->data)) && selection->includes(SP_ITEM(l->data))) { + selection->remove(SP_ITEM(l->data)); + } + } + + // ... clear the lists ... + g_list_free(this->cycling_items_cmp); + g_list_free(this->cycling_items_selected_before); + + this->cycling_items_cmp = NULL; + this->cycling_items_selected_before = NULL; + this->cycling_cur_item = NULL; + + // ... and rebuild them with the new items. + this->cycling_items_cmp = g_list_copy(this->cycling_items); + SPItem *item; + + for(GList *l = this->cycling_items; l != NULL; l = l->next) { + item = SP_ITEM(l->data); + arenaitem = item->get_arenaitem(desktop->dkey); + arenaitem->setOpacity(0.3); + + if (selection->includes(item)) { + // already selected items are stored separately, too + this->cycling_items_selected_before = g_list_append(this->cycling_items_selected_before, item); + } + } + + // set the current item to the bottommost one so that the cycling step below re-starts at the top + this->cycling_cur_item = g_list_last(this->cycling_items); + } + + this->cycling_wrap = prefs->getBool("/options/selection/cycleWrap", true); + + // Cycle through the items underneath the mouse pointer, one-by-one + this->sp_select_context_cycle_through_items(selection, scroll_event, shift_pressed); + + ret = TRUE; + + GtkWindow *w =GTK_WINDOW(gtk_widget_get_toplevel( GTK_WIDGET(desktop->canvas) )); + if (w) + { + gtk_window_present(w); + gtk_widget_grab_focus (GTK_WIDGET(desktop->canvas)); + } + } + break; + } + + case GDK_KEY_PRESS: // keybindings for select context + { + { + guint keyval = get_group0_keyval(&event->key); + + bool alt = ( MOD__ALT(event) + || (keyval == GDK_KEY_Alt_L) + || (keyval == GDK_KEY_Alt_R) + || (keyval == GDK_KEY_Meta_L) + || (keyval == GDK_KEY_Meta_R)); + + if (!key_is_a_modifier (keyval)) { + this->defaultMessageContext()->clear(); + } else if (this->grabbed || _seltrans->isGrabbed()) { + if (Inkscape::Rubberband::get(desktop)->is_started()) { + // if Alt then change cursor to moving cursor: + if (alt) { + Inkscape::Rubberband::get(desktop)->setMode(RUBBERBAND_MODE_TOUCHPATH); + } + } else { + // do not change the statusbar text when mousekey is down to move or transform the object, + // because the statusbar text is already updated somewhere else. + break; + } + } else { + sp_event_show_modifier_tip (this->defaultMessageContext(), event, + _("Ctrl: click to select in groups; drag to move hor/vert"), + _("Shift: click to toggle select; drag for rubberband selection"), + _("Alt: click to select under; scroll mouse-wheel to cycle-select; drag to move selected or select by touch")); + + // if Alt and nonempty selection, show moving cursor ("move selected"): + if (alt && !selection->isEmpty() && !desktop->isWaitingCursor()) { + GdkWindow* window = gtk_widget_get_window (GTK_WIDGET (sp_desktop_canvas(desktop))); + + gdk_window_set_cursor(window, CursorSelectDragging); + } + //*/ + break; + } + } + + gdouble const nudge = prefs->getDoubleLimited("/options/nudgedistance/value", 2, 0, 1000, "px"); // in px + gdouble const offset = prefs->getDoubleLimited("/options/defaultscale/value", 2, 0, 1000, "px"); + int const snaps = prefs->getInt("/options/rotationsnapsperpi/value", 12); + + switch (get_group0_keyval (&event->key)) { + case GDK_KEY_Left: // move selection left + case GDK_KEY_KP_Left: + if (!MOD__CTRL(event)) { // not ctrl + gint mul = 1 + gobble_key_events( get_group0_keyval(&event->key), 0); // with any mask + + if (MOD__ALT(event)) { // alt + if (MOD__SHIFT(event)) { + sp_selection_move_screen(sp_desktop_selection(desktop), mul*-10, 0); // shift + } else { + sp_selection_move_screen(sp_desktop_selection(desktop), mul*-1, 0); // no shift + } + } else { // no alt + if (MOD__SHIFT(event)) { + sp_selection_move(sp_desktop_selection(desktop), mul*-10*nudge, 0); // shift + } else { + sp_selection_move(sp_desktop_selection(desktop), mul*-nudge, 0); // no shift + } + } + + ret = TRUE; + } + break; + + case GDK_KEY_Up: // move selection up + case GDK_KEY_KP_Up: + if (!MOD__CTRL(event)) { // not ctrl + gint mul = 1 + gobble_key_events(get_group0_keyval(&event->key), 0); // with any mask + + if (MOD__ALT(event)) { // alt + if (MOD__SHIFT(event)) { + sp_selection_move_screen(sp_desktop_selection(desktop), 0, mul*10); // shift + } else { + sp_selection_move_screen(sp_desktop_selection(desktop), 0, mul*1); // no shift + } + } else { // no alt + if (MOD__SHIFT(event)) { + sp_selection_move(sp_desktop_selection(desktop), 0, mul*10*nudge); // shift + } else { + sp_selection_move(sp_desktop_selection(desktop), 0, mul*nudge); // no shift + } + } + + ret = TRUE; + } + break; + + case GDK_KEY_Right: // move selection right + case GDK_KEY_KP_Right: + if (!MOD__CTRL(event)) { // not ctrl + gint mul = 1 + gobble_key_events(get_group0_keyval(&event->key), 0); // with any mask + + if (MOD__ALT(event)) { // alt + if (MOD__SHIFT(event)) { + sp_selection_move_screen(sp_desktop_selection(desktop), mul*10, 0); // shift + } else { + sp_selection_move_screen(sp_desktop_selection(desktop), mul*1, 0); // no shift + } + } else { // no alt + if (MOD__SHIFT(event)) { + sp_selection_move(sp_desktop_selection(desktop), mul*10*nudge, 0); // shift + } else { + sp_selection_move(sp_desktop_selection(desktop), mul*nudge, 0); // no shift + } + } + + ret = TRUE; + } + break; + + case GDK_KEY_Down: // move selection down + case GDK_KEY_KP_Down: + if (!MOD__CTRL(event)) { // not ctrl + gint mul = 1 + gobble_key_events(get_group0_keyval(&event->key), 0); // with any mask + + if (MOD__ALT(event)) { // alt + if (MOD__SHIFT(event)) { + sp_selection_move_screen(sp_desktop_selection(desktop), 0, mul*-10); // shift + } else { + sp_selection_move_screen(sp_desktop_selection(desktop), 0, mul*-1); // no shift + } + } else { // no alt + if (MOD__SHIFT(event)) { + sp_selection_move(sp_desktop_selection(desktop), 0, mul*-10*nudge); // shift + } else { + sp_selection_move(sp_desktop_selection(desktop), 0, mul*-nudge); // no shift + } + } + + ret = TRUE; + } + break; + + case GDK_KEY_Escape: + if (!this->sp_select_context_abort()) { + selection->clear(); + } + + ret = TRUE; + break; + + case GDK_KEY_a: + case GDK_KEY_A: + if (MOD__CTRL_ONLY(event)) { + sp_edit_select_all(desktop); + ret = TRUE; + } + break; + + case GDK_KEY_space: + /* stamping mode: show outline mode moving */ + /* FIXME: Is next condition ok? (lauris) */ + if (this->dragging && this->grabbed) { + _seltrans->stamp(); + ret = TRUE; + } + break; + + case GDK_KEY_x: + case GDK_KEY_X: + if (MOD__ALT_ONLY(event)) { + desktop->setToolboxFocusTo ("altx"); + ret = TRUE; + } + break; + + case GDK_KEY_bracketleft: + if (MOD__ALT(event)) { + gint mul = 1 + gobble_key_events(get_group0_keyval(&event->key), 0); // with any mask + sp_selection_rotate_screen(selection, mul*1); + } else if (MOD__CTRL(event)) { + sp_selection_rotate(selection, 90); + } else if (snaps) { + sp_selection_rotate(selection, 180.0/snaps); + } + + ret = TRUE; + break; + + case GDK_KEY_bracketright: + if (MOD__ALT(event)) { + gint mul = 1 + gobble_key_events(get_group0_keyval(&event->key), 0); // with any mask + sp_selection_rotate_screen(selection, -1*mul); + } else if (MOD__CTRL(event)) { + sp_selection_rotate(selection, -90); + } else if (snaps) { + sp_selection_rotate(selection, -180.0/snaps); + } + + ret = TRUE; + break; + + case GDK_KEY_less: + case GDK_KEY_comma: + if (MOD__ALT(event)) { + gint mul = 1 + gobble_key_events(get_group0_keyval(&event->key), 0); // with any mask + sp_selection_scale_screen(selection, -2*mul); + } else if (MOD__CTRL(event)) { + sp_selection_scale_times(selection, 0.5); + } else { + gint mul = 1 + gobble_key_events(get_group0_keyval(&event->key), 0); // with any mask + sp_selection_scale(selection, -offset*mul); + } + + ret = TRUE; + break; + + case GDK_KEY_greater: + case GDK_KEY_period: + if (MOD__ALT(event)) { + gint mul = 1 + gobble_key_events(get_group0_keyval(&event->key), 0); // with any mask + sp_selection_scale_screen(selection, 2*mul); + } else if (MOD__CTRL(event)) { + sp_selection_scale_times(selection, 2); + } else { + gint mul = 1 + gobble_key_events(get_group0_keyval(&event->key), 0); // with any mask + sp_selection_scale(selection, offset*mul); + } + + ret = TRUE; + break; + + case GDK_KEY_Return: + if (MOD__CTRL_ONLY(event)) { + if (selection->singleItem()) { + SPItem *clicked_item = selection->singleItem(); + + if ( SP_IS_GROUP(clicked_item) || SP_IS_BOX3D(clicked_item)) { // enter group or a 3D box + desktop->setCurrentLayer(reinterpret_cast(clicked_item)); + sp_desktop_selection(desktop)->clear(); + } else { + this->desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("Selected object is not a group. Cannot enter.")); + } + } + + ret = TRUE; + } + break; + + case GDK_KEY_BackSpace: + if (MOD__CTRL_ONLY(event)) { + sp_select_context_up_one_layer(desktop); + ret = TRUE; + } + break; + + case GDK_KEY_s: + case GDK_KEY_S: + if (MOD__SHIFT_ONLY(event)) { + if (!selection->isEmpty()) { + _seltrans->increaseState(); + } + + ret = TRUE; + } + break; + + case GDK_KEY_g: + case GDK_KEY_G: + if (MOD__SHIFT_ONLY(event)) { + sp_selection_to_guides(desktop); + ret = true; + } + break; + + default: + break; + } + break; + } + case GDK_KEY_RELEASE: { + guint keyval = get_group0_keyval(&event->key); + if (key_is_a_modifier (keyval)) { + this->defaultMessageContext()->clear(); + } + + bool alt = ( MOD__ALT(event) + || (keyval == GDK_KEY_Alt_L) + || (keyval == GDK_KEY_Alt_R) + || (keyval == GDK_KEY_Meta_L) + || (keyval == GDK_KEY_Meta_R)); + + if (Inkscape::Rubberband::get(desktop)->is_started()) { + // if Alt then change cursor to moving cursor: + if (alt) { + Inkscape::Rubberband::get(desktop)->setMode(RUBBERBAND_MODE_RECT); + } + } else { + if (alt) { // TODO: Should we have a variable like is_cycling or is it harmless to run this piece of code each time? + // quit cycle-selection and reset opacities + if (is_cycling) + { + sp_select_context_reset_opacities(this); + is_cycling = false; + } + + } + } + + } + // set cursor to default. + if (!desktop->isWaitingCursor()) { + // Do we need to reset the cursor here on key release ? + //GdkWindow* window = gtk_widget_get_window (GTK_WIDGET (sp_desktop_canvas(desktop))); + //gdk_window_set_cursor(window, event_context->cursor); + } + break; + + default: + break; + } + + if (!ret) { + ret = ToolBase::root_handler(event); + } + + return ret; +} + +} +} +} + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 : diff --git a/src/ui/tools/select-tool.h b/src/ui/tools/select-tool.h new file mode 100644 index 000000000..b26fc03bc --- /dev/null +++ b/src/ui/tools/select-tool.h @@ -0,0 +1,73 @@ +#ifndef __SP_SELECT_CONTEXT_H__ +#define __SP_SELECT_CONTEXT_H__ + +/* + * Select tool + * + * Authors: + * Lauris Kaplinski + * + * Copyright (C) 1999-2002 authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include "ui/tools/tool-base.h" +#include + +#define SP_SELECT_CONTEXT(obj) (dynamic_cast((Inkscape::UI::Tools::ToolBase*)obj)) +#define SP_IS_SELECT_CONTEXT(obj) (dynamic_cast((const Inkscape::UI::Tools::ToolBase*)obj) != NULL) + +struct SPCanvasItem; + +namespace Inkscape { + class MessageContext; + class SelTrans; + class SelectionDescriber; +} + +namespace Inkscape { +namespace UI { +namespace Tools { + +class SelectTool : public ToolBase { +public: + SelectTool(); + virtual ~SelectTool(); + + guint dragging : 1; + guint moved : 1; + bool button_press_shift; + bool button_press_ctrl; + bool button_press_alt; + + GList *cycling_items; + GList *cycling_items_cmp; + GList *cycling_items_selected_before; + GList *cycling_cur_item; + bool cycling_wrap; + + SPItem *item; + SPCanvasItem *grabbed; + Inkscape::SelTrans *_seltrans; + Inkscape::SelectionDescriber *_describer; + + static const std::string prefsPath; + + virtual void setup(); + virtual void set(const Inkscape::Preferences::Entry& val); + virtual bool root_handler(GdkEvent* event); + virtual bool item_handler(SPItem* item, GdkEvent* event); + + virtual const std::string& getPrefsPath(); + +private: + bool sp_select_context_abort(); + void sp_select_context_cycle_through_items(Inkscape::Selection *selection, GdkEventScroll *scroll_event, bool shift_pressed); +}; + +} +} +} + +#endif diff --git a/src/ui/tools/spiral-tool.cpp b/src/ui/tools/spiral-tool.cpp new file mode 100644 index 000000000..3199ee06e --- /dev/null +++ b/src/ui/tools/spiral-tool.cpp @@ -0,0 +1,467 @@ +/* + * Spiral drawing context + * + * Authors: + * Mitsuru Oka + * Lauris Kaplinski + * bulia byak + * Jon A. Cruz + * Abhishek Sharma + * + * Copyright (C) 1999-2001 Lauris Kaplinski + * Copyright (C) 2001-2002 Mitsuru Oka + * + * Released under GNU GPL + */ + +#include "config.h" + +#include +#include +#include + +#include "macros.h" +#include "display/sp-canvas.h" +#include "sp-spiral.h" +#include "document.h" +#include "document-undo.h" +#include "sp-namedview.h" +#include "selection.h" +#include "desktop-handles.h" +#include "snap.h" +#include "desktop.h" +#include "desktop-style.h" +#include "message-context.h" +#include "pixmaps/cursor-spiral.xpm" +#include "ui/tools/spiral-tool.h" +#include +#include "xml/repr.h" +#include "xml/node-event-vector.h" +#include "preferences.h" +#include "context-fns.h" +#include "shape-editor.h" +#include "verbs.h" +#include "display/sp-canvas-item.h" + +using Inkscape::DocumentUndo; + +#include "tool-factory.h" + +namespace Inkscape { +namespace UI { +namespace Tools { + +namespace { + ToolBase* createSpiralContext() { + return new SpiralTool(); + } + + bool spiralContextRegistered = ToolFactory::instance().registerObject("/tools/shapes/spiral", createSpiralContext); +} + +const std::string& SpiralTool::getPrefsPath() { + return SpiralTool::prefsPath; +} + +const std::string SpiralTool::prefsPath = "/tools/shapes/spiral"; + +SpiralTool::SpiralTool() : ToolBase() { + this->cursor_shape = cursor_spiral_xpm; + this->hot_x = 4; + this->hot_y = 4; + this->xp = 0; + this->yp = 0; + this->tolerance = 0; + this->within_tolerance = false; + this->item_to_select = NULL; + + this->spiral = NULL; + + this->revo = 3.0; + this->exp = 1.0; + this->t0 = 0.0; +} + +void SpiralTool::finish() { + SPDesktop *desktop = this->desktop; + + sp_canvas_item_ungrab(SP_CANVAS_ITEM(desktop->acetate), GDK_CURRENT_TIME); + + this->finishItem(); + this->sel_changed_connection.disconnect(); + + ToolBase::finish(); +} + +SpiralTool::~SpiralTool() { + this->enableGrDrag(false); + + this->sel_changed_connection.disconnect(); + + delete this->shape_editor; + this->shape_editor = NULL; + + /* fixme: This is necessary because we do not grab */ + if (this->spiral) { + this->finishItem(); + } +} + +/** + * Callback that processes the "changed" signal on the selection; + * destroys old and creates new knotholder. + */ +void SpiralTool::selection_changed(Inkscape::Selection *selection) { + this->shape_editor->unset_item(SH_KNOTHOLDER); + this->shape_editor->set_item(selection->singleItem(), SH_KNOTHOLDER); +} + +void SpiralTool::setup() { + ToolBase::setup(); + + sp_event_context_read(this, "expansion"); + sp_event_context_read(this, "revolution"); + sp_event_context_read(this, "t0"); + + this->shape_editor = new ShapeEditor(this->desktop); + + SPItem *item = sp_desktop_selection(this->desktop)->singleItem(); + if (item) { + this->shape_editor->set_item(item, SH_KNOTHOLDER); + } + + Inkscape::Selection *selection = sp_desktop_selection(this->desktop); + this->sel_changed_connection.disconnect(); + + this->sel_changed_connection = selection->connectChanged(sigc::mem_fun(this, &SpiralTool::selection_changed)); + + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + + if (prefs->getBool("/tools/shapes/selcue")) { + this->enableSelectionCue(); + } + + if (prefs->getBool("/tools/shapes/gradientdrag")) { + this->enableGrDrag(); + } +} + +void SpiralTool::set(const Inkscape::Preferences::Entry& val) { + Glib::ustring name = val.getEntryName(); + + if (name == "expansion") { + this->exp = CLAMP(val.getDouble(), 0.0, 1000.0); + } else if (name == "revolution") { + this->revo = CLAMP(val.getDouble(3.0), 0.05, 40.0); + } else if (name == "t0") { + this->t0 = CLAMP(val.getDouble(), 0.0, 0.999); + } +} + +bool SpiralTool::root_handler(GdkEvent* event) { + static gboolean dragging; + + SPDesktop *desktop = this->desktop; + Inkscape::Selection *selection = sp_desktop_selection (desktop); + + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + this->tolerance = prefs->getIntLimited("/options/dragtolerance/value", 0, 0, 100); + + gint ret = FALSE; + + switch (event->type) { + case GDK_BUTTON_PRESS: + if (event->button.button == 1 && !this->space_panning) { + dragging = TRUE; + + this->center = Inkscape::setup_for_drag_start(desktop, this, event); + + SnapManager &m = desktop->namedview->snap_manager; + m.setup(desktop); + m.freeSnapReturnByRef(this->center, Inkscape::SNAPSOURCE_NODE_HANDLE); + m.unSetup(); + + sp_canvas_item_grab(SP_CANVAS_ITEM(desktop->acetate), + ( GDK_KEY_PRESS_MASK | + GDK_BUTTON_RELEASE_MASK | + GDK_POINTER_MOTION_MASK | + GDK_POINTER_MOTION_HINT_MASK | + GDK_BUTTON_PRESS_MASK ), + NULL, event->button.time); + ret = TRUE; + } + break; + + case GDK_MOTION_NOTIFY: + if (dragging && (event->motion.state & GDK_BUTTON1_MASK) && !this->space_panning) { + if ( this->within_tolerance + && ( abs( (gint) event->motion.x - this->xp ) < this->tolerance ) + && ( abs( (gint) event->motion.y - this->yp ) < this->tolerance ) ) { + break; // do not drag if we're within tolerance from origin + } + // Once the user has moved farther than tolerance from the original location + // (indicating they intend to draw, not click), then always process the + // motion notify coordinates as given (no snapping back to origin) + this->within_tolerance = false; + + Geom::Point const motion_w(event->motion.x, event->motion.y); + Geom::Point motion_dt(this->desktop->w2d(motion_w)); + + SnapManager &m = desktop->namedview->snap_manager; + m.setup(desktop, true, this->spiral); + m.freeSnapReturnByRef(motion_dt, Inkscape::SNAPSOURCE_NODE_HANDLE); + m.unSetup(); + + this->drag(motion_dt, event->motion.state); + + gobble_motion_events(GDK_BUTTON1_MASK); + + ret = TRUE; + } else if (!sp_event_context_knot_mouseover(this)) { + SnapManager &m = desktop->namedview->snap_manager; + m.setup(desktop); + Geom::Point const motion_w(event->motion.x, event->motion.y); + Geom::Point motion_dt(desktop->w2d(motion_w)); + m.preSnap(Inkscape::SnapCandidatePoint(motion_dt, Inkscape::SNAPSOURCE_NODE_HANDLE)); + m.unSetup(); + } + break; + + case GDK_BUTTON_RELEASE: + this->xp = this->yp = 0; + if (event->button.button == 1 && !this->space_panning) { + dragging = FALSE; + sp_event_context_discard_delayed_snap_event(this); + + if (!this->within_tolerance) { + // we've been dragging, finish the spiral + this->finishItem(); + } else if (this->item_to_select) { + // no dragging, select clicked item if any + if (event->button.state & GDK_SHIFT_MASK) { + selection->toggle(this->item_to_select); + } else { + selection->set(this->item_to_select); + } + } else { + // click in an empty space + selection->clear(); + } + + this->item_to_select = NULL; + ret = TRUE; + sp_canvas_item_ungrab(SP_CANVAS_ITEM(desktop->acetate), event->button.time); + } + break; + + case GDK_KEY_PRESS: + switch (get_group0_keyval(&event->key)) { + case GDK_KEY_Alt_R: + case GDK_KEY_Control_L: + case GDK_KEY_Control_R: + case GDK_KEY_Shift_L: + case GDK_KEY_Shift_R: + case GDK_KEY_Meta_L: // Meta is when you press Shift+Alt (at least on my machine) + case GDK_KEY_Meta_R: + sp_event_show_modifier_tip(this->defaultMessageContext(), event, + _("Ctrl: snap angle"), + NULL, + _("Alt: lock spiral radius")); + break; + case GDK_KEY_Up: + case GDK_KEY_Down: + case GDK_KEY_KP_Up: + case GDK_KEY_KP_Down: + // prevent the zoom field from activation + if (!MOD__CTRL_ONLY(event)) + ret = TRUE; + break; + + case GDK_KEY_x: + case GDK_KEY_X: + if (MOD__ALT_ONLY(event)) { + desktop->setToolboxFocusTo ("altx-spiral"); + ret = TRUE; + } + break; + + case GDK_KEY_Escape: + if (dragging) { + dragging = false; + sp_event_context_discard_delayed_snap_event(this); + // if drawing, cancel, otherwise pass it up for deselecting + this->cancel(); + ret = TRUE; + } + break; + + case GDK_KEY_space: + if (dragging) { + sp_canvas_item_ungrab(SP_CANVAS_ITEM(desktop->acetate), + event->button.time); + dragging = false; + sp_event_context_discard_delayed_snap_event(this); + + if (!this->within_tolerance) { + // we've been dragging, finish the spiral + this->finish(); + } + // do not return true, so that space would work switching to selector + } + break; + + case GDK_KEY_Delete: + case GDK_KEY_KP_Delete: + case GDK_KEY_BackSpace: + ret = this->deleteSelectedDrag(MOD__CTRL_ONLY(event)); + break; + + default: + break; + } + break; + + case GDK_KEY_RELEASE: + switch (get_group0_keyval(&event->key)) { + case GDK_KEY_Alt_L: + case GDK_KEY_Alt_R: + case GDK_KEY_Control_L: + case GDK_KEY_Control_R: + case GDK_KEY_Shift_L: + case GDK_KEY_Shift_R: + case GDK_KEY_Meta_L: // Meta is when you press Shift+Alt + case GDK_KEY_Meta_R: + this->defaultMessageContext()->clear(); + break; + + default: + break; + } + break; + + default: + break; + } + + if (!ret) { + ret = ToolBase::root_handler(event); + } + + return ret; +} + +void SpiralTool::drag(Geom::Point const &p, guint state) { + SPDesktop *desktop = SP_EVENT_CONTEXT(this)->desktop; + + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + int const snaps = prefs->getInt("/options/rotationsnapsperpi/value", 12); + + if (!this->spiral) { + if (Inkscape::have_viable_layer(desktop, this->message_context) == false) { + return; + } + + // Create object + Inkscape::XML::Document *xml_doc = SP_EVENT_CONTEXT_DOCUMENT(this)->getReprDoc(); + Inkscape::XML::Node *repr = xml_doc->createElement("svg:path"); + repr->setAttribute("sodipodi:type", "spiral"); + + // Set style + sp_desktop_apply_style_tool(desktop, repr, "/tools/shapes/spiral", false); + + this->spiral = SP_SPIRAL(desktop->currentLayer()->appendChildRepr(repr)); + Inkscape::GC::release(repr); + this->spiral->transform = SP_ITEM(desktop->currentLayer())->i2doc_affine().inverse(); + this->spiral->updateRepr(); + + desktop->canvas->forceFullRedrawAfterInterruptions(5); + } + + SnapManager &m = desktop->namedview->snap_manager; + m.setup(desktop, true, this->spiral); + Geom::Point pt2g = p; + m.freeSnapReturnByRef(pt2g, Inkscape::SNAPSOURCE_NODE_HANDLE); + m.unSetup(); + Geom::Point const p0 = desktop->dt2doc(this->center); + Geom::Point const p1 = desktop->dt2doc(pt2g); + + Geom::Point const delta = p1 - p0; + gdouble const rad = Geom::L2(delta); + + gdouble arg = Geom::atan2(delta) - 2.0*M_PI*this->spiral->revo; + + if (state & GDK_CONTROL_MASK) { + arg = sp_round(arg, M_PI/snaps); + } + + /* Fixme: these parameters should be got from dialog box */ + this->spiral->setPosition(p0[Geom::X], p0[Geom::Y], + /*expansion*/ this->exp, + /*revolution*/ this->revo, + rad, arg, + /*t0*/ this->t0); + + /* status text */ + Inkscape::Util::Quantity q = Inkscape::Util::Quantity(rad, "px"); + GString *rads = g_string_new(q.string(desktop->namedview->doc_units).c_str()); + this->message_context->setF(Inkscape::IMMEDIATE_MESSAGE, + _("Spiral: radius %s, angle %5g°; with Ctrl to snap angle"), + rads->str, sp_round((arg + 2.0*M_PI*this->spiral->revo)*180/M_PI, 0.0001)); + g_string_free(rads, FALSE); +} + +void SpiralTool::finishItem() { + this->message_context->clear(); + + if (this->spiral != NULL) { + if (this->spiral->rad == 0) { + this->cancel(); // Don't allow the creating of zero sized spiral, for example when the start and and point snap to the snap grid point + return; + } + + spiral->set_shape(); + spiral->updateRepr(SP_OBJECT_WRITE_EXT); + spiral->doWriteTransform(spiral->getRepr(), spiral->transform, NULL, true); + + this->desktop->canvas->endForcedFullRedraws(); + + sp_desktop_selection(this->desktop)->set(this->spiral); + DocumentUndo::done(sp_desktop_document(this->desktop), SP_VERB_CONTEXT_SPIRAL, _("Create spiral")); + + this->spiral = NULL; + } +} + +void SpiralTool::cancel() { + sp_desktop_selection(this->desktop)->clear(); + sp_canvas_item_ungrab(SP_CANVAS_ITEM(this->desktop->acetate), 0); + + if (this->spiral != NULL) { + this->spiral->deleteObject(); + this->spiral = NULL; + } + + this->within_tolerance = false; + this->xp = 0; + this->yp = 0; + this->item_to_select = NULL; + + this->desktop->canvas->endForcedFullRedraws(); + + DocumentUndo::cancel(sp_desktop_document(this->desktop)); +} + +} +} +} + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/ui/tools/spiral-tool.h b/src/ui/tools/spiral-tool.h new file mode 100644 index 000000000..416aeb7e4 --- /dev/null +++ b/src/ui/tools/spiral-tool.h @@ -0,0 +1,66 @@ +#ifndef __SP_SPIRAL_CONTEXT_H__ +#define __SP_SPIRAL_CONTEXT_H__ + +/** \file + * Spiral drawing context + */ +/* + * Authors: + * Mitsuru Oka + * Lauris Kaplinski + * + * Copyright (C) 1999-2001 Lauris Kaplinski + * Copyright (C) 2001-2002 Mitsuru Oka + * + * Released under GNU GPL + */ + +#include +#include +#include +#include <2geom/point.h> +#include "ui/tools/tool-base.h" + +#include "sp-spiral.h" + +#define SP_SPIRAL_CONTEXT(obj) (dynamic_cast((Inkscape::UI::Tools::ToolBase*)obj)) +#define SP_IS_SPIRAL_CONTEXT(obj) (dynamic_cast((const Inkscape::UI::Tools::ToolBase*)obj) != NULL) + +namespace Inkscape { +namespace UI { +namespace Tools { + +class SpiralTool : public ToolBase { +public: + SpiralTool(); + virtual ~SpiralTool(); + + static const std::string prefsPath; + + virtual void setup(); + virtual void finish(); + virtual void set(const Inkscape::Preferences::Entry& val); + virtual bool root_handler(GdkEvent* event); + + virtual const std::string& getPrefsPath(); + +private: + SPSpiral * spiral; + Geom::Point center; + gdouble revo; + gdouble exp; + gdouble t0; + + sigc::connection sel_changed_connection; + + void drag(Geom::Point const &p, guint state); + void finishItem(); + void cancel(); + void selection_changed(Inkscape::Selection *selection); +}; + +} +} +} + +#endif diff --git a/src/ui/tools/spray-tool.cpp b/src/ui/tools/spray-tool.cpp new file mode 100644 index 000000000..0ded1e44b --- /dev/null +++ b/src/ui/tools/spray-tool.cpp @@ -0,0 +1,890 @@ +/* + * Spray Tool + * + * Authors: + * Pierre-Antoine MARC + * Pierre CACLIN + * Aurel-Aimé MARMION + * Julien LERAY + * Benoît LAVORATA + * Vincent MONTAGNE + * Pierre BARBRY-BLOT + * Steren GIANNINI (steren.giannini@gmail.com) + * Jon A. Cruz + * Abhishek Sharma + * + * Copyright (C) 2009 authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include "config.h" + +#include + +#include "ui/dialog/dialog-manager.h" + +#include "svg/svg.h" + +#include +#include "macros.h" +#include "document.h" +#include "document-undo.h" +#include "selection.h" +#include "desktop.h" +#include "desktop-events.h" +#include "desktop-handles.h" +#include "message-context.h" +#include "pixmaps/cursor-spray.xpm" +#include +#include "xml/repr.h" +#include "context-fns.h" +#include "sp-item.h" +#include "inkscape.h" + +#include "splivarot.h" +#include "sp-item-group.h" +#include "sp-shape.h" +#include "sp-path.h" +#include "path-chemistry.h" + +#include "sp-text.h" +#include "sp-flowtext.h" +#include "display/sp-canvas.h" +#include "display/canvas-bpath.h" +#include "display/canvas-arena.h" +#include "display/curve.h" +#include "livarot/Shape.h" +#include <2geom/circle.h> +#include <2geom/transforms.h> +#include "preferences.h" +#include "style.h" +#include "box3d.h" +#include "sp-item-transform.h" +#include "filter-chemistry.h" + +#include "ui/tools/spray-tool.h" +#include "helper/action.h" +#include "verbs.h" + +#include + +#include +#include +#include + +using Inkscape::DocumentUndo; +using namespace std; + +#define DDC_RED_RGBA 0xff0000ff +#define DYNA_MIN_WIDTH 1.0e-6 + +#include "tool-factory.h" + +namespace Inkscape { +namespace UI { +namespace Tools { + +namespace { + ToolBase* createSprayContext() { + return new SprayTool(); + } + + bool sprayContextRegistered = ToolFactory::instance().registerObject("/tools/spray", createSprayContext); +} + +const std::string& SprayTool::getPrefsPath() { + return SprayTool::prefsPath; +} + +const std::string SprayTool::prefsPath = "/tools/spray"; + +/** + * This function returns pseudo-random numbers from a normal distribution + * @param mu : mean + * @param sigma : standard deviation ( > 0 ) + */ +inline double NormalDistribution(double mu, double sigma) +{ + // use Box Muller's algorithm + return mu + sigma * sqrt( -2.0 * log(g_random_double_range(0, 1)) ) * cos( 2.0*M_PI*g_random_double_range(0, 1) ); +} + +/* Method to rotate items */ +static void sp_spray_rotate_rel(Geom::Point c, SPDesktop */*desktop*/, SPItem *item, Geom::Rotate const &rotation) +{ + Geom::Translate const s(c); + Geom::Affine affine = Geom::Affine(s).inverse() * Geom::Affine(rotation) * Geom::Affine(s); + // Rotate item. + item->set_i2d_affine(item->i2dt_affine() * (Geom::Affine)affine); + // Use each item's own transform writer, consistent with sp_selection_apply_affine() + item->doWriteTransform(item->getRepr(), item->transform); + // Restore the center position (it's changed because the bbox center changed) + if (item->isCenterSet()) { + item->setCenter(c); + item->updateRepr(); + } +} + +/* Method to scale items */ +static void sp_spray_scale_rel(Geom::Point c, SPDesktop */*desktop*/, SPItem *item, Geom::Scale const &scale) +{ + Geom::Translate const s(c); + item->set_i2d_affine(item->i2dt_affine() * s.inverse() * scale * s); + item->doWriteTransform(item->getRepr(), item->transform); +} + +SprayTool::SprayTool() : ToolBase() { + this->usetilt = 0; + this->dilate_area = 0; + this->usetext = false; + this->population = 0; + this->is_drawing = false; + this->mode = 0; + this->usepressure = 0; + + this->cursor_shape = cursor_spray_xpm; + this->hot_x = 4; + this->hot_y = 4; + + /* attributes */ + this->dragging = FALSE; + this->distrib = 1; + this->width = 0.2; + this->force = 0.2; + this->ratio = 0; + this->tilt = 0; + this->mean = 0.2; + this->rotation_variation = 0; + this->standard_deviation = 0.2; + this->scale = 1; + this->scale_variation = 1; + this->pressure = TC_DEFAULT_PRESSURE; + + this->is_dilating = false; + this->has_dilated = false; +} + +SprayTool::~SprayTool() { + this->enableGrDrag(false); + this->style_set_connection.disconnect(); + + if (this->dilate_area) { + sp_canvas_item_destroy(this->dilate_area); + this->dilate_area = NULL; + } +} + +static bool is_transform_modes(gint mode) +{ + return (mode == SPRAY_MODE_COPY || + mode == SPRAY_MODE_CLONE || + mode == SPRAY_MODE_SINGLE_PATH || + mode == SPRAY_OPTION); +} + +void SprayTool::update_cursor(bool /*with_shift*/) { + guint num = 0; + gchar *sel_message = NULL; + + if (!desktop->selection->isEmpty()) { + num = g_slist_length(const_cast(desktop->selection->itemList())); + sel_message = g_strdup_printf(ngettext("%i object selected","%i objects selected",num), num); + } else { + sel_message = g_strdup_printf("%s", _("Nothing selected")); + } + + switch (this->mode) { + case SPRAY_MODE_COPY: + this->message_context->setF(Inkscape::NORMAL_MESSAGE, _("%s. Drag, click or click and scroll to spray copies of the initial selection."), sel_message); + break; + case SPRAY_MODE_CLONE: + this->message_context->setF(Inkscape::NORMAL_MESSAGE, _("%s. Drag, click or click and scroll to spray clones of the initial selection."), sel_message); + break; + case SPRAY_MODE_SINGLE_PATH: + this->message_context->setF(Inkscape::NORMAL_MESSAGE, _("%s. Drag, click or click and scroll to spray in a single path of the initial selection."), sel_message); + break; + default: + break; + } + + this->sp_event_context_update_cursor(); + g_free(sel_message); +} + +void SprayTool::setup() { + ToolBase::setup(); + + { + /* TODO: have a look at sp_dyna_draw_context_setup where the same is done.. generalize? at least make it an arcto! */ + Geom::PathVector path; + Geom::Circle(0, 0, 1).getPath(path); + + SPCurve *c = new SPCurve(path); + + this->dilate_area = sp_canvas_bpath_new(sp_desktop_controls(this->desktop), c); + c->unref(); + sp_canvas_bpath_set_fill(SP_CANVAS_BPATH(this->dilate_area), 0x00000000,(SPWindRule)0); + sp_canvas_bpath_set_stroke(SP_CANVAS_BPATH(this->dilate_area), 0xff9900ff, 1.0, SP_STROKE_LINEJOIN_MITER, SP_STROKE_LINECAP_BUTT); + sp_canvas_item_hide(this->dilate_area); + } + + this->is_drawing = false; + + sp_event_context_read(this, "distrib"); + sp_event_context_read(this, "width"); + sp_event_context_read(this, "ratio"); + sp_event_context_read(this, "tilt"); + sp_event_context_read(this, "rotation_variation"); + sp_event_context_read(this, "scale_variation"); + sp_event_context_read(this, "mode"); + sp_event_context_read(this, "population"); + sp_event_context_read(this, "force"); + sp_event_context_read(this, "mean"); + sp_event_context_read(this, "standard_deviation"); + sp_event_context_read(this, "usepressure"); + sp_event_context_read(this, "Scale"); + + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + if (prefs->getBool("/tools/spray/selcue")) { + this->enableSelectionCue(); + } + if (prefs->getBool("/tools/spray/gradientdrag")) { + this->enableGrDrag(); + } +} + +void SprayTool::set(const Inkscape::Preferences::Entry& val) { + Glib::ustring path = val.getEntryName(); + + if (path == "mode") { + this->mode = val.getInt(); + this->update_cursor(false); + } else if (path == "width") { + this->width = 0.01 * CLAMP(val.getInt(10), 1, 100); + } else if (path == "usepressure") { + this->usepressure = val.getBool(); + } else if (path == "population") { + this->population = 0.01 * CLAMP(val.getInt(10), 1, 100); + } else if (path == "rotation_variation") { + this->rotation_variation = CLAMP(val.getDouble(0.0), 0, 100.0); + } else if (path == "scale_variation") { + this->scale_variation = CLAMP(val.getDouble(1.0), 0, 100.0); + } else if (path == "standard_deviation") { + this->standard_deviation = 0.01 * CLAMP(val.getInt(10), 1, 100); + } else if (path == "mean") { + this->mean = 0.01 * CLAMP(val.getInt(10), 1, 100); +// Not implemented in the toolbar and preferences yet + } else if (path == "distribution") { + this->distrib = val.getInt(1); + } else if (path == "tilt") { + this->tilt = CLAMP(val.getDouble(0.1), 0, 1000.0); + } else if (path == "ratio") { + this->ratio = CLAMP(val.getDouble(), 0.0, 0.9); + } else if (path == "force") { + this->force = CLAMP(val.getDouble(1.0), 0, 1.0); + } +} + +static void sp_spray_extinput(SprayTool *tc, GdkEvent *event) +{ + if (gdk_event_get_axis(event, GDK_AXIS_PRESSURE, &tc->pressure)) { + tc->pressure = CLAMP(tc->pressure, TC_MIN_PRESSURE, TC_MAX_PRESSURE); + } else { + tc->pressure = TC_DEFAULT_PRESSURE; + } +} + +static double get_dilate_radius(SprayTool *tc) +{ + return 250 * tc->width/SP_EVENT_CONTEXT(tc)->desktop->current_zoom(); +} + +static double get_path_force(SprayTool *tc) +{ + double force = 8 * (tc->usepressure? tc->pressure : TC_DEFAULT_PRESSURE) + /sqrt(SP_EVENT_CONTEXT(tc)->desktop->current_zoom()); + if (force > 3) { + force += 4 * (force - 3); + } + return force * tc->force; +} + +static double get_path_mean(SprayTool *tc) +{ + return tc->mean; +} + +static double get_path_standard_deviation(SprayTool *tc) +{ + return tc->standard_deviation; +} + +static double get_move_force(SprayTool *tc) +{ + double force = (tc->usepressure? tc->pressure : TC_DEFAULT_PRESSURE); + return force * tc->force; +} + +static double get_move_mean(SprayTool *tc) +{ + return tc->mean; +} + +static double get_move_standard_deviation(SprayTool *tc) +{ + return tc->standard_deviation; +} + +/** + * Method to handle the distribution of the items + * @param[out] radius : radius of the position of the sprayed object + * @param[out] angle : angle of the position of the sprayed object + * @param[in] a : mean + * @param[in] s : standard deviation + * @param[in] choice : + + */ +static void random_position(double &radius, double &angle, double &a, double &s, int /*choice*/) +{ + // angle is taken from an uniform distribution + angle = g_random_double_range(0, M_PI*2.0); + + // radius is taken from a Normal Distribution + double radius_temp =-1; + while(!((radius_temp >= 0) && (radius_temp <=1 ))) + { + radius_temp = NormalDistribution(a, s); + } + // Because we are in polar coordinates, a special treatment has to be done to the radius. + // Otherwise, positions taken from an uniform repartition on radius and angle will not seam to + // be uniformily distributed on the disk (more at the center and less at the boundary). + // We counter this effect with a 0.5 exponent. This is empiric. + radius = pow(radius_temp, 0.5); + +} + +static bool sp_spray_recursive(SPDesktop *desktop, + Inkscape::Selection *selection, + SPItem *item, + Geom::Point p, + Geom::Point /*vector*/, + gint mode, + double radius, + double /*force*/, + double population, + double &scale, + double scale_variation, + bool /*reverse*/, + double mean, + double standard_deviation, + double ratio, + double tilt, + double rotation_variation, + gint _distrib) +{ + bool did = false; + + if (SP_IS_BOX3D(item) ) { + // convert 3D boxes to ordinary groups before spraying their shapes + item = box3d_convert_to_group(SP_BOX3D(item)); + selection->add(item); + } + + double _fid = g_random_double_range(0, 1); + double angle = g_random_double_range( - rotation_variation / 100.0 * M_PI , rotation_variation / 100.0 * M_PI ); + double _scale = g_random_double_range( 1.0 - scale_variation / 100.0, 1.0 + scale_variation / 100.0 ); + double dr; double dp; + random_position( dr, dp, mean, standard_deviation, _distrib ); + dr=dr*radius; + + if (mode == SPRAY_MODE_COPY) { + Geom::OptRect a = item->documentVisualBounds(); + if (a) { + SPItem *item_copied; + if(_fid <= population) + { + // duplicate + SPDocument *doc = item->document; + Inkscape::XML::Document* xml_doc = doc->getReprDoc(); + Inkscape::XML::Node *old_repr = item->getRepr(); + Inkscape::XML::Node *parent = old_repr->parent(); + Inkscape::XML::Node *copy = old_repr->duplicate(xml_doc); + parent->appendChild(copy); + + SPObject *new_obj = doc->getObjectByRepr(copy); + item_copied = SP_ITEM(new_obj); //convertion object->item + Geom::Point center=item->getCenter(); + sp_spray_scale_rel(center,desktop, item_copied, Geom::Scale(_scale,_scale)); + sp_spray_scale_rel(center,desktop, item_copied, Geom::Scale(scale,scale)); + + sp_spray_rotate_rel(center,desktop,item_copied, Geom::Rotate(angle)); + //Move the cursor p + Geom::Point move = (Geom::Point(cos(tilt)*cos(dp)*dr/(1-ratio)+sin(tilt)*sin(dp)*dr/(1+ratio), -sin(tilt)*cos(dp)*dr/(1-ratio)+cos(tilt)*sin(dp)*dr/(1+ratio)))+(p-a->midpoint()); + sp_item_move_rel(item_copied, Geom::Translate(move[Geom::X], -move[Geom::Y])); + did = true; + } + } + } else if (mode == SPRAY_MODE_SINGLE_PATH) { + + SPItem *father = NULL; //initial Object + SPItem *item_copied = NULL; //Projected Object + SPItem *unionResult = NULL; //previous union + SPItem *son = NULL; //father copy + + int i=1; + for (GSList *items = g_slist_copy(const_cast(selection->itemList())); + items != NULL; + items = items->next) { + + SPItem *item1 = SP_ITEM(items->data); + if (i == 1) { + father = item1; + } + if (i == 2) { + unionResult = item1; + } + i++; + } + SPDocument *doc = father->document; + Inkscape::XML::Document* xml_doc = doc->getReprDoc(); + Inkscape::XML::Node *old_repr = father->getRepr(); + Inkscape::XML::Node *parent = old_repr->parent(); + + Geom::OptRect a = father->documentVisualBounds(); + if (a) { + if (i == 2) { + Inkscape::XML::Node *copy1 = old_repr->duplicate(xml_doc); + parent->appendChild(copy1); + SPObject *new_obj1 = doc->getObjectByRepr(copy1); + son = SP_ITEM(new_obj1); // conversion object->item + unionResult = son; + Inkscape::GC::release(copy1); + } + + if (_fid <= population) { // Rules the population of objects sprayed + // duplicates the father + Inkscape::XML::Node *copy2 = old_repr->duplicate(xml_doc); + parent->appendChild(copy2); + SPObject *new_obj2 = doc->getObjectByRepr(copy2); + item_copied = SP_ITEM(new_obj2); + + // Move around the cursor + Geom::Point move = (Geom::Point(cos(tilt)*cos(dp)*dr/(1-ratio)+sin(tilt)*sin(dp)*dr/(1+ratio), -sin(tilt)*cos(dp)*dr/(1-ratio)+cos(tilt)*sin(dp)*dr/(1+ratio)))+(p-a->midpoint()); + + Geom::Point center=father->getCenter(); + sp_spray_scale_rel(center, desktop, item_copied, Geom::Scale(_scale, _scale)); + sp_spray_scale_rel(center, desktop, item_copied, Geom::Scale(scale, scale)); + sp_spray_rotate_rel(center, desktop, item_copied, Geom::Rotate(angle)); + sp_item_move_rel(item_copied, Geom::Translate(move[Geom::X], -move[Geom::Y])); + + // union and duplication + selection->clear(); + selection->add(item_copied); + selection->add(unionResult); + sp_selected_path_union_skip_undo(selection, selection->desktop()); + selection->add(father); + Inkscape::GC::release(copy2); + did = true; + } + } + } else if (mode == SPRAY_MODE_CLONE) { + Geom::OptRect a = item->documentVisualBounds(); + if (a) { + if(_fid <= population) { + SPItem *item_copied; + SPDocument *doc = item->document; + Inkscape::XML::Document* xml_doc = doc->getReprDoc(); + Inkscape::XML::Node *old_repr = item->getRepr(); + Inkscape::XML::Node *parent = old_repr->parent(); + + // Creation of the clone + Inkscape::XML::Node *clone = xml_doc->createElement("svg:use"); + // Ad the clone to the list of the father's sons + parent->appendChild(clone); + // Generates the link between father and son attributes + gchar *href_str = g_strdup_printf("#%s", old_repr->attribute("id")); + clone->setAttribute("xlink:href", href_str, false); + g_free(href_str); + + SPObject *clone_object = doc->getObjectByRepr(clone); + // conversion object->item + item_copied = SP_ITEM(clone_object); + Geom::Point center = item->getCenter(); + sp_spray_scale_rel(center, desktop, item_copied, Geom::Scale(_scale, _scale)); + sp_spray_scale_rel(center, desktop, item_copied, Geom::Scale(scale, scale)); + sp_spray_rotate_rel(center, desktop, item_copied, Geom::Rotate(angle)); + Geom::Point move = (Geom::Point(cos(tilt)*cos(dp)*dr/(1-ratio)+sin(tilt)*sin(dp)*dr/(1+ratio), -sin(tilt)*cos(dp)*dr/(1-ratio)+cos(tilt)*sin(dp)*dr/(1+ratio)))+(p-a->midpoint()); + sp_item_move_rel(item_copied, Geom::Translate(move[Geom::X], -move[Geom::Y])); + + Inkscape::GC::release(clone); + + did = true; + } + } + } + + return did; +} + +static bool sp_spray_dilate(SprayTool *tc, Geom::Point /*event_p*/, Geom::Point p, Geom::Point vector, bool reverse) +{ + Inkscape::Selection *selection = sp_desktop_selection(SP_EVENT_CONTEXT(tc)->desktop); + SPDesktop *desktop = SP_EVENT_CONTEXT(tc)->desktop; + + if (selection->isEmpty()) { + return false; + } + + bool did = false; + double radius = get_dilate_radius(tc); + double path_force = get_path_force(tc); + if (radius == 0 || path_force == 0) { + return false; + } + double path_mean = get_path_mean(tc); + if (radius == 0 || path_mean == 0) { + return false; + } + double path_standard_deviation = get_path_standard_deviation(tc); + if (radius == 0 || path_standard_deviation == 0) { + return false; + } + double move_force = get_move_force(tc); + double move_mean = get_move_mean(tc); + double move_standard_deviation = get_move_standard_deviation(tc); + + for (GSList *items = g_slist_copy(const_cast(selection->itemList())); + items != NULL; + items = items->next) { + + SPItem *item = SP_ITEM(items->data); + + if (is_transform_modes(tc->mode)) { + if (sp_spray_recursive(desktop, selection, item, p, vector, tc->mode, radius, move_force, tc->population, tc->scale, tc->scale_variation, reverse, move_mean, move_standard_deviation, tc->ratio, tc->tilt, tc->rotation_variation, tc->distrib)) + did = true; + } else { + if (sp_spray_recursive(desktop, selection, item, p, vector, tc->mode, radius, path_force, tc->population, tc->scale, tc->scale_variation, reverse, path_mean, path_standard_deviation, tc->ratio, tc->tilt, tc->rotation_variation, tc->distrib)) + did = true; + } + } + + return did; +} + +static void sp_spray_update_area(SprayTool *tc) +{ + double radius = get_dilate_radius(tc); + Geom::Affine const sm ( Geom::Scale(radius/(1-tc->ratio), radius/(1+tc->ratio)) ); + sp_canvas_item_affine_absolute(tc->dilate_area, (sm* Geom::Rotate(tc->tilt))* Geom::Translate(SP_EVENT_CONTEXT(tc)->desktop->point())); + sp_canvas_item_show(tc->dilate_area); +} + +static void sp_spray_switch_mode(SprayTool *tc, gint mode, bool with_shift) +{ + // select the button mode + SP_EVENT_CONTEXT(tc)->desktop->setToolboxSelectOneValue("spray_tool_mode", mode); + // need to set explicitly, because the prefs may not have changed by the previous + tc->mode = mode; + tc->update_cursor(with_shift); +} + +bool SprayTool::root_handler(GdkEvent* event) { + gint ret = FALSE; + + switch (event->type) { + case GDK_ENTER_NOTIFY: + sp_canvas_item_show(this->dilate_area); + break; + case GDK_LEAVE_NOTIFY: + sp_canvas_item_hide(this->dilate_area); + break; + case GDK_BUTTON_PRESS: + if (event->button.button == 1 && !this->space_panning) { + if (Inkscape::have_viable_layer(desktop, this->message_context) == false) { + return TRUE; + } + + Geom::Point const motion_w(event->button.x, event->button.y); + Geom::Point const motion_dt(desktop->w2d(motion_w)); + this->last_push = desktop->dt2doc(motion_dt); + + sp_spray_extinput(this, event); + + desktop->canvas->forceFullRedrawAfterInterruptions(3); + this->is_drawing = true; + this->is_dilating = true; + this->has_dilated = false; + + if(this->is_dilating && event->button.button == 1 && !this->space_panning) { + sp_spray_dilate(this, motion_w, desktop->dt2doc(motion_dt), Geom::Point(0,0), MOD__SHIFT(event)); + } + + this->has_dilated = true; + ret = TRUE; + } + break; + case GDK_MOTION_NOTIFY: { + Geom::Point const motion_w(event->motion.x, + event->motion.y); + Geom::Point motion_dt(desktop->w2d(motion_w)); + Geom::Point motion_doc(desktop->dt2doc(motion_dt)); + sp_spray_extinput(this, event); + + // draw the dilating cursor + double radius = get_dilate_radius(this); + Geom::Affine const sm (Geom::Scale(radius/(1-this->ratio), radius/(1+this->ratio)) ); + sp_canvas_item_affine_absolute(this->dilate_area, (sm*Geom::Rotate(this->tilt))*Geom::Translate(desktop->w2d(motion_w))); + sp_canvas_item_show(this->dilate_area); + + guint num = 0; + if (!desktop->selection->isEmpty()) { + num = g_slist_length(const_cast(desktop->selection->itemList())); + } + if (num == 0) { + this->message_context->flash(Inkscape::ERROR_MESSAGE, _("Nothing selected! Select objects to spray.")); + } + + // dilating: + if (this->is_drawing && ( event->motion.state & GDK_BUTTON1_MASK )) { + sp_spray_dilate(this, motion_w, motion_doc, motion_doc - this->last_push, event->button.state & GDK_SHIFT_MASK? true : false); + //this->last_push = motion_doc; + this->has_dilated = true; + + // it's slow, so prevent clogging up with events + gobble_motion_events(GDK_BUTTON1_MASK); + return TRUE; + } + } + break; + /*Spray with the scroll*/ + case GDK_SCROLL: { + if (event->scroll.state & GDK_BUTTON1_MASK) { + double temp ; + temp = this->population; + this->population = 1.0; + desktop->setToolboxAdjustmentValue("population", this->population * 100); + Geom::Point const scroll_w(event->button.x, event->button.y); + Geom::Point const scroll_dt = desktop->point();; + Geom::Point motion_doc(desktop->dt2doc(scroll_dt)); + switch (event->scroll.direction) { + case GDK_SCROLL_DOWN: + case GDK_SCROLL_UP: { + if (Inkscape::have_viable_layer(desktop, this->message_context) == false) { + return TRUE; + } + this->last_push = desktop->dt2doc(scroll_dt); + sp_spray_extinput(this, event); + desktop->canvas->forceFullRedrawAfterInterruptions(3); + this->is_drawing = true; + this->is_dilating = true; + this->has_dilated = false; + if(this->is_dilating && !this->space_panning) { + sp_spray_dilate(this, scroll_w, desktop->dt2doc(scroll_dt), Geom::Point(0,0), false); + } + this->has_dilated = true; + + this->population = temp; + desktop->setToolboxAdjustmentValue("population", this->population * 100); + + ret = TRUE; + } + break; + case GDK_SCROLL_RIGHT: + {} break; + case GDK_SCROLL_LEFT: + {} break; + } + } + break; + } + + case GDK_BUTTON_RELEASE: { + Geom::Point const motion_w(event->button.x, event->button.y); + Geom::Point const motion_dt(desktop->w2d(motion_w)); + + desktop->canvas->endForcedFullRedraws(); + this->is_drawing = false; + + if (this->is_dilating && event->button.button == 1 && !this->space_panning) { + if (!this->has_dilated) { + // if we did not rub, do a light tap + this->pressure = 0.03; + sp_spray_dilate(this, motion_w, desktop->dt2doc(motion_dt), Geom::Point(0,0), MOD__SHIFT(event)); + } + this->is_dilating = false; + this->has_dilated = false; + switch (this->mode) { + case SPRAY_MODE_COPY: + DocumentUndo::done(sp_desktop_document(SP_EVENT_CONTEXT(this)->desktop), + SP_VERB_CONTEXT_SPRAY, _("Spray with copies")); + break; + case SPRAY_MODE_CLONE: + DocumentUndo::done(sp_desktop_document(SP_EVENT_CONTEXT(this)->desktop), + SP_VERB_CONTEXT_SPRAY, _("Spray with clones")); + break; + case SPRAY_MODE_SINGLE_PATH: + DocumentUndo::done(sp_desktop_document(SP_EVENT_CONTEXT(this)->desktop), + SP_VERB_CONTEXT_SPRAY, _("Spray in single path")); + break; + } + } + break; + } + + case GDK_KEY_PRESS: + switch (get_group0_keyval (&event->key)) { + case GDK_KEY_j: + case GDK_KEY_J: + if (MOD__SHIFT_ONLY(event)) { + sp_spray_switch_mode(this, SPRAY_MODE_COPY, MOD__SHIFT(event)); + ret = TRUE; + } + break; + case GDK_KEY_k: + case GDK_KEY_K: + if (MOD__SHIFT_ONLY(event)) { + sp_spray_switch_mode(this, SPRAY_MODE_CLONE, MOD__SHIFT(event)); + ret = TRUE; + } + break; + case GDK_KEY_l: + case GDK_KEY_L: + if (MOD__SHIFT_ONLY(event)) { + sp_spray_switch_mode(this, SPRAY_MODE_SINGLE_PATH, MOD__SHIFT(event)); + ret = TRUE; + } + break; + case GDK_KEY_Up: + case GDK_KEY_KP_Up: + if (!MOD__CTRL_ONLY(event)) { + this->population += 0.01; + if (this->population > 1.0) { + this->population = 1.0; + } + desktop->setToolboxAdjustmentValue("spray-population", this->population * 100); + ret = TRUE; + } + break; + case GDK_KEY_Down: + case GDK_KEY_KP_Down: + if (!MOD__CTRL_ONLY(event)) { + this->population -= 0.01; + if (this->population < 0.0) { + this->population = 0.0; + } + desktop->setToolboxAdjustmentValue("spray-population", this->population * 100); + ret = TRUE; + } + break; + case GDK_KEY_Right: + case GDK_KEY_KP_Right: + if (!MOD__CTRL_ONLY(event)) { + this->width += 0.01; + if (this->width > 1.0) { + this->width = 1.0; + } + // the same spinbutton is for alt+x + desktop->setToolboxAdjustmentValue("altx-spray", this->width * 100); + sp_spray_update_area(this); + ret = TRUE; + } + break; + case GDK_KEY_Left: + case GDK_KEY_KP_Left: + if (!MOD__CTRL_ONLY(event)) { + this->width -= 0.01; + if (this->width < 0.01) { + this->width = 0.01; + } + desktop->setToolboxAdjustmentValue("altx-spray", this->width * 100); + sp_spray_update_area(this); + ret = TRUE; + } + break; + case GDK_KEY_Home: + case GDK_KEY_KP_Home: + this->width = 0.01; + desktop->setToolboxAdjustmentValue("altx-spray", this->width * 100); + sp_spray_update_area(this); + ret = TRUE; + break; + case GDK_KEY_End: + case GDK_KEY_KP_End: + this->width = 1.0; + desktop->setToolboxAdjustmentValue("altx-spray", this->width * 100); + sp_spray_update_area(this); + ret = TRUE; + break; + case GDK_KEY_x: + case GDK_KEY_X: + if (MOD__ALT_ONLY(event)) { + desktop->setToolboxFocusTo("altx-spray"); + ret = TRUE; + } + break; + case GDK_KEY_Shift_L: + case GDK_KEY_Shift_R: + this->update_cursor(true); + break; + case GDK_KEY_Control_L: + case GDK_KEY_Control_R: + break; + case GDK_KEY_Delete: + case GDK_KEY_KP_Delete: + case GDK_KEY_BackSpace: + ret = this->deleteSelectedDrag(MOD__CTRL_ONLY(event)); + break; + + default: + break; + } + break; + + case GDK_KEY_RELEASE: { + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + switch (get_group0_keyval(&event->key)) { + case GDK_KEY_Shift_L: + case GDK_KEY_Shift_R: + this->update_cursor(false); + break; + case GDK_KEY_Control_L: + case GDK_KEY_Control_R: + sp_spray_switch_mode (this, prefs->getInt("/tools/spray/mode"), MOD__SHIFT(event)); + this->message_context->clear(); + break; + default: + sp_spray_switch_mode (this, prefs->getInt("/tools/spray/mode"), MOD__SHIFT(event)); + break; + } + } + + default: + break; + } + + if (!ret) { +// if ((SP_EVENT_CONTEXT_CLASS(sp_spray_context_parent_class))->root_handler) { +// ret = (SP_EVENT_CONTEXT_CLASS(sp_spray_context_parent_class))->root_handler(event_context, event); +// } + ret = ToolBase::root_handler(event); + } + + return ret; +} + +} +} +} + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : + diff --git a/src/ui/tools/spray-tool.h b/src/ui/tools/spray-tool.h new file mode 100644 index 000000000..e7362fd50 --- /dev/null +++ b/src/ui/tools/spray-tool.h @@ -0,0 +1,121 @@ +#ifndef __SP_SPRAY_CONTEXT_H__ +#define __SP_SPRAY_CONTEXT_H__ + +/* + * Spray Tool + * + * Authors: + * Pierre-Antoine MARC + * Pierre CACLIN + * Aurel-Aimé MARMION + * Julien LERAY + * Benoît LAVORATA + * Vincent MONTAGNE + * Pierre BARBRY-BLOT + * + * Copyright (C) 2009 authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include <2geom/point.h> +#include "ui/tools/tool-base.h" + +#define SP_SPRAY_CONTEXT(obj) (dynamic_cast((Inkscape::UI::Tools::ToolBase*)obj)) +#define SP_IS_SPRAY_CONTEXT(obj) (dynamic_cast((const Inkscape::UI::Tools::ToolBase*)obj) != NULL) + +namespace Inkscape { + namespace UI { + namespace Dialog { + class Dialog; + } + } +} + + +#define SAMPLING_SIZE 8 /* fixme: ?? */ + +#define TC_MIN_PRESSURE 0.0 +#define TC_MAX_PRESSURE 1.0 +#define TC_DEFAULT_PRESSURE 0.35 + +namespace Inkscape { +namespace UI { +namespace Tools { + +enum { + SPRAY_MODE_COPY, + SPRAY_MODE_CLONE, + SPRAY_MODE_SINGLE_PATH, + SPRAY_OPTION, +}; + +class SprayTool : public ToolBase { +public: + SprayTool(); + virtual ~SprayTool(); + + //ToolBase event_context; + //Inkscape::UI::Dialog::Dialog *dialog_option;//Attribut de type SprayOptionClass, localisé dans scr/ui/dialog + /* extended input data */ + gdouble pressure; + + /* attributes */ + guint dragging : 1; /* mouse state: mouse is dragging */ + guint usepressure : 1; + guint usetilt : 1; + bool usetext ; + + double width; + double ratio; + double tilt; + double rotation_variation; + double force; + double population; + double scale_variation; + double scale; + double mean; + double standard_deviation; + + gint distrib; + + gint mode; + + bool is_drawing; + + bool is_dilating; + bool has_dilated; + Geom::Point last_push; + SPCanvasItem *dilate_area; + + sigc::connection style_set_connection; + + static const std::string prefsPath; + + virtual void setup(); + virtual void set(const Inkscape::Preferences::Entry& val); + virtual bool root_handler(GdkEvent* event); + + virtual const std::string& getPrefsPath(); + + + void update_cursor(bool /*with_shift*/); +}; + +} +} +} + +#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:fileencoding=utf-8:textwidth=99 : + diff --git a/src/ui/tools/star-tool.cpp b/src/ui/tools/star-tool.cpp new file mode 100644 index 000000000..b5d8c4418 --- /dev/null +++ b/src/ui/tools/star-tool.cpp @@ -0,0 +1,494 @@ +/* + * Star drawing context + * + * Authors: + * Mitsuru Oka + * Lauris Kaplinski + * bulia byak + * Jon A. Cruz + * Abhishek Sharma + * + * Copyright (C) 1999-2002 Lauris Kaplinski + * Copyright (C) 2001-2002 Mitsuru Oka + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include +#include + +#include + +#include "macros.h" +#include "display/sp-canvas.h" +#include "sp-star.h" +#include "document.h" +#include "document-undo.h" +#include "sp-namedview.h" +#include "selection.h" +#include "desktop-handles.h" +#include "snap.h" +#include "desktop.h" +#include "desktop-style.h" +#include "message-context.h" +#include "pixmaps/cursor-star.xpm" +#include +#include "preferences.h" +#include "xml/repr.h" +#include "xml/node-event-vector.h" +#include "context-fns.h" +#include "shape-editor.h" +#include "verbs.h" +#include "display/sp-canvas-item.h" + +#include "ui/tools/star-tool.h" + +using Inkscape::DocumentUndo; + +#include "tool-factory.h" + +namespace Inkscape { +namespace UI { +namespace Tools { + +namespace { + ToolBase* createStarContext() { + return new StarTool(); + } + + bool starContextRegistered = ToolFactory::instance().registerObject("/tools/shapes/star", createStarContext); +} + +const std::string& StarTool::getPrefsPath() { + return StarTool::prefsPath; +} + +const std::string StarTool::prefsPath = "/tools/shapes/star"; + +StarTool::StarTool() : ToolBase() { + this->randomized = 0; + this->rounded = 0; + + this->cursor_shape = cursor_star_xpm; + this->hot_x = 4; + this->hot_y = 4; + this->xp = 0; + this->yp = 0; + this->tolerance = 0; + this->within_tolerance = false; + this->item_to_select = NULL; + //this->tool_url = "/tools/shapes/star"; + + this->star = NULL; + + this->magnitude = 5; + this->proportion = 0.5; + this->isflatsided = false; +} + +void StarTool::finish() { + sp_canvas_item_ungrab(SP_CANVAS_ITEM(desktop->acetate), GDK_CURRENT_TIME); + + this->finishItem(); + this->sel_changed_connection.disconnect(); + + ToolBase::finish(); +} + +StarTool::~StarTool() { + this->enableGrDrag(false); + + this->sel_changed_connection.disconnect(); + + delete this->shape_editor; + this->shape_editor = NULL; + + /* fixme: This is necessary because we do not grab */ + if (this->star) { + this->finishItem(); + } +} + +/** + * Callback that processes the "changed" signal on the selection; + * destroys old and creates new knotholder. + * + * @param selection Should not be NULL. + */ +void StarTool::selection_changed(Inkscape::Selection* selection) { + g_assert (selection != NULL); + + this->shape_editor->unset_item(SH_KNOTHOLDER); + this->shape_editor->set_item(selection->singleItem(), SH_KNOTHOLDER); +} + +void StarTool::setup() { + ToolBase::setup(); + + sp_event_context_read(this, "magnitude"); + sp_event_context_read(this, "proportion"); + sp_event_context_read(this, "isflatsided"); + sp_event_context_read(this, "rounded"); + sp_event_context_read(this, "randomized"); + + this->shape_editor = new ShapeEditor(this->desktop); + + SPItem *item = sp_desktop_selection(this->desktop)->singleItem(); + if (item) { + this->shape_editor->set_item(item, SH_KNOTHOLDER); + } + + Inkscape::Selection *selection = sp_desktop_selection(this->desktop); + + this->sel_changed_connection.disconnect(); + + this->sel_changed_connection = selection->connectChanged(sigc::mem_fun(this, &StarTool::selection_changed)); + + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + if (prefs->getBool("/tools/shapes/selcue")) { + this->enableSelectionCue(); + } + + if (prefs->getBool("/tools/shapes/gradientdrag")) { + this->enableGrDrag(); + } +} + +void StarTool::set(const Inkscape::Preferences::Entry& val) { + Glib::ustring path = val.getEntryName(); + + if (path == "magnitude") { + this->magnitude = CLAMP(val.getInt(5), 3, 1024); + } else if (path == "proportion") { + this->proportion = CLAMP(val.getDouble(0.5), 0.01, 2.0); + } else if (path == "isflatsided") { + this->isflatsided = val.getBool(); + } else if (path == "rounded") { + this->rounded = val.getDouble(); + } else if (path == "randomized") { + this->randomized = val.getDouble(); + } +} + +bool StarTool::root_handler(GdkEvent* event) { + static bool dragging; + + SPDesktop *desktop = this->desktop; + Inkscape::Selection *selection = sp_desktop_selection (desktop); + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + + this->tolerance = prefs->getIntLimited("/options/dragtolerance/value", 0, 0, 100); + + gint ret = FALSE; + + switch (event->type) { + case GDK_BUTTON_PRESS: + if (event->button.button == 1 && !this->space_panning) { + dragging = true; + + this->center = Inkscape::setup_for_drag_start(desktop, this, event); + + /* Snap center */ + SnapManager &m = desktop->namedview->snap_manager; + m.setup(desktop, true); + m.freeSnapReturnByRef(this->center, Inkscape::SNAPSOURCE_NODE_HANDLE); + m.unSetup(); + + sp_canvas_item_grab(SP_CANVAS_ITEM(desktop->acetate), + GDK_KEY_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | + GDK_POINTER_MOTION_MASK | + GDK_POINTER_MOTION_HINT_MASK | + GDK_BUTTON_PRESS_MASK, + NULL, event->button.time); + ret = TRUE; + } + break; + + case GDK_MOTION_NOTIFY: + if (dragging && (event->motion.state & GDK_BUTTON1_MASK) && !this->space_panning) { + if ( this->within_tolerance + && ( abs( (gint) event->motion.x - this->xp ) < this->tolerance ) + && ( abs( (gint) event->motion.y - this->yp ) < this->tolerance ) ) { + break; // do not drag if we're within tolerance from origin + } + // Once the user has moved farther than tolerance from the original location + // (indicating they intend to draw, not click), then always process the + // motion notify coordinates as given (no snapping back to origin) + this->within_tolerance = false; + + Geom::Point const motion_w(event->motion.x, event->motion.y); + Geom::Point motion_dt(desktop->w2d(motion_w)); + + this->drag(motion_dt, event->motion.state); + + gobble_motion_events(GDK_BUTTON1_MASK); + + ret = TRUE; + } else if (!sp_event_context_knot_mouseover(this)) { + SnapManager &m = desktop->namedview->snap_manager; + m.setup(desktop); + + Geom::Point const motion_w(event->motion.x, event->motion.y); + Geom::Point motion_dt(desktop->w2d(motion_w)); + + m.preSnap(Inkscape::SnapCandidatePoint(motion_dt, Inkscape::SNAPSOURCE_NODE_HANDLE)); + m.unSetup(); + } + break; + case GDK_BUTTON_RELEASE: + this->xp = this->yp = 0; + + if (event->button.button == 1 && !this->space_panning) { + dragging = false; + + sp_event_context_discard_delayed_snap_event(this); + + if (!this->within_tolerance) { + // we've been dragging, finish the star + this->finishItem(); + } else if (this->item_to_select) { + // no dragging, select clicked item if any + if (event->button.state & GDK_SHIFT_MASK) { + selection->toggle(this->item_to_select); + } else { + selection->set(this->item_to_select); + } + } else { + // click in an empty space + selection->clear(); + } + + this->item_to_select = NULL; + ret = TRUE; + sp_canvas_item_ungrab(SP_CANVAS_ITEM (desktop->acetate), event->button.time); + } + break; + + case GDK_KEY_PRESS: + switch (get_group0_keyval(&event->key)) { + case GDK_KEY_Alt_R: + case GDK_KEY_Control_L: + case GDK_KEY_Control_R: + case GDK_KEY_Shift_L: + case GDK_KEY_Shift_R: + case GDK_KEY_Meta_L: // Meta is when you press Shift+Alt (at least on my machine) + case GDK_KEY_Meta_R: + sp_event_show_modifier_tip(this->defaultMessageContext(), event, + _("Ctrl: snap angle; keep rays radial"), + NULL, + NULL); + break; + + case GDK_KEY_Up: + case GDK_KEY_Down: + case GDK_KEY_KP_Up: + case GDK_KEY_KP_Down: + // prevent the zoom field from activation + if (!MOD__CTRL_ONLY(event)) + ret = TRUE; + break; + + case GDK_KEY_x: + case GDK_KEY_X: + if (MOD__ALT_ONLY(event)) { + desktop->setToolboxFocusTo ("altx-star"); + ret = TRUE; + } + break; + + case GDK_KEY_Escape: + if (dragging) { + dragging = false; + sp_event_context_discard_delayed_snap_event(this); + // if drawing, cancel, otherwise pass it up for deselecting + this->cancel(); + ret = TRUE; + } + break; + + case GDK_KEY_space: + if (dragging) { + sp_canvas_item_ungrab(SP_CANVAS_ITEM(desktop->acetate), event->button.time); + + dragging = false; + + sp_event_context_discard_delayed_snap_event(this); + + if (!this->within_tolerance) { + // we've been dragging, finish the star + this->finishItem(); + } + // do not return true, so that space would work switching to selector + } + break; + + case GDK_KEY_Delete: + case GDK_KEY_KP_Delete: + case GDK_KEY_BackSpace: + ret = this->deleteSelectedDrag(MOD__CTRL_ONLY(event)); + break; + + default: + break; + } + break; + + case GDK_KEY_RELEASE: + switch (get_group0_keyval (&event->key)) { + case GDK_KEY_Alt_L: + case GDK_KEY_Alt_R: + case GDK_KEY_Control_L: + case GDK_KEY_Control_R: + case GDK_KEY_Shift_L: + case GDK_KEY_Shift_R: + case GDK_KEY_Meta_L: // Meta is when you press Shift+Alt + case GDK_KEY_Meta_R: + this->defaultMessageContext()->clear(); + break; + + default: + break; + } + break; + + default: + break; + } + + if (!ret) { + ret = ToolBase::root_handler(event); + } + + return ret; +} + +void StarTool::drag(Geom::Point p, guint state) +{ + SPDesktop *desktop = this->desktop; + + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + int const snaps = prefs->getInt("/options/rotationsnapsperpi/value", 12); + + if (!this->star) { + if (Inkscape::have_viable_layer(desktop, this->message_context) == false) { + return; + } + + // Create object + Inkscape::XML::Document *xml_doc = this->desktop->doc()->getReprDoc(); + Inkscape::XML::Node *repr = xml_doc->createElement("svg:path"); + repr->setAttribute("sodipodi:type", "star"); + + // Set style + sp_desktop_apply_style_tool(desktop, repr, "/tools/shapes/star", false); + + this->star = SP_STAR(desktop->currentLayer()->appendChildRepr(repr)); + + Inkscape::GC::release(repr); + this->star->transform = SP_ITEM(desktop->currentLayer())->i2doc_affine().inverse(); + this->star->updateRepr(); + + desktop->canvas->forceFullRedrawAfterInterruptions(5); + } + + /* Snap corner point with no constraints */ + SnapManager &m = desktop->namedview->snap_manager; + + m.setup(desktop, true, this->star); + Geom::Point pt2g = p; + m.freeSnapReturnByRef(pt2g, Inkscape::SNAPSOURCE_NODE_HANDLE); + m.unSetup(); + + Geom::Point const p0 = desktop->dt2doc(this->center); + Geom::Point const p1 = desktop->dt2doc(pt2g); + + double const sides = (gdouble) this->magnitude; + Geom::Point const d = p1 - p0; + Geom::Coord const r1 = Geom::L2(d); + double arg1 = atan2(d); + + if (state & GDK_CONTROL_MASK) { + /* Snap angle */ + arg1 = sp_round(arg1, M_PI / snaps); + } + + sp_star_position_set(this->star, this->magnitude, p0, r1, r1 * this->proportion, + arg1, arg1 + M_PI / sides, this->isflatsided, this->rounded, this->randomized); + + /* status text */ + Inkscape::Util::Quantity q = Inkscape::Util::Quantity(r1, "px"); + GString *rads = g_string_new(q.string(desktop->namedview->doc_units).c_str()); + this->message_context->setF(Inkscape::IMMEDIATE_MESSAGE, + ( this->isflatsided? + _("Polygon: radius %s, angle %5g°; with Ctrl to snap angle") + : _("Star: radius %s, angle %5g°; with Ctrl to snap angle") ), + rads->str, sp_round((arg1) * 180 / M_PI, 0.0001)); + + g_string_free(rads, FALSE); +} + +void StarTool::finishItem() { + this->message_context->clear(); + + if (this->star != NULL) { + if (this->star->r[1] == 0) { + // Don't allow the creating of zero sized arc, for example + // when the start and and point snap to the snap grid point + this->cancel(); + return; + } + + // Set transform center, so that odd stars rotate correctly + // LP #462157 + this->star->setCenter(this->center); + this->star->set_shape(); + this->star->updateRepr(SP_OBJECT_WRITE_EXT); + this->star->doWriteTransform(this->star->getRepr(), this->star->transform, NULL, true); + + desktop->canvas->endForcedFullRedraws(); + + sp_desktop_selection(desktop)->set(this->star); + DocumentUndo::done(sp_desktop_document(desktop), SP_VERB_CONTEXT_STAR, + _("Create star")); + + this->star = NULL; + } +} + +void StarTool::cancel() { + sp_desktop_selection(desktop)->clear(); + sp_canvas_item_ungrab(SP_CANVAS_ITEM(desktop->acetate), 0); + + if (this->star != NULL) { + this->star->deleteObject(); + this->star = NULL; + } + + this->within_tolerance = false; + this->xp = 0; + this->yp = 0; + this->item_to_select = NULL; + + desktop->canvas->endForcedFullRedraws(); + + DocumentUndo::cancel(sp_desktop_document(desktop)); +} + +} +} +} + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/ui/tools/star-tool.h b/src/ui/tools/star-tool.h new file mode 100644 index 000000000..da3774e68 --- /dev/null +++ b/src/ui/tools/star-tool.h @@ -0,0 +1,74 @@ +#ifndef __SP_STAR_CONTEXT_H__ +#define __SP_STAR_CONTEXT_H__ + +/* + * Star drawing context + * + * Authors: + * Mitsuru Oka + * Lauris Kaplinski + * + * Copyright (C) 1999-2002 Lauris Kaplinski + * Copyright (C) 2001-2002 Mitsuru Oka + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include +#include +#include <2geom/point.h> +#include "ui/tools/tool-base.h" + +#include "sp-star.h" + +namespace Inkscape { +namespace UI { +namespace Tools { + +class StarTool : public ToolBase { +public: + StarTool(); + virtual ~StarTool(); + + static const std::string prefsPath; + + virtual void setup(); + virtual void finish(); + virtual void set(const Inkscape::Preferences::Entry& val); + virtual bool root_handler(GdkEvent* event); + + virtual const std::string& getPrefsPath(); + +private: + SPStar* star; + + Geom::Point center; + + /* Number of corners */ + gint magnitude; + + /* Outer/inner radius ratio */ + gdouble proportion; + + /* flat sides or not? */ + bool isflatsided; + + /* rounded corners ratio */ + gdouble rounded; + + // randomization + gdouble randomized; + + sigc::connection sel_changed_connection; + + void drag(Geom::Point p, guint state); + void finishItem(); + void cancel(); + void selection_changed(Inkscape::Selection* selection); +}; + +} +} +} + +#endif diff --git a/src/ui/tools/text-tool.cpp b/src/ui/tools/text-tool.cpp new file mode 100644 index 000000000..0a8b35110 --- /dev/null +++ b/src/ui/tools/text-tool.cpp @@ -0,0 +1,1765 @@ +/* + * TextTool + * + * Authors: + * Lauris Kaplinski + * bulia byak + * Jon A. Cruz + * Abhishek Sharma + * + * Copyright (C) 1999-2005 authors + * Copyright (C) 2001 Ximian, Inc. + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#ifdef HAVE_CONFIG_H +# include +#endif + +#include +#include +#include +#include +#include +#include +#include + +#include "context-fns.h" +#include "desktop-handles.h" +#include "desktop-style.h" +#include "desktop.h" +#include "document.h" +#include "document-undo.h" +#include "macros.h" +#include "message-context.h" +#include "message-stack.h" +#include "pixmaps/cursor-text-insert.xpm" +#include "pixmaps/cursor-text.xpm" +#include "preferences.h" +#include "rubberband.h" +#include "selection-chemistry.h" +#include "selection.h" +#include "shape-editor.h" +#include "sp-flowtext.h" +#include "sp-namedview.h" +#include "sp-text.h" +#include "style.h" +#include "ui/tools/text-tool.h" +#include "text-editing.h" +#include "ui/control-manager.h" +#include "verbs.h" +#include "xml/node-event-vector.h" +#include "xml/repr.h" +#include +#include "tool-factory.h" + +using Inkscape::ControlManager; +using Inkscape::DocumentUndo; + +namespace Inkscape { +namespace UI { +namespace Tools { + +static void sp_text_context_selection_changed(Inkscape::Selection *selection, TextTool *tc); +static void sp_text_context_selection_modified(Inkscape::Selection *selection, guint flags, TextTool *tc); +static bool sp_text_context_style_set(SPCSSAttr const *css, TextTool *tc); +static int sp_text_context_style_query(SPStyle *style, int property, TextTool *tc); + +static void sp_text_context_validate_cursor_iterators(TextTool *tc); +static void sp_text_context_update_cursor(TextTool *tc, bool scroll_to_see = true); +static void sp_text_context_update_text_selection(TextTool *tc); +static gint sp_text_context_timeout(TextTool *tc); +static void sp_text_context_forget_text(TextTool *tc); + +static gint sptc_focus_in(GtkWidget *widget, GdkEventFocus *event, TextTool *tc); +static gint sptc_focus_out(GtkWidget *widget, GdkEventFocus *event, TextTool *tc); +static void sptc_commit(GtkIMContext *imc, gchar *string, TextTool *tc); + +namespace { + ToolBase* createTextContext() { + return new TextTool(); + } + + bool textContextRegistered = ToolFactory::instance().registerObject("/tools/text", createTextContext); +} + +const std::string& TextTool::getPrefsPath() { + return TextTool::prefsPath; +} + +const std::string TextTool::prefsPath = "/tools/text"; + + +TextTool::TextTool() : ToolBase() { + this->preedit_string = 0; + this->unipos = 0; + + this->cursor_shape = cursor_text_xpm; + this->hot_x = 7; + this->hot_y = 7; + + this->xp = 0; + this->yp = 0; + this->tolerance = 0; + this->within_tolerance = false; + + this->imc = NULL; + + this->text = NULL; + this->pdoc = Geom::Point(0, 0); + + this->unimode = false; + + this->cursor = NULL; + this->indicator = NULL; + this->frame = NULL; + this->grabbed = NULL; + this->timeout = 0; + this->show = FALSE; + this->phase = 0; + this->nascent_object = 0; + this->over_text = 0; + this->dragging = 0; + this->creating = 0; +} + +TextTool::~TextTool() { + delete this->shape_editor; + this->shape_editor = NULL; + + if (this->grabbed) { + sp_canvas_item_ungrab(this->grabbed, GDK_CURRENT_TIME); + this->grabbed = NULL; + } + + Inkscape::Rubberband::get(this->desktop)->stop(); +} + +void TextTool::setup() { + GtkSettings* settings = gtk_settings_get_default(); + gint timeout = 0; + g_object_get( settings, "gtk-cursor-blink-time", &timeout, NULL ); + + if (timeout < 0) { + timeout = 200; + } else { + timeout /= 2; + } + + this->cursor = ControlManager::getManager().createControlLine(sp_desktop_controls(desktop), Geom::Point(100, 0), Geom::Point(100, 100)); + this->cursor->setRgba32(0x000000ff); + sp_canvas_item_hide(this->cursor); + + this->indicator = sp_canvas_item_new(sp_desktop_controls(desktop), SP_TYPE_CTRLRECT, NULL); + SP_CTRLRECT(this->indicator)->setRectangle(Geom::Rect(Geom::Point(0, 0), Geom::Point(100, 100))); + SP_CTRLRECT(this->indicator)->setColor(0x0000ff7f, false, 0); + sp_canvas_item_hide(this->indicator); + + this->frame = sp_canvas_item_new(sp_desktop_controls(desktop), SP_TYPE_CTRLRECT, NULL); + SP_CTRLRECT(this->frame)->setRectangle(Geom::Rect(Geom::Point(0, 0), Geom::Point(100, 100))); + SP_CTRLRECT(this->frame)->setColor(0x0000ff7f, false, 0); + sp_canvas_item_hide(this->frame); + + this->timeout = g_timeout_add(timeout, (GSourceFunc) sp_text_context_timeout, this); + + this->imc = gtk_im_multicontext_new(); + if (this->imc) { + GtkWidget *canvas = GTK_WIDGET(sp_desktop_canvas(desktop)); + + /* im preedit handling is very broken in inkscape for + * multi-byte characters. See bug 1086769. + * We need to let the IM handle the preediting, and + * just take in the characters when they're finished being + * entered. + */ + gtk_im_context_set_use_preedit(this->imc, FALSE); + gtk_im_context_set_client_window(this->imc, + gtk_widget_get_window (canvas)); + + g_signal_connect(G_OBJECT(canvas), "focus_in_event", G_CALLBACK(sptc_focus_in), this); + g_signal_connect(G_OBJECT(canvas), "focus_out_event", G_CALLBACK(sptc_focus_out), this); + g_signal_connect(G_OBJECT(this->imc), "commit", G_CALLBACK(sptc_commit), this); + + if (gtk_widget_has_focus(canvas)) { + sptc_focus_in(canvas, NULL, this); + } + } + + ToolBase::setup(); + + this->shape_editor = new ShapeEditor(this->desktop); + + SPItem *item = sp_desktop_selection(this->desktop)->singleItem(); + if (item && SP_IS_FLOWTEXT(item) && SP_FLOWTEXT(item)->has_internal_frame()) { + this->shape_editor->set_item(item, SH_KNOTHOLDER); + } + + this->sel_changed_connection = sp_desktop_selection(desktop)->connectChanged( + sigc::bind(sigc::ptr_fun(&sp_text_context_selection_changed), this) + ); + this->sel_modified_connection = sp_desktop_selection(desktop)->connectModified( + sigc::bind(sigc::ptr_fun(&sp_text_context_selection_modified), this) + ); + this->style_set_connection = desktop->connectSetStyle( + sigc::bind(sigc::ptr_fun(&sp_text_context_style_set), this) + ); + this->style_query_connection = desktop->connectQueryStyle( + sigc::bind(sigc::ptr_fun(&sp_text_context_style_query), this) + ); + + sp_text_context_selection_changed(sp_desktop_selection(desktop), this); + + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + if (prefs->getBool("/tools/text/selcue")) { + this->enableSelectionCue(); + } + if (prefs->getBool("/tools/text/gradientdrag")) { + this->enableGrDrag(); + } +} + +void TextTool::finish() { + if (this->desktop) { + sp_signal_disconnect_by_data(sp_desktop_canvas(this->desktop), this); + } + + this->enableGrDrag(false); + + this->style_set_connection.disconnect(); + this->style_query_connection.disconnect(); + this->sel_changed_connection.disconnect(); + this->sel_modified_connection.disconnect(); + + sp_text_context_forget_text(SP_TEXT_CONTEXT(this)); + + if (this->imc) { + g_object_unref(G_OBJECT(this->imc)); + this->imc = NULL; + } + + if (this->timeout) { + g_source_remove(this->timeout); + this->timeout = 0; + } + + if (this->cursor) { + sp_canvas_item_destroy(this->cursor); + this->cursor = NULL; + } + + if (this->indicator) { + sp_canvas_item_destroy(this->indicator); + this->indicator = NULL; + } + + if (this->frame) { + sp_canvas_item_destroy(this->frame); + this->frame = NULL; + } + + for (std::vector::iterator it = this->text_selection_quads.begin() ; + it != this->text_selection_quads.end() ; ++it) { + sp_canvas_item_hide(*it); + sp_canvas_item_destroy(*it); + } + + this->text_selection_quads.clear(); +} + +bool TextTool::item_handler(SPItem* item, GdkEvent* event) { + SPItem *item_ungrouped; + + gint ret = FALSE; + + sp_text_context_validate_cursor_iterators(this); + Inkscape::Text::Layout::iterator old_start = this->text_sel_start; + + switch (event->type) { + case GDK_BUTTON_PRESS: + if (event->button.button == 1 && !this->space_panning) { + // find out clicked item, disregarding groups + item_ungrouped = desktop->getItemAtPoint(Geom::Point(event->button.x, event->button.y), TRUE); + if (SP_IS_TEXT(item_ungrouped) || SP_IS_FLOWTEXT(item_ungrouped)) { + sp_desktop_selection(desktop)->set(item_ungrouped); + if (this->text) { + // find out click point in document coordinates + Geom::Point p = desktop->w2d(Geom::Point(event->button.x, event->button.y)); + // set the cursor closest to that point + if (event->button.state & GDK_SHIFT_MASK) { + this->text_sel_start = old_start; + this->text_sel_end = sp_te_get_position_by_coords(this->text, p); + } else { + this->text_sel_start = this->text_sel_end = sp_te_get_position_by_coords(this->text, p); + } + // update display + sp_text_context_update_cursor(this); + sp_text_context_update_text_selection(this); + this->dragging = 1; + } + ret = TRUE; + } + } + break; + case GDK_2BUTTON_PRESS: + if (event->button.button == 1 && this->text) { + Inkscape::Text::Layout const *layout = te_get_layout(this->text); + if (layout) { + if (!layout->isStartOfWord(this->text_sel_start)) + this->text_sel_start.prevStartOfWord(); + if (!layout->isEndOfWord(this->text_sel_end)) + this->text_sel_end.nextEndOfWord(); + sp_text_context_update_cursor(this); + sp_text_context_update_text_selection(this); + this->dragging = 2; + ret = TRUE; + } + } + break; + case GDK_3BUTTON_PRESS: + if (event->button.button == 1 && this->text) { + this->text_sel_start.thisStartOfLine(); + this->text_sel_end.thisEndOfLine(); + sp_text_context_update_cursor(this); + sp_text_context_update_text_selection(this); + this->dragging = 3; + ret = TRUE; + } + break; + case GDK_BUTTON_RELEASE: + if (event->button.button == 1 && this->dragging && !this->space_panning) { + this->dragging = 0; + sp_event_context_discard_delayed_snap_event(this); + ret = TRUE; + } + break; + case GDK_MOTION_NOTIFY: + if ((event->motion.state & GDK_BUTTON1_MASK) && this->dragging && !this->space_panning) { + Inkscape::Text::Layout const *layout = te_get_layout(this->text); + if (!layout) break; + // find out click point in document coordinates + Geom::Point p = desktop->w2d(Geom::Point(event->button.x, event->button.y)); + // set the cursor closest to that point + Inkscape::Text::Layout::iterator new_end = sp_te_get_position_by_coords(this->text, p); + if (this->dragging == 2) { + // double-click dragging: go by word + if (new_end < this->text_sel_start) { + if (!layout->isStartOfWord(new_end)) + new_end.prevStartOfWord(); + } else + if (!layout->isEndOfWord(new_end)) + new_end.nextEndOfWord(); + } else if (this->dragging == 3) { + // triple-click dragging: go by line + if (new_end < this->text_sel_start) + new_end.thisStartOfLine(); + else + new_end.thisEndOfLine(); + } + // update display + if (this->text_sel_end != new_end) { + this->text_sel_end = new_end; + sp_text_context_update_cursor(this); + sp_text_context_update_text_selection(this); + } + gobble_motion_events(GDK_BUTTON1_MASK); + ret = TRUE; + break; + } + // find out item under mouse, disregarding groups + item_ungrouped = desktop->getItemAtPoint(Geom::Point(event->button.x, event->button.y), TRUE); + if (SP_IS_TEXT(item_ungrouped) || SP_IS_FLOWTEXT(item_ungrouped)) { + + Inkscape::Text::Layout const *layout = te_get_layout(item_ungrouped); + if (layout->inputTruncated()) { + SP_CTRLRECT(this->indicator)->setColor(0xff0000ff, false, 0); + } else { + SP_CTRLRECT(this->indicator)->setColor(0x0000ff7f, false, 0); + } + Geom::OptRect ibbox = item_ungrouped->desktopVisualBounds(); + if (ibbox) { + SP_CTRLRECT(this->indicator)->setRectangle(*ibbox); + } + sp_canvas_item_show(this->indicator); + + this->cursor_shape = cursor_text_insert_xpm; + this->hot_x = 7; + this->hot_y = 10; + this->sp_event_context_update_cursor(); + sp_text_context_update_text_selection(this); + + if (SP_IS_TEXT (item_ungrouped)) { + desktop->event_context->defaultMessageContext()->set(Inkscape::NORMAL_MESSAGE, _("Click to edit the text, drag to select part of the text.")); + } else { + desktop->event_context->defaultMessageContext()->set(Inkscape::NORMAL_MESSAGE, _("Click to edit the flowed text, drag to select part of the text.")); + } + + this->over_text = true; + + ret = TRUE; + } + break; + default: + break; + } + + if (!ret) { + ret = ToolBase::item_handler(item, event); + } + + return ret; +} + +static void sp_text_context_setup_text(TextTool *tc) +{ + ToolBase *ec = SP_EVENT_CONTEXT(tc); + + /* Create */ + Inkscape::XML::Document *xml_doc = SP_EVENT_CONTEXT_DESKTOP(ec)->doc()->getReprDoc(); + Inkscape::XML::Node *rtext = xml_doc->createElement("svg:text"); + rtext->setAttribute("xml:space", "preserve"); // we preserve spaces in the text objects we create + + /* Set style */ + sp_desktop_apply_style_tool(SP_EVENT_CONTEXT_DESKTOP(ec), rtext, "/tools/text", true); + + sp_repr_set_svg_double(rtext, "x", tc->pdoc[Geom::X]); + sp_repr_set_svg_double(rtext, "y", tc->pdoc[Geom::Y]); + + /* Create */ + Inkscape::XML::Node *rtspan = xml_doc->createElement("svg:tspan"); + rtspan->setAttribute("sodipodi:role", "line"); // otherwise, why bother creating the tspan? + rtext->addChild(rtspan, NULL); + Inkscape::GC::release(rtspan); + + /* Create TEXT */ + Inkscape::XML::Node *rstring = xml_doc->createTextNode(""); + rtspan->addChild(rstring, NULL); + Inkscape::GC::release(rstring); + SPItem *text_item = SP_ITEM(ec->desktop->currentLayer()->appendChildRepr(rtext)); + /* fixme: Is selection::changed really immediate? */ + /* yes, it's immediate .. why does it matter? */ + sp_desktop_selection(ec->desktop)->set(text_item); + Inkscape::GC::release(rtext); + text_item->transform = SP_ITEM(ec->desktop->currentLayer())->i2doc_affine().inverse(); + + text_item->updateRepr(); + text_item->doWriteTransform(text_item->getRepr(), text_item->transform, NULL, true); + DocumentUndo::done(sp_desktop_document(ec->desktop), SP_VERB_CONTEXT_TEXT, + _("Create text")); +} + +/** + * Insert the character indicated by tc.uni to replace the current selection, + * and reset tc.uni/tc.unipos to empty string. + * + * \pre tc.uni/tc.unipos non-empty. + */ +static void insert_uni_char(TextTool *const tc) +{ + g_return_if_fail(tc->unipos + && tc->unipos < sizeof(tc->uni) + && tc->uni[tc->unipos] == '\0'); + unsigned int uv; + std::stringstream ss; + ss << std::hex << tc->uni; + ss >> uv; + tc->unipos = 0; + tc->uni[tc->unipos] = '\0'; + + if ( !g_unichar_isprint(static_cast(uv)) + && !(g_unichar_validate(static_cast(uv)) && (g_unichar_type(static_cast(uv)) == G_UNICODE_PRIVATE_USE) ) ) { + // This may be due to bad input, so it goes to statusbar. + tc->desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, + _("Non-printable character")); + } else { + if (!tc->text) { // printable key; create text if none (i.e. if nascent_object) + sp_text_context_setup_text(tc); + tc->nascent_object = 0; // we don't need it anymore, having created a real + } + + gchar u[10]; + guint const len = g_unichar_to_utf8(uv, u); + u[len] = '\0'; + + tc->text_sel_start = tc->text_sel_end = sp_te_replace(tc->text, tc->text_sel_start, tc->text_sel_end, u); + sp_text_context_update_cursor(tc); + sp_text_context_update_text_selection(tc); + DocumentUndo::done(sp_desktop_document(tc->desktop), SP_VERB_DIALOG_TRANSFORM, + _("Insert Unicode character")); + } +} + +static void hex_to_printable_utf8_buf(char const *const ehex, char *utf8) +{ + unsigned int uv; + std::stringstream ss; + ss << std::hex << ehex; + ss >> uv; + if (!g_unichar_isprint((gunichar) uv)) { + uv = 0xfffd; + } + guint const len = g_unichar_to_utf8(uv, utf8); + utf8[len] = '\0'; +} + +static void show_curr_uni_char(TextTool *const tc) +{ + g_return_if_fail(tc->unipos < sizeof(tc->uni) + && tc->uni[tc->unipos] == '\0'); + if (tc->unipos) { + char utf8[10]; + hex_to_printable_utf8_buf(tc->uni, utf8); + + /* Status bar messages are in pango markup, so we need xml escaping. */ + if (utf8[1] == '\0') { + switch(utf8[0]) { + case '<': strcpy(utf8, "<"); break; + case '>': strcpy(utf8, ">"); break; + case '&': strcpy(utf8, "&"); break; + default: break; + } + } + tc->defaultMessageContext()->setF(Inkscape::NORMAL_MESSAGE, + _("Unicode (Enter to finish): %s: %s"), tc->uni, utf8); + } else { + tc->defaultMessageContext()->set(Inkscape::NORMAL_MESSAGE, _("Unicode (Enter to finish): ")); + } +} + +bool TextTool::root_handler(GdkEvent* event) { + sp_canvas_item_hide(this->indicator); + + sp_text_context_validate_cursor_iterators(this); + + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + this->tolerance = prefs->getIntLimited("/options/dragtolerance/value", 0, 0, 100); + + switch (event->type) { + case GDK_BUTTON_PRESS: + if (event->button.button == 1 && !this->space_panning) { + + if (Inkscape::have_viable_layer(desktop, desktop->messageStack()) == false) { + return TRUE; + } + + // save drag origin + this->xp = (gint) event->button.x; + this->yp = (gint) event->button.y; + this->within_tolerance = true; + + Geom::Point const button_pt(event->button.x, event->button.y); + Geom::Point button_dt(desktop->w2d(button_pt)); + + SnapManager &m = desktop->namedview->snap_manager; + m.setup(desktop); + m.freeSnapReturnByRef(button_dt, Inkscape::SNAPSOURCE_NODE_HANDLE); + m.unSetup(); + + this->p0 = button_dt; + Inkscape::Rubberband::get(desktop)->start(desktop, this->p0); + sp_canvas_item_grab(SP_CANVAS_ITEM(desktop->acetate), + GDK_KEY_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_BUTTON_PRESS_MASK | GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK, + NULL, event->button.time); + this->grabbed = SP_CANVAS_ITEM(desktop->acetate); + this->creating = 1; + + /* Processed */ + return TRUE; + } + break; + case GDK_MOTION_NOTIFY: + if (this->over_text) { + this->over_text = 0; + // update cursor and statusbar: we are not over a text object now + this->cursor_shape = cursor_text_xpm; + this->hot_x = 7; + this->hot_y = 7; + this->sp_event_context_update_cursor(); + desktop->event_context->defaultMessageContext()->clear(); + } + + if (this->creating && (event->motion.state & GDK_BUTTON1_MASK) && !this->space_panning) { + if ( this->within_tolerance + && ( abs( (gint) event->motion.x - this->xp ) < this->tolerance ) + && ( abs( (gint) event->motion.y - this->yp ) < this->tolerance ) ) { + break; // do not drag if we're within tolerance from origin + } + // Once the user has moved farther than tolerance from the original location + // (indicating they intend to draw, not click), then always process the + // motion notify coordinates as given (no snapping back to origin) + this->within_tolerance = false; + + Geom::Point const motion_pt(event->motion.x, event->motion.y); + Geom::Point p = desktop->w2d(motion_pt); + + SnapManager &m = desktop->namedview->snap_manager; + m.setup(desktop); + m.freeSnapReturnByRef(p, Inkscape::SNAPSOURCE_NODE_HANDLE); + m.unSetup(); + + Inkscape::Rubberband::get(desktop)->move(p); + gobble_motion_events(GDK_BUTTON1_MASK); + + // status text + Inkscape::Util::Quantity x_q = Inkscape::Util::Quantity(fabs((p - this->p0)[Geom::X]), "px"); + Inkscape::Util::Quantity y_q = Inkscape::Util::Quantity(fabs((p - this->p0)[Geom::Y]), "px"); + GString *xs = g_string_new(x_q.string(desktop->namedview->doc_units).c_str()); + GString *ys = g_string_new(y_q.string(desktop->namedview->doc_units).c_str()); + this->message_context->setF(Inkscape::IMMEDIATE_MESSAGE, _("Flowed text frame: %s × %s"), xs->str, ys->str); + + g_string_free(xs, FALSE); + g_string_free(ys, FALSE); + + } else if (!sp_event_context_knot_mouseover(this)) { + SnapManager &m = desktop->namedview->snap_manager; + m.setup(desktop); + + Geom::Point const motion_w(event->motion.x, event->motion.y); + Geom::Point motion_dt(desktop->w2d(motion_w)); + m.preSnap(Inkscape::SnapCandidatePoint(motion_dt, Inkscape::SNAPSOURCE_OTHER_HANDLE)); + m.unSetup(); + } + break; + case GDK_BUTTON_RELEASE: + if (event->button.button == 1 && !this->space_panning) { + sp_event_context_discard_delayed_snap_event(this); + + Geom::Point p1 = desktop->w2d(Geom::Point(event->button.x, event->button.y)); + + SnapManager &m = desktop->namedview->snap_manager; + m.setup(desktop); + m.freeSnapReturnByRef(p1, Inkscape::SNAPSOURCE_NODE_HANDLE); + m.unSetup(); + + if (this->grabbed) { + sp_canvas_item_ungrab(this->grabbed, GDK_CURRENT_TIME); + this->grabbed = NULL; + } + + Inkscape::Rubberband::get(desktop)->stop(); + + if (this->creating && this->within_tolerance) { + /* Button 1, set X & Y & new item */ + sp_desktop_selection(desktop)->clear(); + this->pdoc = desktop->dt2doc(p1); + this->show = TRUE; + this->phase = 1; + this->nascent_object = 1; // new object was just created + + /* Cursor */ + sp_canvas_item_show(this->cursor); + // Cursor height is defined by the new text object's font size; it needs to be set + // artificially here, for the text object does not exist yet: + double cursor_height = sp_desktop_get_font_size_tool(desktop); + this->cursor->setCoords(p1, p1 + Geom::Point(0, cursor_height)); + if (this->imc) { + GdkRectangle im_cursor; + Geom::Point const top_left = SP_EVENT_CONTEXT(this)->desktop->get_display_area().corner(3); + Geom::Point const cursor_size(0, cursor_height); + Geom::Point const im_position = SP_EVENT_CONTEXT(this)->desktop->d2w(p1 + cursor_size - top_left); + im_cursor.x = (int) floor(im_position[Geom::X]); + im_cursor.y = (int) floor(im_position[Geom::Y]); + im_cursor.width = 0; + im_cursor.height = (int) -floor(SP_EVENT_CONTEXT(this)->desktop->d2w(cursor_size)[Geom::Y]); + gtk_im_context_set_cursor_location(this->imc, &im_cursor); + } + this->message_context->set(Inkscape::NORMAL_MESSAGE, _("Type text; Enter to start new line.")); // FIXME:: this is a copy of a string from _update_cursor below, do not desync + + this->within_tolerance = false; + } else if (this->creating) { + double cursor_height = sp_desktop_get_font_size_tool(desktop); + if (fabs(p1[Geom::Y] - this->p0[Geom::Y]) > cursor_height) { + // otherwise even one line won't fit; most probably a slip of hand (even if bigger than tolerance) + SPItem *ft = create_flowtext_with_internal_frame (desktop, this->p0, p1); + /* Set style */ + sp_desktop_apply_style_tool(desktop, ft->getRepr(), "/tools/text", true); + sp_desktop_selection(desktop)->set(ft); + desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("Flowed text is created.")); + DocumentUndo::done(sp_desktop_document(desktop), SP_VERB_CONTEXT_TEXT, + _("Create flowed text")); + } else { + desktop->messageStack()->flash(Inkscape::ERROR_MESSAGE, _("The frame is too small for the current font size. Flowed text not created.")); + } + } + this->creating = false; + return TRUE; + } + break; + case GDK_KEY_PRESS: { + guint const group0_keyval = get_group0_keyval(&event->key); + + if (group0_keyval == GDK_KEY_KP_Add || + group0_keyval == GDK_KEY_KP_Subtract) { + if (!(event->key.state & GDK_MOD2_MASK)) // mod2 is NumLock; if on, type +/- keys + break; // otherwise pass on keypad +/- so they can zoom + } + + if ((this->text) || (this->nascent_object)) { + // there is an active text object in this context, or a new object was just created + + if (this->unimode || !this->imc + || (MOD__CTRL(event) && MOD__SHIFT(event)) // input methods tend to steal this for unimode, + // but we have our own so make sure they don't swallow it + || !gtk_im_context_filter_keypress(this->imc, (GdkEventKey*) event)) { + //IM did not consume the key, or we're in unimode + + if (!MOD__CTRL_ONLY(event) && this->unimode) { + /* TODO: ISO 14755 (section 3 Definitions) says that we should also + accept the first 6 characters of alphabets other than the latin + alphabet "if the Latin alphabet is not used". The below is also + reasonable (viz. hope that the user's keyboard includes latin + characters and force latin interpretation -- just as we do for our + keyboard shortcuts), but differs from the ISO 14755 + recommendation. */ + switch (group0_keyval) { + case GDK_KEY_space: + case GDK_KEY_KP_Space: { + if (this->unipos) { + insert_uni_char(this); + } + /* Stay in unimode. */ + show_curr_uni_char(this); + return TRUE; + } + + case GDK_KEY_BackSpace: { + g_return_val_if_fail(this->unipos < sizeof(this->uni), TRUE); + if (this->unipos) { + this->uni[--this->unipos] = '\0'; + } + show_curr_uni_char(this); + return TRUE; + } + + case GDK_KEY_Return: + case GDK_KEY_KP_Enter: { + if (this->unipos) { + insert_uni_char(this); + } + /* Exit unimode. */ + this->unimode = false; + this->defaultMessageContext()->clear(); + return TRUE; + } + + case GDK_KEY_Escape: { + // Cancel unimode. + this->unimode = false; + gtk_im_context_reset(this->imc); + this->defaultMessageContext()->clear(); + return TRUE; + } + + case GDK_KEY_Shift_L: + case GDK_KEY_Shift_R: + break; + + default: { + if (g_ascii_isxdigit(group0_keyval)) { + g_return_val_if_fail(this->unipos < sizeof(this->uni) - 1, TRUE); + this->uni[this->unipos++] = group0_keyval; + this->uni[this->unipos] = '\0'; + if (this->unipos == 8) { + /* This behaviour is partly to allow us to continue to + use a fixed-length buffer for tc->uni. Reason for + choosing the number 8 is that it's the length of + ``canonical form'' mentioned in the ISO 14755 spec. + An advantage over choosing 6 is that it allows using + backspace for typos & misremembering when entering a + 6-digit number. */ + insert_uni_char(this); + } + show_curr_uni_char(this); + return TRUE; + } else { + /* The intent is to ignore but consume characters that could be + typos for hex digits. Gtk seems to ignore & consume all + non-hex-digits, and we do similar here. Though note that some + shortcuts (like keypad +/- for zoom) get processed before + reaching this code. */ + return TRUE; + } + } + } + } + + Inkscape::Text::Layout::iterator old_start = this->text_sel_start; + Inkscape::Text::Layout::iterator old_end = this->text_sel_end; + bool cursor_moved = false; + int screenlines = 1; + if (this->text) { + double spacing = sp_te_get_average_linespacing(this->text); + Geom::Rect const d = desktop->get_display_area(); + screenlines = (int) floor(fabs(d.min()[Geom::Y] - d.max()[Geom::Y])/spacing) - 1; + if (screenlines <= 0) + screenlines = 1; + } + + /* Neither unimode nor IM consumed key; process text tool shortcuts */ + switch (group0_keyval) { + case GDK_KEY_x: + case GDK_KEY_X: + if (MOD__ALT_ONLY(event)) { + desktop->setToolboxFocusTo ("altx-text"); + return TRUE; + } + break; + case GDK_KEY_space: + if (MOD__CTRL_ONLY(event)) { + /* No-break space */ + if (!this->text) { // printable key; create text if none (i.e. if nascent_object) + sp_text_context_setup_text(this); + this->nascent_object = 0; // we don't need it anymore, having created a real + } + this->text_sel_start = this->text_sel_end = sp_te_replace(this->text, this->text_sel_start, this->text_sel_end, "\302\240"); + sp_text_context_update_cursor(this); + sp_text_context_update_text_selection(this); + desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("No-break space")); + DocumentUndo::done(sp_desktop_document(desktop), SP_VERB_CONTEXT_TEXT, + _("Insert no-break space")); + return TRUE; + } + break; + case GDK_KEY_U: + case GDK_KEY_u: + if (MOD__CTRL_ONLY(event) || (MOD__CTRL(event) && MOD__SHIFT(event))) { + if (this->unimode) { + this->unimode = false; + this->defaultMessageContext()->clear(); + } else { + this->unimode = true; + this->unipos = 0; + this->defaultMessageContext()->set(Inkscape::NORMAL_MESSAGE, _("Unicode (Enter to finish): ")); + } + if (this->imc) { + gtk_im_context_reset(this->imc); + } + return TRUE; + } + break; + case GDK_KEY_B: + case GDK_KEY_b: + if (MOD__CTRL_ONLY(event) && this->text) { + SPStyle const *style = sp_te_style_at_position(this->text, std::min(this->text_sel_start, this->text_sel_end)); + SPCSSAttr *css = sp_repr_css_attr_new(); + if (style->font_weight.computed == SP_CSS_FONT_WEIGHT_NORMAL + || style->font_weight.computed == SP_CSS_FONT_WEIGHT_100 + || style->font_weight.computed == SP_CSS_FONT_WEIGHT_200 + || style->font_weight.computed == SP_CSS_FONT_WEIGHT_300 + || style->font_weight.computed == SP_CSS_FONT_WEIGHT_400) + sp_repr_css_set_property(css, "font-weight", "bold"); + else + sp_repr_css_set_property(css, "font-weight", "normal"); + sp_te_apply_style(this->text, this->text_sel_start, this->text_sel_end, css); + sp_repr_css_attr_unref(css); + DocumentUndo::done(sp_desktop_document(desktop), SP_VERB_CONTEXT_TEXT, + _("Make bold")); + sp_text_context_update_cursor(this); + sp_text_context_update_text_selection(this); + return TRUE; + } + break; + case GDK_KEY_I: + case GDK_KEY_i: + if (MOD__CTRL_ONLY(event) && this->text) { + SPStyle const *style = sp_te_style_at_position(this->text, std::min(this->text_sel_start, this->text_sel_end)); + SPCSSAttr *css = sp_repr_css_attr_new(); + if (style->font_style.computed != SP_CSS_FONT_STYLE_NORMAL) + sp_repr_css_set_property(css, "font-style", "normal"); + else + sp_repr_css_set_property(css, "font-style", "italic"); + sp_te_apply_style(this->text, this->text_sel_start, this->text_sel_end, css); + sp_repr_css_attr_unref(css); + DocumentUndo::done(sp_desktop_document(desktop), SP_VERB_CONTEXT_TEXT, + _("Make italic")); + sp_text_context_update_cursor(this); + sp_text_context_update_text_selection(this); + return TRUE; + } + break; + + case GDK_KEY_A: + case GDK_KEY_a: + if (MOD__CTRL_ONLY(event) && this->text) { + Inkscape::Text::Layout const *layout = te_get_layout(this->text); + if (layout) { + this->text_sel_start = layout->begin(); + this->text_sel_end = layout->end(); + sp_text_context_update_cursor(this); + sp_text_context_update_text_selection(this); + return TRUE; + } + } + break; + + case GDK_KEY_Return: + case GDK_KEY_KP_Enter: + { + if (!this->text) { // printable key; create text if none (i.e. if nascent_object) + sp_text_context_setup_text(this); + this->nascent_object = 0; // we don't need it anymore, having created a real + } + + iterator_pair enter_pair; + bool success = sp_te_delete(this->text, this->text_sel_start, this->text_sel_end, enter_pair); + (void)success; // TODO cleanup + this->text_sel_start = this->text_sel_end = enter_pair.first; + + this->text_sel_start = this->text_sel_end = sp_te_insert_line(this->text, this->text_sel_start); + + sp_text_context_update_cursor(this); + sp_text_context_update_text_selection(this); + DocumentUndo::done(sp_desktop_document(desktop), SP_VERB_CONTEXT_TEXT, + _("New line")); + return TRUE; + } + case GDK_KEY_BackSpace: + if (this->text) { // if nascent_object, do nothing, but return TRUE; same for all other delete and move keys + + bool noSelection = false; + + if (MOD__CTRL(event)) { + this->text_sel_start = this->text_sel_end; + } + + if (this->text_sel_start == this->text_sel_end) { + if (MOD__CTRL(event)) { + this->text_sel_start.prevStartOfWord(); + } else { + this->text_sel_start.prevCursorPosition(); + } + noSelection = true; + } + + iterator_pair bspace_pair; + bool success = sp_te_delete(this->text, this->text_sel_start, this->text_sel_end, bspace_pair); + + if (noSelection) { + if (success) { + this->text_sel_start = this->text_sel_end = bspace_pair.first; + } else { // nothing deleted + this->text_sel_start = this->text_sel_end = bspace_pair.second; + } + } else { + if (success) { + this->text_sel_start = this->text_sel_end = bspace_pair.first; + } else { // nothing deleted + this->text_sel_start = bspace_pair.first; + this->text_sel_end = bspace_pair.second; + } + } + + sp_text_context_update_cursor(this); + sp_text_context_update_text_selection(this); + DocumentUndo::done(sp_desktop_document(desktop), SP_VERB_CONTEXT_TEXT, + _("Backspace")); + } + return TRUE; + case GDK_KEY_Delete: + case GDK_KEY_KP_Delete: + if (this->text) { + bool noSelection = false; + + if (MOD__CTRL(event)) { + this->text_sel_start = this->text_sel_end; + } + + if (this->text_sel_start == this->text_sel_end) { + if (MOD__CTRL(event)) { + this->text_sel_end.nextEndOfWord(); + } else { + this->text_sel_end.nextCursorPosition(); + } + noSelection = true; + } + + iterator_pair del_pair; + bool success = sp_te_delete(this->text, this->text_sel_start, this->text_sel_end, del_pair); + + if (noSelection) { + this->text_sel_start = this->text_sel_end = del_pair.first; + } else { + if (success) { + this->text_sel_start = this->text_sel_end = del_pair.first; + } else { // nothing deleted + this->text_sel_start = del_pair.first; + this->text_sel_end = del_pair.second; + } + } + + + sp_text_context_update_cursor(this); + sp_text_context_update_text_selection(this); + DocumentUndo::done(sp_desktop_document(desktop), SP_VERB_CONTEXT_TEXT, + _("Delete")); + } + return TRUE; + case GDK_KEY_Left: + case GDK_KEY_KP_Left: + case GDK_KEY_KP_4: + if (this->text) { + if (MOD__ALT(event)) { + gint mul = 1 + gobble_key_events( + get_group0_keyval(&event->key), 0); // with any mask + if (MOD__SHIFT(event)) + sp_te_adjust_kerning_screen(this->text, this->text_sel_start, this->text_sel_end, desktop, Geom::Point(mul*-10, 0)); + else + sp_te_adjust_kerning_screen(this->text, this->text_sel_start, this->text_sel_end, desktop, Geom::Point(mul*-1, 0)); + sp_text_context_update_cursor(this); + sp_text_context_update_text_selection(this); + DocumentUndo::maybeDone(sp_desktop_document(desktop), "kern:left", SP_VERB_CONTEXT_TEXT, + _("Kern to the left")); + } else { + if (MOD__CTRL(event)) + this->text_sel_end.cursorLeftWithControl(); + else + this->text_sel_end.cursorLeft(); + cursor_moved = true; + break; + } + } + return TRUE; + case GDK_KEY_Right: + case GDK_KEY_KP_Right: + case GDK_KEY_KP_6: + if (this->text) { + if (MOD__ALT(event)) { + gint mul = 1 + gobble_key_events( + get_group0_keyval(&event->key), 0); // with any mask + if (MOD__SHIFT(event)) + sp_te_adjust_kerning_screen(this->text, this->text_sel_start, this->text_sel_end, desktop, Geom::Point(mul*10, 0)); + else + sp_te_adjust_kerning_screen(this->text, this->text_sel_start, this->text_sel_end, desktop, Geom::Point(mul*1, 0)); + sp_text_context_update_cursor(this); + sp_text_context_update_text_selection(this); + DocumentUndo::maybeDone(sp_desktop_document(desktop), "kern:right", SP_VERB_CONTEXT_TEXT, + _("Kern to the right")); + } else { + if (MOD__CTRL(event)) + this->text_sel_end.cursorRightWithControl(); + else + this->text_sel_end.cursorRight(); + cursor_moved = true; + break; + } + } + return TRUE; + case GDK_KEY_Up: + case GDK_KEY_KP_Up: + case GDK_KEY_KP_8: + if (this->text) { + if (MOD__ALT(event)) { + gint mul = 1 + gobble_key_events( + get_group0_keyval(&event->key), 0); // with any mask + if (MOD__SHIFT(event)) + sp_te_adjust_kerning_screen(this->text, this->text_sel_start, this->text_sel_end, desktop, Geom::Point(0, mul*-10)); + else + sp_te_adjust_kerning_screen(this->text, this->text_sel_start, this->text_sel_end, desktop, Geom::Point(0, mul*-1)); + sp_text_context_update_cursor(this); + sp_text_context_update_text_selection(this); + DocumentUndo::maybeDone(sp_desktop_document(desktop), "kern:up", SP_VERB_CONTEXT_TEXT, + _("Kern up")); + } else { + if (MOD__CTRL(event)) + this->text_sel_end.cursorUpWithControl(); + else + this->text_sel_end.cursorUp(); + cursor_moved = true; + break; + } + } + return TRUE; + case GDK_KEY_Down: + case GDK_KEY_KP_Down: + case GDK_KEY_KP_2: + if (this->text) { + if (MOD__ALT(event)) { + gint mul = 1 + gobble_key_events( + get_group0_keyval(&event->key), 0); // with any mask + if (MOD__SHIFT(event)) + sp_te_adjust_kerning_screen(this->text, this->text_sel_start, this->text_sel_end, desktop, Geom::Point(0, mul*10)); + else + sp_te_adjust_kerning_screen(this->text, this->text_sel_start, this->text_sel_end, desktop, Geom::Point(0, mul*1)); + sp_text_context_update_cursor(this); + sp_text_context_update_text_selection(this); + DocumentUndo::maybeDone(sp_desktop_document(desktop), "kern:down", SP_VERB_CONTEXT_TEXT, + _("Kern down")); + } else { + if (MOD__CTRL(event)) + this->text_sel_end.cursorDownWithControl(); + else + this->text_sel_end.cursorDown(); + cursor_moved = true; + break; + } + } + return TRUE; + case GDK_KEY_Home: + case GDK_KEY_KP_Home: + if (this->text) { + if (MOD__CTRL(event)) + this->text_sel_end.thisStartOfShape(); + else + this->text_sel_end.thisStartOfLine(); + cursor_moved = true; + break; + } + return TRUE; + case GDK_KEY_End: + case GDK_KEY_KP_End: + if (this->text) { + if (MOD__CTRL(event)) + this->text_sel_end.nextStartOfShape(); + else + this->text_sel_end.thisEndOfLine(); + cursor_moved = true; + break; + } + return TRUE; + case GDK_KEY_Page_Down: + case GDK_KEY_KP_Page_Down: + if (this->text) { + this->text_sel_end.cursorDown(screenlines); + cursor_moved = true; + break; + } + return TRUE; + case GDK_KEY_Page_Up: + case GDK_KEY_KP_Page_Up: + if (this->text) { + this->text_sel_end.cursorUp(screenlines); + cursor_moved = true; + break; + } + return TRUE; + case GDK_KEY_Escape: + if (this->creating) { + this->creating = 0; + if (this->grabbed) { + sp_canvas_item_ungrab(this->grabbed, GDK_CURRENT_TIME); + this->grabbed = NULL; + } + Inkscape::Rubberband::get(desktop)->stop(); + } else { + sp_desktop_selection(desktop)->clear(); + } + this->nascent_object = FALSE; + return TRUE; + case GDK_KEY_bracketleft: + if (this->text) { + if (MOD__ALT(event) || MOD__CTRL(event)) { + if (MOD__ALT(event)) { + if (MOD__SHIFT(event)) { + // FIXME: alt+shift+[] does not work, don't know why + sp_te_adjust_rotation_screen(this->text, this->text_sel_start, this->text_sel_end, desktop, -10); + } else { + sp_te_adjust_rotation_screen(this->text, this->text_sel_start, this->text_sel_end, desktop, -1); + } + } else { + sp_te_adjust_rotation(this->text, this->text_sel_start, this->text_sel_end, desktop, -90); + } + DocumentUndo::maybeDone(sp_desktop_document(desktop), "textrot:ccw", SP_VERB_CONTEXT_TEXT, + _("Rotate counterclockwise")); + sp_text_context_update_cursor(this); + sp_text_context_update_text_selection(this); + return TRUE; + } + } + break; + case GDK_KEY_bracketright: + if (this->text) { + if (MOD__ALT(event) || MOD__CTRL(event)) { + if (MOD__ALT(event)) { + if (MOD__SHIFT(event)) { + // FIXME: alt+shift+[] does not work, don't know why + sp_te_adjust_rotation_screen(this->text, this->text_sel_start, this->text_sel_end, desktop, 10); + } else { + sp_te_adjust_rotation_screen(this->text, this->text_sel_start, this->text_sel_end, desktop, 1); + } + } else { + sp_te_adjust_rotation(this->text, this->text_sel_start, this->text_sel_end, desktop, 90); + } + DocumentUndo::maybeDone(sp_desktop_document(desktop), "textrot:cw", SP_VERB_CONTEXT_TEXT, + _("Rotate clockwise")); + sp_text_context_update_cursor(this); + sp_text_context_update_text_selection(this); + return TRUE; + } + } + break; + case GDK_KEY_less: + case GDK_KEY_comma: + if (this->text) { + if (MOD__ALT(event)) { + if (MOD__CTRL(event)) { + if (MOD__SHIFT(event)) + sp_te_adjust_linespacing_screen(this->text, this->text_sel_start, this->text_sel_end, desktop, -10); + else + sp_te_adjust_linespacing_screen(this->text, this->text_sel_start, this->text_sel_end, desktop, -1); + DocumentUndo::maybeDone(sp_desktop_document(desktop), "linespacing:dec", SP_VERB_CONTEXT_TEXT, + _("Contract line spacing")); + } else { + if (MOD__SHIFT(event)) + sp_te_adjust_tspan_letterspacing_screen(this->text, this->text_sel_start, this->text_sel_end, desktop, -10); + else + sp_te_adjust_tspan_letterspacing_screen(this->text, this->text_sel_start, this->text_sel_end, desktop, -1); + DocumentUndo::maybeDone(sp_desktop_document(desktop), "letterspacing:dec", SP_VERB_CONTEXT_TEXT, + _("Contract letter spacing")); + } + sp_text_context_update_cursor(this); + sp_text_context_update_text_selection(this); + return TRUE; + } + } + break; + case GDK_KEY_greater: + case GDK_KEY_period: + if (this->text) { + if (MOD__ALT(event)) { + if (MOD__CTRL(event)) { + if (MOD__SHIFT(event)) + sp_te_adjust_linespacing_screen(this->text, this->text_sel_start, this->text_sel_end, desktop, 10); + else + sp_te_adjust_linespacing_screen(this->text, this->text_sel_start, this->text_sel_end, desktop, 1); + DocumentUndo::maybeDone(sp_desktop_document(desktop), "linespacing:inc", SP_VERB_CONTEXT_TEXT, + _("Expand line spacing")); + } else { + if (MOD__SHIFT(event)) + sp_te_adjust_tspan_letterspacing_screen(this->text, this->text_sel_start, this->text_sel_end, desktop, 10); + else + sp_te_adjust_tspan_letterspacing_screen(this->text, this->text_sel_start, this->text_sel_end, desktop, 1); + DocumentUndo::maybeDone(sp_desktop_document(desktop), "letterspacing:inc", SP_VERB_CONTEXT_TEXT, + _("Expand letter spacing"));\ + } + sp_text_context_update_cursor(this); + sp_text_context_update_text_selection(this); + return TRUE; + } + } + break; + default: + break; + } + + if (cursor_moved) { + if (!MOD__SHIFT(event)) + this->text_sel_start = this->text_sel_end; + if (old_start != this->text_sel_start || old_end != this->text_sel_end) { + sp_text_context_update_cursor(this); + sp_text_context_update_text_selection(this); + } + return TRUE; + } + + } else return TRUE; // return the "I took care of it" value if it was consumed by the IM + } else { // do nothing if there's no object to type in - the key will be sent to parent context, + // except up/down that are swallowed to prevent the zoom field from activation + if ((group0_keyval == GDK_KEY_Up || + group0_keyval == GDK_KEY_Down || + group0_keyval == GDK_KEY_KP_Up || + group0_keyval == GDK_KEY_KP_Down ) + && !MOD__CTRL_ONLY(event)) { + return TRUE; + } else if (group0_keyval == GDK_KEY_Escape) { // cancel rubberband + if (this->creating) { + this->creating = 0; + if (this->grabbed) { + sp_canvas_item_ungrab(this->grabbed, GDK_CURRENT_TIME); + this->grabbed = NULL; + } + Inkscape::Rubberband::get(desktop)->stop(); + } + } else if ((group0_keyval == GDK_KEY_x || group0_keyval == GDK_KEY_X) && MOD__ALT_ONLY(event)) { + desktop->setToolboxFocusTo ("altx-text"); + return TRUE; + } + } + break; + } + + case GDK_KEY_RELEASE: + if (!this->unimode && this->imc && gtk_im_context_filter_keypress(this->imc, (GdkEventKey*) event)) { + return TRUE; + } + break; + default: + break; + } + + // if nobody consumed it so far +// if ((SP_EVENT_CONTEXT_CLASS(sp_text_context_parent_class))->root_handler) { // and there's a handler in parent context, +// return (SP_EVENT_CONTEXT_CLASS(sp_text_context_parent_class))->root_handler(event_context, event); // send event to parent +// } else { +// return FALSE; // return "I did nothing" value so that global shortcuts can be activated +// } + return ToolBase::root_handler(event); + +} + +/** + Attempts to paste system clipboard into the currently edited text, returns true on success + */ +bool sp_text_paste_inline(ToolBase *ec) +{ + if (!SP_IS_TEXT_CONTEXT(ec)) + return false; + + TextTool *tc = SP_TEXT_CONTEXT(ec); + + if ((tc->text) || (tc->nascent_object)) { + // there is an active text object in this context, or a new object was just created + + Glib::RefPtr refClipboard = Gtk::Clipboard::get(); + Glib::ustring const clip_text = refClipboard->wait_for_text(); + + if (!clip_text.empty()) { + // Fix for 244940 + // The XML standard defines the following as valid characters + // (Extensible Markup Language (XML) 1.0 (Fourth Edition) paragraph 2.2) + // char ::= #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD] | [#x10000-#x10FFFF] + // Since what comes in off the paste buffer will go right into XML, clean + // the text here. + Glib::ustring text(clip_text); + Glib::ustring::iterator itr = text.begin(); + gunichar paste_string_uchar; + + while(itr != text.end()) + { + paste_string_uchar = *itr; + + // Make sure we don't have a control character. We should really check + // for the whole range above... Add the rest of the invalid cases from + // above if we find additional issues + if(paste_string_uchar >= 0x00000020 || + paste_string_uchar == 0x00000009 || + paste_string_uchar == 0x0000000A || + paste_string_uchar == 0x0000000D) { + itr++; + } else { + itr = text.erase(itr); + } + } + + if (!tc->text) { // create text if none (i.e. if nascent_object) + sp_text_context_setup_text(tc); + tc->nascent_object = 0; // we don't need it anymore, having created a real + } + + // using indices is slow in ustrings. Whatever. + Glib::ustring::size_type begin = 0; + for ( ; ; ) { + Glib::ustring::size_type end = text.find('\n', begin); + if (end == Glib::ustring::npos) { + if (begin != text.length()) + tc->text_sel_start = tc->text_sel_end = sp_te_replace(tc->text, tc->text_sel_start, tc->text_sel_end, text.substr(begin).c_str()); + break; + } + tc->text_sel_start = tc->text_sel_end = sp_te_replace(tc->text, tc->text_sel_start, tc->text_sel_end, text.substr(begin, end - begin).c_str()); + tc->text_sel_start = tc->text_sel_end = sp_te_insert_line(tc->text, tc->text_sel_start); + begin = end + 1; + } + DocumentUndo::done(sp_desktop_document(ec->desktop), SP_VERB_CONTEXT_TEXT, + _("Paste text")); + + return true; + } + } // FIXME: else create and select a new object under cursor! + + return false; +} + +/** + Gets the raw characters that comprise the currently selected text, converting line + breaks into lf characters. +*/ +Glib::ustring sp_text_get_selected_text(ToolBase const *ec) +{ + if (!SP_IS_TEXT_CONTEXT(ec)) + return ""; + TextTool const *tc = SP_TEXT_CONTEXT(ec); + if (tc->text == NULL) + return ""; + + return sp_te_get_string_multiline(tc->text, tc->text_sel_start, tc->text_sel_end); +} + +SPCSSAttr *sp_text_get_style_at_cursor(ToolBase const *ec) +{ + if (!SP_IS_TEXT_CONTEXT(ec)) + return NULL; + TextTool const *tc = SP_TEXT_CONTEXT(ec); + if (tc->text == NULL) + return NULL; + + SPObject const *obj = sp_te_object_at_position(tc->text, tc->text_sel_end); + + if (obj) { + return take_style_from_item(const_cast(obj)); + } + + return NULL; +} + +/** + Deletes the currently selected characters. Returns false if there is no + text selection currently. +*/ +bool sp_text_delete_selection(ToolBase *ec) +{ + if (!SP_IS_TEXT_CONTEXT(ec)) + return false; + TextTool *tc = SP_TEXT_CONTEXT(ec); + if (tc->text == NULL) + return false; + + if (tc->text_sel_start == tc->text_sel_end) + return false; + + iterator_pair pair; + bool success = sp_te_delete(tc->text, tc->text_sel_start, tc->text_sel_end, pair); + + + if (success) { + tc->text_sel_start = tc->text_sel_end = pair.first; + } else { // nothing deleted + tc->text_sel_start = pair.first; + tc->text_sel_end = pair.second; + } + + sp_text_context_update_cursor(tc); + sp_text_context_update_text_selection(tc); + + return true; +} + +/** + * \param selection Should not be NULL. + */ +static void +sp_text_context_selection_changed(Inkscape::Selection *selection, TextTool *tc) +{ + g_assert(selection != NULL); + + ToolBase *ec = SP_EVENT_CONTEXT(tc); + + ec->shape_editor->unset_item(SH_KNOTHOLDER); + SPItem *item = selection->singleItem(); + if (item && SP_IS_FLOWTEXT(item) && SP_FLOWTEXT(item)->has_internal_frame()) { + ec->shape_editor->set_item(item, SH_KNOTHOLDER); + } + + if (tc->text && (item != tc->text)) { + sp_text_context_forget_text(tc); + } + tc->text = NULL; + + if (SP_IS_TEXT(item) || SP_IS_FLOWTEXT(item)) { + tc->text = item; + Inkscape::Text::Layout const *layout = te_get_layout(tc->text); + if (layout) + tc->text_sel_start = tc->text_sel_end = layout->end(); + } else { + tc->text = NULL; + } + + // we update cursor without scrolling, because this position may not be final; + // item_handler moves cusros to the point of click immediately + sp_text_context_update_cursor(tc, false); + sp_text_context_update_text_selection(tc); +} + +static void +sp_text_context_selection_modified(Inkscape::Selection */*selection*/, guint /*flags*/, TextTool *tc) +{ + sp_text_context_update_cursor(tc); + sp_text_context_update_text_selection(tc); +} + +static bool sp_text_context_style_set(SPCSSAttr const *css, TextTool *tc) +{ + if (tc->text == NULL) + return false; + if (tc->text_sel_start == tc->text_sel_end) + return false; // will get picked up by the parent and applied to the whole text object + + sp_te_apply_style(tc->text, tc->text_sel_start, tc->text_sel_end, css); + DocumentUndo::done(sp_desktop_document(tc->desktop), SP_VERB_CONTEXT_TEXT, + _("Set text style")); + sp_text_context_update_cursor(tc); + sp_text_context_update_text_selection(tc); + + return true; +} + +static int +sp_text_context_style_query(SPStyle *style, int property, TextTool *tc) +{ + if (tc->text == NULL) { + return QUERY_STYLE_NOTHING; + } + const Inkscape::Text::Layout *layout = te_get_layout(tc->text); + if (layout == NULL) { + return QUERY_STYLE_NOTHING; + } + sp_text_context_validate_cursor_iterators(tc); + + GSList *styles_list = NULL; + + Inkscape::Text::Layout::iterator begin_it, end_it; + if (tc->text_sel_start < tc->text_sel_end) { + begin_it = tc->text_sel_start; + end_it = tc->text_sel_end; + } else { + begin_it = tc->text_sel_end; + end_it = tc->text_sel_start; + } + if (begin_it == end_it) { + if (!begin_it.prevCharacter()) { + end_it.nextCharacter(); + } + } + for (Inkscape::Text::Layout::iterator it = begin_it ; it < end_it ; it.nextStartOfSpan()) { + SPObject const *pos_obj = 0; + void *rawptr = 0; + layout->getSourceOfCharacter(it, &rawptr); + if (!rawptr || !SP_IS_OBJECT(rawptr)) { + continue; + } + pos_obj = SP_OBJECT(rawptr); + while (SP_IS_STRING(pos_obj) && pos_obj->parent) { + pos_obj = pos_obj->parent; // SPStrings don't have style + } + styles_list = g_slist_prepend(styles_list, (gpointer)pos_obj); + } + + int result = sp_desktop_query_style_from_list (styles_list, style, property); + + g_slist_free(styles_list); + return result; +} + +static void sp_text_context_validate_cursor_iterators(TextTool *tc) +{ + if (tc->text == NULL) + return; + Inkscape::Text::Layout const *layout = te_get_layout(tc->text); + if (layout) { // undo can change the text length without us knowing it + layout->validateIterator(&tc->text_sel_start); + layout->validateIterator(&tc->text_sel_end); + } +} + +static void sp_text_context_update_cursor(TextTool *tc, bool scroll_to_see) +{ + // due to interruptible display, tc may already be destroyed during a display update before + // the cursor update (can't do both atomically, alas) + if (!tc->desktop) return; + + if (tc->text) { + Geom::Point p0, p1; + sp_te_get_cursor_coords(tc->text, tc->text_sel_end, p0, p1); + Geom::Point const d0 = p0 * tc->text->i2dt_affine(); + Geom::Point const d1 = p1 * tc->text->i2dt_affine(); + + // scroll to show cursor + if (scroll_to_see) { + Geom::Point const center = SP_EVENT_CONTEXT(tc)->desktop->get_display_area().midpoint(); + if (Geom::L2(d0 - center) > Geom::L2(d1 - center)) + // unlike mouse moves, here we must scroll all the way at first shot, so we override the autoscrollspeed + SP_EVENT_CONTEXT(tc)->desktop->scroll_to_point(d0, 1.0); + else + SP_EVENT_CONTEXT(tc)->desktop->scroll_to_point(d1, 1.0); + } + + sp_canvas_item_show(tc->cursor); + tc->cursor->setCoords(d0, d1); + + /* fixme: ... need another transformation to get canvas widget coordinate space? */ + if (tc->imc) { + GdkRectangle im_cursor = { 0, 0, 1, 1 }; + Geom::Point const top_left = SP_EVENT_CONTEXT(tc)->desktop->get_display_area().corner(3); + Geom::Point const im_d0 = SP_EVENT_CONTEXT(tc)->desktop->d2w(d0 - top_left); + Geom::Point const im_d1 = SP_EVENT_CONTEXT(tc)->desktop->d2w(d1 - top_left); + im_cursor.x = (int) floor(im_d0[Geom::X]); + im_cursor.y = (int) floor(im_d1[Geom::Y]); + im_cursor.width = (int) floor(im_d1[Geom::X]) - im_cursor.x; + im_cursor.height = (int) floor(im_d0[Geom::Y]) - im_cursor.y; + gtk_im_context_set_cursor_location(tc->imc, &im_cursor); + } + + tc->show = TRUE; + tc->phase = 1; + + Inkscape::Text::Layout const *layout = te_get_layout(tc->text); + int const nChars = layout->iteratorToCharIndex(layout->end()); + char const *trunc = ""; + bool truncated = false; + if (layout->inputTruncated()) { + truncated = true; + trunc = _(" [truncated]"); + } + if (SP_IS_FLOWTEXT(tc->text)) { + SPItem *frame = SP_FLOWTEXT(tc->text)->get_frame (NULL); // first frame only + if (frame) { + if (truncated) { + SP_CTRLRECT(tc->frame)->setColor(0xff0000ff, false, 0); + } else { + SP_CTRLRECT(tc->frame)->setColor(0x0000ff7f, false, 0); + } + sp_canvas_item_show(tc->frame); + Geom::OptRect frame_bbox = frame->desktopVisualBounds(); + if (frame_bbox) { + SP_CTRLRECT(tc->frame)->setRectangle(*frame_bbox); + } + } + + SP_EVENT_CONTEXT(tc)->message_context->setF(Inkscape::NORMAL_MESSAGE, ngettext("Type or edit flowed text (%d character%s); Enter to start new paragraph.", "Type or edit flowed text (%d characters%s); Enter to start new paragraph.", nChars), nChars, trunc); + } else { + SP_EVENT_CONTEXT(tc)->message_context->setF(Inkscape::NORMAL_MESSAGE, ngettext("Type or edit text (%d character%s); Enter to start new line.", "Type or edit text (%d characters%s); Enter to start new line.", nChars), nChars, trunc); + } + + } else { + sp_canvas_item_hide(tc->cursor); + sp_canvas_item_hide(tc->frame); + tc->show = FALSE; + if (!tc->nascent_object) { + SP_EVENT_CONTEXT(tc)->message_context->set(Inkscape::NORMAL_MESSAGE, _("Click to select or create text, drag to create flowed text; then type.")); // FIXME: this is a copy of string from tools-switch, do not desync + } + } + + SP_EVENT_CONTEXT(tc)->desktop->emitToolSubselectionChanged((gpointer)tc); +} + +static void sp_text_context_update_text_selection(TextTool *tc) +{ + // due to interruptible display, tc may already be destroyed during a display update before + // the selection update (can't do both atomically, alas) + if (!tc->desktop) return; + + for (std::vector::iterator it = tc->text_selection_quads.begin() ; it != tc->text_selection_quads.end() ; it++) { + sp_canvas_item_hide(*it); + sp_canvas_item_destroy(*it); + } + tc->text_selection_quads.clear(); + + std::vector quads; + if (tc->text != NULL) + quads = sp_te_create_selection_quads(tc->text, tc->text_sel_start, tc->text_sel_end, (tc->text)->i2dt_affine()); + for (unsigned i = 0 ; i < quads.size() ; i += 4) { + SPCanvasItem *quad_canvasitem; + quad_canvasitem = sp_canvas_item_new(sp_desktop_controls(tc->desktop), SP_TYPE_CTRLQUADR, NULL); + // FIXME: make the color settable in prefs + // for now, use semitrasparent blue, as cairo cannot do inversion :( + sp_ctrlquadr_set_rgba32(SP_CTRLQUADR(quad_canvasitem), 0x00777777); + sp_ctrlquadr_set_coords(SP_CTRLQUADR(quad_canvasitem), quads[i], quads[i+1], quads[i+2], quads[i+3]); + sp_canvas_item_show(quad_canvasitem); + tc->text_selection_quads.push_back(quad_canvasitem); + } +} + +static gint sp_text_context_timeout(TextTool *tc) +{ + if (tc->show) { + sp_canvas_item_show(tc->cursor); + if (tc->phase) { + tc->phase = 0; + tc->cursor->setRgba32(0x000000ff); + } else { + tc->phase = 1; + tc->cursor->setRgba32(0xffffffff); + } + } + + return TRUE; +} + +static void sp_text_context_forget_text(TextTool *tc) +{ + if (! tc->text) return; + SPItem *ti = tc->text; + (void)ti; + /* We have to set it to zero, + * or selection changed signal messes everything up */ + tc->text = NULL; + +/* FIXME: this automatic deletion when nothing is inputted crashes the XML edittor and also crashes when duplicating an empty flowtext. + So don't create an empty flowtext in the first place? Create it when first character is typed. + */ +/* + if ((SP_IS_TEXT(ti) || SP_IS_FLOWTEXT(ti)) && sp_te_input_is_empty(ti)) { + Inkscape::XML::Node *text_repr = ti->getRepr(); + // the repr may already have been unparented + // if we were called e.g. as the result of + // an undo or the element being removed from + // the XML editor + if ( text_repr && text_repr->parent() ) { + sp_repr_unparent(text_repr); + SPDocumentUndo::done(sp_desktop_document(tc->desktop), SP_VERB_CONTEXT_TEXT, + _("Remove empty text")); + } + } +*/ +} + +gint sptc_focus_in(GtkWidget */*widget*/, GdkEventFocus */*event*/, TextTool *tc) +{ + gtk_im_context_focus_in(tc->imc); + return FALSE; +} + +gint sptc_focus_out(GtkWidget */*widget*/, GdkEventFocus */*event*/, TextTool *tc) +{ + gtk_im_context_focus_out(tc->imc); + return FALSE; +} + +static void sptc_commit(GtkIMContext */*imc*/, gchar *string, TextTool *tc) +{ + if (!tc->text) { + sp_text_context_setup_text(tc); + tc->nascent_object = 0; // we don't need it anymore, having created a real + } + + tc->text_sel_start = tc->text_sel_end = sp_te_replace(tc->text, tc->text_sel_start, tc->text_sel_end, string); + sp_text_context_update_cursor(tc); + sp_text_context_update_text_selection(tc); + + DocumentUndo::done(tc->text->document, SP_VERB_CONTEXT_TEXT, + _("Type text")); +} + +void sp_text_context_place_cursor (TextTool *tc, SPObject *text, Inkscape::Text::Layout::iterator where) +{ + SP_EVENT_CONTEXT_DESKTOP (tc)->selection->set (text); + tc->text_sel_start = tc->text_sel_end = where; + sp_text_context_update_cursor(tc); + sp_text_context_update_text_selection(tc); +} + +void sp_text_context_place_cursor_at (TextTool *tc, SPObject *text, Geom::Point const p) +{ + SP_EVENT_CONTEXT_DESKTOP (tc)->selection->set (text); + sp_text_context_place_cursor (tc, text, sp_te_get_position_by_coords(tc->text, p)); +} + +Inkscape::Text::Layout::iterator *sp_text_context_get_cursor_position(TextTool *tc, SPObject *text) +{ + if (text != tc->text) + return NULL; + return &(tc->text_sel_end); +} + +} +} +} + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/ui/tools/text-tool.h b/src/ui/tools/text-tool.h new file mode 100644 index 000000000..ef8a67984 --- /dev/null +++ b/src/ui/tools/text-tool.h @@ -0,0 +1,101 @@ +#ifndef __SP_TEXT_CONTEXT_H__ +#define __SP_TEXT_CONTEXT_H__ + +/* + * TextTool + * + * Authors: + * Lauris Kaplinski + * bulia byak + * + * Copyright (C) 1999-2005 authors + * Copyright (C) 2001 Ximian, Inc. + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +/* #include */ +#include +#include +#include + +#include "ui/tools/tool-base.h" +#include <2geom/point.h> +#include "libnrtype/Layout-TNG.h" + +#define SP_TEXT_CONTEXT(obj) (dynamic_cast((Inkscape::UI::Tools::ToolBase*)obj)) +#define SP_IS_TEXT_CONTEXT(obj) (dynamic_cast((const Inkscape::UI::Tools::ToolBase*)obj) != NULL) + +struct SPCtrlLine; + +namespace Inkscape { +namespace UI { +namespace Tools { + +class TextTool : public ToolBase { +public: + TextTool(); + virtual ~TextTool(); + + sigc::connection sel_changed_connection; + sigc::connection sel_modified_connection; + sigc::connection style_set_connection; + sigc::connection style_query_connection; + + GtkIMContext *imc; + + SPItem *text; // the text we're editing, or NULL if none selected + + /* Text item position in root coordinates */ + Geom::Point pdoc; + /* Insertion point position */ + Inkscape::Text::Layout::iterator text_sel_start; + Inkscape::Text::Layout::iterator text_sel_end; + + gchar uni[9]; + bool unimode; + guint unipos; + + SPCtrlLine *cursor; + SPCanvasItem *indicator; + SPCanvasItem *frame; // hiliting the first frame of flowtext; FIXME: make this a list to accommodate arbitrarily many chained shapes + std::vector text_selection_quads; + gint timeout; + guint show : 1; + guint phase : 1; + guint nascent_object : 1; // true if we're clicked on canvas to put cursor, but no text typed yet so ->text is still NULL + + guint over_text : 1; // true if cursor is over a text object + + guint dragging : 2; // dragging selection over text + + guint creating : 1; // dragging rubberband to create flowtext + SPCanvasItem *grabbed; // we grab while we are creating, to get events even if the mouse goes out of the window + Geom::Point p0; // initial point if the flowtext rect + + /* Preedit String */ + gchar* preedit_string; + + static const std::string prefsPath; + + virtual void setup(); + virtual void finish(); + virtual bool root_handler(GdkEvent* event); + virtual bool item_handler(SPItem* item, GdkEvent* event); + + virtual const std::string& getPrefsPath(); +}; + +bool sp_text_paste_inline(ToolBase *ec); +Glib::ustring sp_text_get_selected_text(ToolBase const *ec); +SPCSSAttr *sp_text_get_style_at_cursor(ToolBase const *ec); +bool sp_text_delete_selection(ToolBase *ec); +void sp_text_context_place_cursor (TextTool *tc, SPObject *text, Inkscape::Text::Layout::iterator where); +void sp_text_context_place_cursor_at (TextTool *tc, SPObject *text, Geom::Point const p); +Inkscape::Text::Layout::iterator *sp_text_context_get_cursor_position(TextTool *tc, SPObject *text); + +} +} +} + +#endif diff --git a/src/ui/tools/tool-base.cpp b/src/ui/tools/tool-base.cpp new file mode 100644 index 000000000..c123aec27 --- /dev/null +++ b/src/ui/tools/tool-base.cpp @@ -0,0 +1,1541 @@ +/* + * Main event handling, and related helper functions. + * + * Authors: + * Lauris Kaplinski + * Frank Felfe + * bulia byak + * Jon A. Cruz + * Kris De Gussem + * + * Copyright (C) 1999-2012 authors + * Copyright (C) 2001-2002 Ximian, Inc. + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#if GLIBMM_DISABLE_DEPRECATED && HAVE_GLIBMM_THREADS_H +#include +#endif + +#include "shortcuts.h" +#include "file.h" +#include "ui/tools/tool-base.h" + +#include +#include +#include +#include +#include +#include + +#include "display/sp-canvas.h" +#include "xml/node-event-vector.h" +#include "sp-cursor.h" +#include "desktop.h" +#include "desktop-handles.h" +#include "desktop-events.h" +#include "desktop-style.h" +#include "widgets/desktop-widget.h" +#include "sp-namedview.h" +#include "selection.h" +#include "interface.h" +#include "macros.h" +#include "tools-switch.h" +#include "preferences.h" +#include "message-context.h" +#include "gradient-drag.h" +#include "attributes.h" +#include "rubberband.h" +#include "selcue.h" +#include "ui/tools/lpe-tool.h" +#include "ui/tool/control-point.h" +#include "shape-editor.h" +#include "sp-guide.h" +#include "color.h" + +// globals for temporary switching to selector by space +static bool selector_toggled = FALSE; +static int switch_selector_to = 0; + +// globals for temporary switching to dropper by 'D' +static bool dropper_toggled = FALSE; +static int switch_dropper_to = 0; + +//static gint xp = 0, yp = 0; // where drag started +//static gint tolerance = 0; +//static bool within_tolerance = false; + +// globals for keeping track of keyboard scroll events in order to accelerate +static guint32 scroll_event_time = 0; +static gdouble scroll_multiply = 1; +static guint scroll_keyval = 0; + + +namespace Inkscape { +namespace UI { +namespace Tools { + +static void set_event_location(SPDesktop * desktop, GdkEvent * event); + + +void ToolBase::set(const Inkscape::Preferences::Entry& /*val*/) { +} + +void ToolBase::activate() { +} + +void ToolBase::deactivate() { +} + +void ToolBase::finish() { + this->enableSelectionCue(false); +} + +ToolBase::ToolBase() { + this->hot_y = 0; + this->xp = 0; + this->cursor_shape = 0; + this->pref_observer = 0; + this->hot_x = 0; + this->yp = 0; + this->within_tolerance = false; + this->tolerance = 0; + //this->key = 0; + this->item_to_select = 0; + + this->desktop = NULL; + this->cursor = NULL; + this->message_context = NULL; + this->_selcue = NULL; + this->_grdrag = NULL; + this->space_panning = false; + this->shape_editor = NULL; + this->_delayed_snap_event = NULL; + this->_dse_callback_in_process = false; + //this->tool_url = NULL; +} + +ToolBase::~ToolBase() { + if (this->message_context) { + delete this->message_context; + } + + if (this->cursor != NULL) { +#if GTK_CHECK_VERSION(3,0,0) + g_object_unref(this->cursor); +#else + gdk_cursor_unref(this->cursor); +#endif + this->cursor = NULL; + } + + if (this->desktop) { + this->desktop = NULL; + } + + if (this->pref_observer) { + delete this->pref_observer; + } + + if (this->_delayed_snap_event) { + delete this->_delayed_snap_event; + } +} + + +/** + * Set the cursor to a standard GDK cursor + */ +static void sp_event_context_set_cursor(ToolBase *event_context, GdkCursorType cursor_type) { + + GtkWidget *w = GTK_WIDGET(sp_desktop_canvas(event_context->desktop)); + GdkDisplay *display = gdk_display_get_default(); + GdkCursor *cursor = gdk_cursor_new_for_display(display, cursor_type); + +#if WITH_GTKMM_3_0 + if (cursor) { + gdk_window_set_cursor (gtk_widget_get_window (w), cursor); + g_object_unref (cursor); + } +#else + gdk_window_set_cursor (gtk_widget_get_window (w), cursor); + gdk_cursor_unref (cursor); +#endif + +} + +/** + * Recreates and draws cursor on desktop related to ToolBase. + */ +void ToolBase::sp_event_context_update_cursor() { + GtkWidget *w = GTK_WIDGET(sp_desktop_canvas(this->desktop)); + if (gtk_widget_get_window (w)) { + + GtkStyle *style = gtk_widget_get_style(w); + + /* fixme: */ + if (this->cursor_shape) { + GdkDisplay *display = gdk_display_get_default(); + if (gdk_display_supports_cursor_alpha(display) && gdk_display_supports_cursor_color(display)) { + bool fillHasColor=false, strokeHasColor=false; + guint32 fillColor = sp_desktop_get_color_tool(this->desktop, this->getPrefsPath(), true, &fillHasColor); + guint32 strokeColor = sp_desktop_get_color_tool(this->desktop, this->getPrefsPath(), false, &strokeHasColor); + double fillOpacity = fillHasColor ? sp_desktop_get_opacity_tool(this->desktop, this->getPrefsPath(), true) : 0; + double strokeOpacity = strokeHasColor ? sp_desktop_get_opacity_tool(this->desktop, this->getPrefsPath(), false) : 0; + + GdkPixbuf *pixbuf = sp_cursor_pixbuf_from_xpm( + this->cursor_shape, + style->black, style->white, + SP_RGBA32_U_COMPOSE(SP_RGBA32_R_U(fillColor),SP_RGBA32_G_U(fillColor),SP_RGBA32_B_U(fillColor),SP_COLOR_F_TO_U(fillOpacity)), + SP_RGBA32_U_COMPOSE(SP_RGBA32_R_U(strokeColor),SP_RGBA32_G_U(strokeColor),SP_RGBA32_B_U(strokeColor),SP_COLOR_F_TO_U(strokeOpacity)) + ); + if (pixbuf != NULL) { + if (this->cursor) { +#if GTK_CHECK_VERSION(3,0,0) + g_object_unref(this->cursor); +#else + gdk_cursor_unref(this->cursor); +#endif + } + this->cursor = gdk_cursor_new_from_pixbuf(display, pixbuf, this->hot_x, this->hot_y); + g_object_unref(pixbuf); + } + } else { + GdkPixbuf *pixbuf = gdk_pixbuf_new_from_xpm_data((const gchar **)this->cursor_shape); + + if (pixbuf) { + if (this->cursor) { +#if GTK_CHECK_VERSION(3,0,0) + g_object_unref(this->cursor); +#else + gdk_cursor_unref(this->cursor); +#endif + } + this->cursor = gdk_cursor_new_from_pixbuf(display, + pixbuf, this->hot_x, this->hot_y); + g_object_unref(pixbuf); + } + } + } + gdk_window_set_cursor(gtk_widget_get_window (w), this->cursor); + gdk_flush(); + } + this->desktop->waiting_cursor = false; +} + +/** + * Callback that gets called on initialization of ToolBase object. + * Redraws mouse cursor, at the moment. + */ + +/** + * When you override it, call this method first. + */ +void ToolBase::setup() { + this->pref_observer = new ToolPrefObserver(this->getPrefsPath(), this); + Inkscape::Preferences::get()->addObserver(*(this->pref_observer)); + + this->sp_event_context_update_cursor(); +} + +/** + * Gobbles next key events on the queue with the same keyval and mask. Returns the number of events consumed. + */ +gint gobble_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; +} + +/** + * Gobbles next motion notify events on the queue with the same mask. Returns the number of events consumed. + */ +gint gobble_motion_events(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_MOTION_NOTIFY + && (event_next->motion.state & mask)) { + // kill it + gdk_event_free(event_next); + // get next + event_next = gdk_event_get(); + i++; + } + // otherwise, put it back onto the queue + if (event_next) + gdk_event_put(event_next); + + return i; +} + +/** + * Toggles current tool between active tool and selector tool. + * Subroutine of sp_event_context_private_root_handler(). + */ +static void sp_toggle_selector(SPDesktop *dt) { + if (!dt->event_context) + return; + + if (tools_isactive(dt, TOOLS_SELECT)) { + if (selector_toggled) { + if (switch_selector_to) + tools_switch(dt, switch_selector_to); + selector_toggled = FALSE; + } else + return; + } else { + selector_toggled = TRUE; + switch_selector_to = tools_active(dt); + tools_switch(dt, TOOLS_SELECT); + } +} + +/** + * Toggles current tool between active tool and dropper tool. + * Subroutine of sp_event_context_private_root_handler(). + */ +void sp_toggle_dropper(SPDesktop *dt) { + if (!dt->event_context) + return; + + if (tools_isactive(dt, TOOLS_DROPPER)) { + if (dropper_toggled) { + if (switch_dropper_to) + tools_switch(dt, switch_dropper_to); + dropper_toggled = FALSE; + } else + return; + } else { + dropper_toggled = TRUE; + switch_dropper_to = tools_active(dt); + tools_switch(dt, TOOLS_DROPPER); + } +} + +/** + * Calculates and keeps track of scroll acceleration. + * Subroutine of sp_event_context_private_root_handler(). + */ +static gdouble accelerate_scroll(GdkEvent *event, gdouble acceleration, + SPCanvas */*canvas*/) { + guint32 time_diff = ((GdkEventKey *) event)->time - scroll_event_time; + + /* key pressed within 500ms ? (1/2 second) */ + if (time_diff > 500 || event->key.keyval != scroll_keyval) { + scroll_multiply = 1; // abort acceleration + } else { + scroll_multiply += acceleration; // continue acceleration + } + + scroll_event_time = ((GdkEventKey *) event)->time; + scroll_keyval = event->key.keyval; + + return scroll_multiply; +} + +/** + * Main event dispatch, gets called from Gdk. + */ +//static gint sp_event_context_private_root_handler( +// ToolBase *event_context, GdkEvent *event) { +// +// return event_context->ceventcontext->root_handler(event); +//} + +bool ToolBase::root_handler(GdkEvent* event) { + static Geom::Point button_w; + static unsigned int panning = 0; + static unsigned int panning_cursor = 0; + static unsigned int zoom_rb = 0; + + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + + /// @todo REmove redundant /value in preference keys + tolerance = prefs->getIntLimited("/options/dragtolerance/value", 0, 0, 100); + + gint ret = FALSE; + + switch (event->type) { + case GDK_2BUTTON_PRESS: + if (panning) { + panning = 0; + sp_canvas_item_ungrab(SP_CANVAS_ITEM(desktop->acetate), event->button.time); + ret = TRUE; + } else { + /* sp_desktop_dialog(); */ + } + break; + + case GDK_BUTTON_PRESS: + // save drag origin + xp = (gint) event->button.x; + yp = (gint) event->button.y; + within_tolerance = true; + + button_w = Geom::Point(event->button.x, event->button.y); + + switch (event->button.button) { + case 1: + if (this->space_panning) { + // When starting panning, make sure there are no snap events pending because these might disable the panning again + sp_event_context_discard_delayed_snap_event(this); + panning = 1; + + sp_canvas_item_grab(SP_CANVAS_ITEM(desktop->acetate), + GDK_KEY_RELEASE_MASK | GDK_BUTTON_RELEASE_MASK + | GDK_POINTER_MOTION_MASK + | GDK_POINTER_MOTION_HINT_MASK, NULL, + event->button.time - 1); + + ret = TRUE; + } + break; + + case 2: + if (event->button.state & GDK_SHIFT_MASK) { + zoom_rb = 2; + } else { + // When starting panning, make sure there are no snap events pending because these might disable the panning again + sp_event_context_discard_delayed_snap_event(this); + panning = 2; + + sp_canvas_item_grab(SP_CANVAS_ITEM(desktop->acetate), + GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK + | GDK_POINTER_MOTION_HINT_MASK, NULL, + event->button.time - 1); + + } + + ret = TRUE; + break; + + case 3: + if ((event->button.state & GDK_SHIFT_MASK) || (event->button.state & GDK_CONTROL_MASK)) { + // When starting panning, make sure there are no snap events pending because these might disable the panning again + sp_event_context_discard_delayed_snap_event(this); + panning = 3; + + sp_canvas_item_grab(SP_CANVAS_ITEM(desktop->acetate), + GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK + | GDK_POINTER_MOTION_HINT_MASK, NULL, + event->button.time); + + ret = TRUE; + } else { + sp_event_root_menu_popup(desktop, NULL, event); + } + break; + + default: + break; + } + break; + + case GDK_MOTION_NOTIFY: + if (panning) { + if (panning == 4 && !xp && !yp ) { + // + mouse panning started, save location and grab canvas + xp = event->motion.x; + yp = event->motion.y; + button_w = Geom::Point(event->motion.x, event->motion.y); + + sp_canvas_item_grab(SP_CANVAS_ITEM(desktop->acetate), + GDK_KEY_RELEASE_MASK | GDK_BUTTON_RELEASE_MASK + | GDK_POINTER_MOTION_MASK + | GDK_POINTER_MOTION_HINT_MASK, NULL, + event->motion.time - 1); + } + + if ((panning == 2 && !(event->motion.state & GDK_BUTTON2_MASK)) + || (panning == 1 && !(event->motion.state & GDK_BUTTON1_MASK)) + || (panning == 3 && !(event->motion.state & GDK_BUTTON3_MASK))) { + /* Gdk seems to lose button release for us sometimes :-( */ + panning = 0; + sp_canvas_item_ungrab(SP_CANVAS_ITEM(desktop->acetate), event->button.time); + ret = TRUE; + } else { + if (within_tolerance && (abs((gint) event->motion.x - xp) + < tolerance) && (abs((gint) event->motion.y - yp) + < tolerance)) { + // do not drag if we're within tolerance from origin + break; + } + + // Once the user has moved farther than tolerance from + // the original location (indicating they intend to move + // the object, not click), then always process the motion + // notify coordinates as given (no snapping back to origin) + within_tolerance = false; + + // gobble subsequent motion events to prevent "sticking" + // when scrolling is slow + gobble_motion_events(panning == 2 ? GDK_BUTTON2_MASK : (panning + == 1 ? GDK_BUTTON1_MASK : GDK_BUTTON3_MASK)); + + if (panning_cursor == 0) { + panning_cursor = 1; + sp_event_context_set_cursor(this, GDK_FLEUR); + } + + Geom::Point const motion_w(event->motion.x, event->motion.y); + Geom::Point const moved_w(motion_w - button_w); + this->desktop->scroll_world(moved_w, true); // we're still scrolling, do not redraw + ret = TRUE; + } + } else if (zoom_rb) { + Geom::Point const motion_w(event->motion.x, event->motion.y); + Geom::Point const motion_dt(desktop->w2d(motion_w)); + + if (within_tolerance && (abs((gint) event->motion.x - xp) + < tolerance) && (abs((gint) event->motion.y - yp) + < tolerance)) { + break; // do not drag if we're within tolerance from origin + } + + // Once the user has moved farther than tolerance from the original location + // (indicating they intend to move the object, not click), then always process the + // motion notify coordinates as given (no snapping back to origin) + within_tolerance = false; + + if (Inkscape::Rubberband::get(desktop)->is_started()) { + Inkscape::Rubberband::get(desktop)->move(motion_dt); + } else { + Inkscape::Rubberband::get(desktop)->start(desktop, motion_dt); + } + + if (zoom_rb == 2) { + gobble_motion_events(GDK_BUTTON2_MASK); + } + } + break; + + case GDK_BUTTON_RELEASE: + xp = yp = 0; + + if (panning_cursor == 1) { + panning_cursor = 0; + GtkWidget *w = GTK_WIDGET(sp_desktop_canvas(this->desktop)); + gdk_window_set_cursor(gtk_widget_get_window (w), this->cursor); + } + + if (within_tolerance && (panning || zoom_rb)) { + zoom_rb = 0; + + if (panning) { + panning = 0; + sp_canvas_item_ungrab(SP_CANVAS_ITEM(desktop->acetate), + event->button.time); + } + + Geom::Point const event_w(event->button.x, event->button.y); + Geom::Point const event_dt(desktop->w2d(event_w)); + + double const zoom_inc = prefs->getDoubleLimited( + "/options/zoomincrement/value", M_SQRT2, 1.01, 10); + + desktop->zoom_relative_keep_point(event_dt, (event->button.state + & GDK_SHIFT_MASK) ? 1 / zoom_inc : zoom_inc); + + desktop->updateNow(); + ret = TRUE; + } else if (panning == event->button.button) { + panning = 0; + sp_canvas_item_ungrab(SP_CANVAS_ITEM(desktop->acetate), + event->button.time); + + // in slow complex drawings, some of the motion events are lost; + // to make up for this, we scroll it once again to the button-up event coordinates + // (i.e. canvas will always get scrolled all the way to the mouse release point, + // even if few intermediate steps were visible) + Geom::Point const motion_w(event->button.x, event->button.y); + Geom::Point const moved_w(motion_w - button_w); + + this->desktop->scroll_world(moved_w); + desktop->updateNow(); + ret = TRUE; + } else if (zoom_rb == event->button.button) { + zoom_rb = 0; + + Geom::OptRect const b = Inkscape::Rubberband::get(desktop)->getRectangle(); + Inkscape::Rubberband::get(desktop)->stop(); + + if (b && !within_tolerance) { + desktop->set_display_area(*b, 10); + } + + ret = TRUE; + } + break; + + case GDK_KEY_PRESS: { + double const acceleration = prefs->getDoubleLimited( + "/options/scrollingacceleration/value", 0, 0, 6); + + int const key_scroll = prefs->getIntLimited("/options/keyscroll/value", + 10, 0, 1000); + + switch (get_group0_keyval(&event->key)) { + // GDK insists on stealing these keys (F1 for no idea what, tab for cycling widgets + // in the editing window). So we resteal them back and run our regular shortcut + // invoker on them. + unsigned int shortcut; + case GDK_KEY_Tab: + case GDK_KEY_ISO_Left_Tab: + case GDK_KEY_F1: + shortcut = get_group0_keyval(&event->key); + + if (event->key.state & GDK_SHIFT_MASK) { + shortcut |= SP_SHORTCUT_SHIFT_MASK; + } + + if (event->key.state & GDK_CONTROL_MASK) { + shortcut |= SP_SHORTCUT_CONTROL_MASK; + } + + if (event->key.state & GDK_MOD1_MASK) { + shortcut |= SP_SHORTCUT_ALT_MASK; + } + + ret = sp_shortcut_invoke(shortcut, desktop); + break; + + case GDK_KEY_Q: + case GDK_KEY_q: + if (desktop->quick_zoomed()) { + ret = TRUE; + } + if (!MOD__SHIFT(event) && !MOD__CTRL(event) && !MOD__ALT(event)) { + desktop->zoom_quick(true); + ret = TRUE; + } + break; + + case GDK_KEY_W: + case GDK_KEY_w: + case GDK_KEY_F4: + /* Close view */ + if (MOD__CTRL_ONLY(event)) { + sp_ui_close_view(NULL); + ret = TRUE; + } + break; + + case GDK_KEY_Left: // Ctrl Left + case GDK_KEY_KP_Left: + case GDK_KEY_KP_4: + if (MOD__CTRL_ONLY(event)) { + int i = (int) floor(key_scroll * accelerate_scroll(event, + acceleration, sp_desktop_canvas(desktop))); + + gobble_key_events(get_group0_keyval(&event->key), GDK_CONTROL_MASK); + this->desktop->scroll_world(i, 0); + ret = TRUE; + } + break; + + case GDK_KEY_Up: // Ctrl Up + case GDK_KEY_KP_Up: + case GDK_KEY_KP_8: + if (MOD__CTRL_ONLY(event)) { + int i = (int) floor(key_scroll * accelerate_scroll(event, + acceleration, sp_desktop_canvas(desktop))); + + gobble_key_events(get_group0_keyval(&event->key), GDK_CONTROL_MASK); + this->desktop->scroll_world(0, i); + ret = TRUE; + } + break; + + case GDK_KEY_Right: // Ctrl Right + case GDK_KEY_KP_Right: + case GDK_KEY_KP_6: + if (MOD__CTRL_ONLY(event)) { + int i = (int) floor(key_scroll * accelerate_scroll(event, + acceleration, sp_desktop_canvas(desktop))); + + gobble_key_events(get_group0_keyval(&event->key), GDK_CONTROL_MASK); + this->desktop->scroll_world(-i, 0); + ret = TRUE; + } + break; + + case GDK_KEY_Down: // Ctrl Down + case GDK_KEY_KP_Down: + case GDK_KEY_KP_2: + if (MOD__CTRL_ONLY(event)) { + int i = (int) floor(key_scroll * accelerate_scroll(event, + acceleration, sp_desktop_canvas(desktop))); + + gobble_key_events(get_group0_keyval(&event->key), GDK_CONTROL_MASK); + this->desktop->scroll_world(0, -i); + ret = TRUE; + } + break; + + case GDK_KEY_F10: + if (MOD__SHIFT_ONLY(event)) { + sp_event_root_menu_popup(desktop, NULL, event); + ret = TRUE; + } + break; + + case GDK_KEY_space: + xp = yp = 0; + within_tolerance = true; + panning = 4; + + this->space_panning = true; + this->message_context->set(Inkscape::INFORMATION_MESSAGE, + _("Space+mouse move to pan canvas")); + + ret = TRUE; + break; + + case GDK_KEY_z: + case GDK_KEY_Z: + if (MOD__ALT_ONLY(event)) { + desktop->zoom_grab_focus(); + ret = TRUE; + } + break; + + default: + break; + } + } + break; + + case GDK_KEY_RELEASE: + // Stop panning on any key release + if (this->space_panning) { + this->space_panning = false; + this->message_context->clear(); + } + + if (panning) { + panning = 0; + xp = yp = 0; + + sp_canvas_item_ungrab(SP_CANVAS_ITEM(desktop->acetate), + event->key.time); + + desktop->updateNow(); + } + + if (panning_cursor == 1) { + panning_cursor = 0; + GtkWidget *w = GTK_WIDGET(sp_desktop_canvas(this->desktop)); + gdk_window_set_cursor(gtk_widget_get_window (w), this->cursor); + } + + switch (get_group0_keyval(&event->key)) { + case GDK_KEY_space: + if (within_tolerance == true) { + // Space was pressed, but not panned + sp_toggle_selector(desktop); + ret = TRUE; + } + + within_tolerance = false; + break; + + case GDK_KEY_Q: + case GDK_KEY_q: + if (desktop->quick_zoomed()) { + desktop->zoom_quick(false); + ret = TRUE; + } + break; + + default: + break; + } + break; + + case GDK_SCROLL: { + bool ctrl = (event->scroll.state & GDK_CONTROL_MASK); + bool wheelzooms = prefs->getBool("/options/wheelzooms/value"); + + int const wheel_scroll = prefs->getIntLimited( + "/options/wheelscroll/value", 40, 0, 1000); + +#if GTK_CHECK_VERSION(3,0,0) + // Size of smooth-scrolls (only used in GTK+ 3) + gdouble delta_x = 0; + gdouble delta_y = 0; +#endif + + /* shift + wheel, pan left--right */ + if (event->scroll.state & GDK_SHIFT_MASK) { + switch (event->scroll.direction) { + case GDK_SCROLL_UP: + desktop->scroll_world(wheel_scroll, 0); + break; + + case GDK_SCROLL_DOWN: + desktop->scroll_world(-wheel_scroll, 0); + break; + + default: + break; + } + + /* ctrl + wheel, zoom in--out */ + } else if ((ctrl && !wheelzooms) || (!ctrl && wheelzooms)) { + double rel_zoom; + double const zoom_inc = prefs->getDoubleLimited( + "/options/zoomincrement/value", M_SQRT2, 1.01, 10); + + switch (event->scroll.direction) { + case GDK_SCROLL_UP: + rel_zoom = zoom_inc; + break; + + case GDK_SCROLL_DOWN: + rel_zoom = 1 / zoom_inc; + break; + + default: + rel_zoom = 0.0; + break; + } + + if (rel_zoom != 0.0) { + Geom::Point const scroll_dt = desktop->point(); + desktop->zoom_relative_keep_point(scroll_dt, rel_zoom); + } + + /* no modifier, pan up--down (left--right on multiwheel mice?) */ + } else { + switch (event->scroll.direction) { + case GDK_SCROLL_UP: + desktop->scroll_world(0, wheel_scroll); + break; + + case GDK_SCROLL_DOWN: + desktop->scroll_world(0, -wheel_scroll); + break; + + case GDK_SCROLL_LEFT: + desktop->scroll_world(wheel_scroll, 0); + break; + + case GDK_SCROLL_RIGHT: + desktop->scroll_world(-wheel_scroll, 0); + break; + +#if GTK_CHECK_VERSION(3,0,0) + case GDK_SCROLL_SMOOTH: + gdk_event_get_scroll_deltas(event, &delta_x, &delta_y); + desktop->scroll_world(delta_x, delta_y); + break; +#endif + } + } + break; + } + default: + break; + } + + return ret; +} + +/** + * Handles item specific events. Gets called from Gdk. + * + * Only reacts to right mouse button at the moment. + * \todo Fixme: do context sensitive popup menu on items. + */ +//gint sp_event_context_private_item_handler(ToolBase *ec, SPItem *item, +// GdkEvent *event) { +// +// return ec->ceventcontext->item_handler(item, event); +//} + +bool ToolBase::item_handler(SPItem* item, GdkEvent* event) { + int ret = FALSE; + + switch (event->type) { + case GDK_BUTTON_PRESS: + if ((event->button.button == 3) && !((event->button.state & GDK_SHIFT_MASK) || (event->button.state & GDK_CONTROL_MASK))) { + sp_event_root_menu_popup(this->desktop, item, event); + ret = TRUE; + } + break; + + default: + break; + } + + return ret; +} + +/** + * Returns true if we're hovering above a knot (needed because we don't want to pre-snap in that case). + */ +bool sp_event_context_knot_mouseover(ToolBase *ec) +{ + if (ec->shape_editor) { + return ec->shape_editor->knot_mouseover(); + } + + return false; +} + +/** + * Creates new ToolBase object and calls its virtual setup() function. + * @todo This is bogus. pref_path should be a private property of the inheriting objects. + */ +//ToolBase * +//sp_event_context_new(GType type, SPDesktop *desktop, gchar const *pref_path, +// unsigned int key) { +// g_return_val_if_fail(g_type_is_a(type, SP_TYPE_EVENT_CONTEXT), NULL); +// g_return_val_if_fail(desktop != NULL, NULL); +// +// ToolBase * const ec = static_cast(g_object_new(type, NULL)); +// +// ec->desktop = desktop; +// ec->_message_context +// = new Inkscape::MessageContext(desktop->messageStack()); +// ec->key = key; +// ec->pref_observer = NULL; +// +// if (pref_path) { +// ec->pref_observer = new ToolPrefObserver(pref_path, ec); +// +// Inkscape::Preferences *prefs = Inkscape::Preferences::get(); +// prefs->addObserver(*(ec->pref_observer)); +// } +// +//// if ((SP_EVENT_CONTEXT_CLASS(G_OBJECT_GET_CLASS(ec)))->setup) +//// (SP_EVENT_CONTEXT_CLASS(G_OBJECT_GET_CLASS(ec)))->setup(ec); +// ec->ceventcontext->setup(); +// +// return ec; +//} + +/** + * Finishes ToolBase. + */ +//void sp_event_context_finish(ToolBase *ec) { +// g_return_if_fail(ec != NULL); +// g_return_if_fail(SP_IS_EVENT_CONTEXT(ec)); +// +// ec->enableSelectionCue(false); +// +//// if (ec->next) { +//// g_warning("Finishing event context with active link\n"); +//// } +// +//// if ((SP_EVENT_CONTEXT_CLASS(G_OBJECT_GET_CLASS(ec)))->finish) +//// (SP_EVENT_CONTEXT_CLASS(G_OBJECT_GET_CLASS(ec)))->finish(ec); +// ec->finish(); +//} + +//-------------------------------member functions + +/** + * Enables/disables the ToolBase's SelCue. + */ +void ToolBase::enableSelectionCue(bool enable) { + if (enable) { + if (!_selcue) { + _selcue = new Inkscape::SelCue(desktop); + } + } else { + delete _selcue; + _selcue = NULL; + } +} + +/** + * Enables/disables the ToolBase's GrDrag. + */ +void ToolBase::enableGrDrag(bool enable) { + if (enable) { + if (!_grdrag) { + _grdrag = new GrDrag(desktop); + } + } else { + if (_grdrag) { + delete _grdrag; + _grdrag = NULL; + } + } +} + +/** + * Delete a selected GrDrag point + */ +bool ToolBase::deleteSelectedDrag(bool just_one) { + + if (_grdrag && _grdrag->selected) { + _grdrag->deleteSelected(just_one); + return TRUE; + } + + return FALSE; +} + +/** + * Calls virtual set() function of ToolBase. + */ +void sp_event_context_read(ToolBase *ec, gchar const *key) { + g_return_if_fail(ec != NULL); + g_return_if_fail(SP_IS_EVENT_CONTEXT(ec)); + g_return_if_fail(key != NULL); + +// if ((SP_EVENT_CONTEXT_CLASS(G_OBJECT_GET_CLASS(ec)))->set) { +// Inkscape::Preferences *prefs = Inkscape::Preferences::get(); +// Inkscape::Preferences::Entry val = prefs->getEntry( +// ec->pref_observer->observed_path + '/' + key); +// (SP_EVENT_CONTEXT_CLASS(G_OBJECT_GET_CLASS(ec)))->set(ec, &val); +// } + + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + Inkscape::Preferences::Entry val = prefs->getEntry(ec->pref_observer->observed_path + '/' + key); + ec->set(val); +} + +/** + * Calls virtual activate() function of ToolBase. + */ +void sp_event_context_activate(ToolBase *ec) { + g_return_if_fail(ec != NULL); + g_return_if_fail(SP_IS_EVENT_CONTEXT(ec)); + + // Make sure no delayed snapping events are carried over after switching contexts + // (this is only an additional safety measure against sloppy coding, because each + // context should take care of this by itself. + sp_event_context_discard_delayed_snap_event(ec); + +// if ((SP_EVENT_CONTEXT_CLASS(G_OBJECT_GET_CLASS(ec)))->activate) +// (SP_EVENT_CONTEXT_CLASS(G_OBJECT_GET_CLASS(ec)))->activate(ec); + ec->activate(); +} + +/** + * Calls virtual deactivate() function of ToolBase. + */ +//void sp_event_context_deactivate(ToolBase *ec) { +// g_return_if_fail(ec != NULL); +// g_return_if_fail(SP_IS_EVENT_CONTEXT(ec)); +// +//// if ((SP_EVENT_CONTEXT_CLASS(G_OBJECT_GET_CLASS(ec)))->deactivate) +//// (SP_EVENT_CONTEXT_CLASS(G_OBJECT_GET_CLASS(ec)))->deactivate(ec); +// ec->deactivate(); +//} + +/** + * Calls virtual root_handler(), the main event handling function. + */ +gint sp_event_context_root_handler(ToolBase * event_context, + GdkEvent * event) +{ + switch (event->type) { + case GDK_MOTION_NOTIFY: + sp_event_context_snap_delay_handler(event_context, NULL, NULL, + (GdkEventMotion *) event, + DelayedSnapEvent::EVENTCONTEXT_ROOT_HANDLER); + break; + case GDK_BUTTON_RELEASE: + if (event_context && event_context->_delayed_snap_event) { + // If we have any pending snapping action, then invoke it now + sp_event_context_snap_watchdog_callback( + event_context->_delayed_snap_event); + } + break; + case GDK_BUTTON_PRESS: + case GDK_2BUTTON_PRESS: + case GDK_3BUTTON_PRESS: + // Snapping will be on hold if we're moving the mouse at high speeds. When starting + // drawing a new shape we really should snap though. + event_context->desktop->namedview->snap_manager.snapprefs.setSnapPostponedGlobally( + false); + break; + default: + break; + } + + return sp_event_context_virtual_root_handler(event_context, event); +} + +gint sp_event_context_virtual_root_handler(ToolBase * event_context, GdkEvent * event) { + gint ret = false; + if (event_context) { // If no event-context is available then do nothing, otherwise Inkscape would crash + // (see the comment in SPDesktop::set_event_context, and bug LP #622350) + //ret = (SP_EVENT_CONTEXT_CLASS(G_OBJECT_GET_CLASS(event_context)))->root_handler(event_context, event); + ret = event_context->root_handler(event); + + set_event_location(event_context->desktop, event); + } + return ret; +} + +/** + * Calls virtual item_handler(), the item event handling function. + */ +gint sp_event_context_item_handler(ToolBase * event_context, + SPItem * item, GdkEvent * event) { + switch (event->type) { + case GDK_MOTION_NOTIFY: + sp_event_context_snap_delay_handler(event_context, (gpointer) item, NULL, (GdkEventMotion *) event, DelayedSnapEvent::EVENTCONTEXT_ITEM_HANDLER); + break; + case GDK_BUTTON_RELEASE: + if (event_context && event_context->_delayed_snap_event) { + // If we have any pending snapping action, then invoke it now + sp_event_context_snap_watchdog_callback(event_context->_delayed_snap_event); + } + break; + case GDK_BUTTON_PRESS: + case GDK_2BUTTON_PRESS: + case GDK_3BUTTON_PRESS: + // Snapping will be on hold if we're moving the mouse at high speeds. When starting + // drawing a new shape we really should snap though. + event_context->desktop->namedview->snap_manager.snapprefs.setSnapPostponedGlobally(false); + break; + default: + break; + } + + return sp_event_context_virtual_item_handler(event_context, item, event); +} + +gint sp_event_context_virtual_item_handler(ToolBase * event_context, SPItem * item, GdkEvent * event) { + gint ret = false; + if (event_context) { // If no event-context is available then do nothing, otherwise Inkscape would crash + // (see the comment in SPDesktop::set_event_context, and bug LP #622350) + //ret = (SP_EVENT_CONTEXT_CLASS(G_OBJECT_GET_CLASS(event_context)))->item_handler(event_context, item, event); + ret = event_context->item_handler(item, event); + + if (!ret) { + ret = sp_event_context_virtual_root_handler(event_context, event); + } else { + set_event_location(event_context->desktop, event); + } + } + + return ret; +} + +/** + * Shows coordinates on status bar. + */ +static void set_event_location(SPDesktop *desktop, GdkEvent *event) { + if (event->type != GDK_MOTION_NOTIFY) { + return; + } + + Geom::Point const button_w(event->button.x, event->button.y); + Geom::Point const button_dt(desktop->w2d(button_w)); + desktop->set_coordinate_status(button_dt); +} + +//------------------------------------------------------------------- +/** + * Create popup menu and tell Gtk to show it. + */ +void sp_event_root_menu_popup(SPDesktop *desktop, SPItem *item, GdkEvent *event) { + + // It seems the param item is the SPItem at the bottom of the z-order + // Using the same function call used on left click in sp_select_context_item_handler() to get top of z-order + // fixme: sp_canvas_arena should set the top z-order object as arena->active + item = sp_event_context_find_item (desktop, + Geom::Point(event->button.x, event->button.y), FALSE, FALSE); + + /* fixme: This is not what I want but works for now (Lauris) */ + if (event->type == GDK_KEY_PRESS) { + item = sp_desktop_selection(desktop)->singleItem(); + } + + ContextMenu* CM = new ContextMenu(desktop, item); + CM->show(); + + switch (event->type) { + case GDK_BUTTON_PRESS: + CM->popup(event->button.button, event->button.time); + break; + case GDK_KEY_PRESS: + CM->popup(0, event->key.time); + break; + default: + break; + } +} + +/** + * Show tool context specific modifier tip. + */ +void sp_event_show_modifier_tip(Inkscape::MessageContext *message_context, + GdkEvent *event, gchar const *ctrl_tip, gchar const *shift_tip, + gchar const *alt_tip) { + guint keyval = get_group0_keyval(&event->key); + + bool ctrl = ctrl_tip && (MOD__CTRL(event) || (keyval == GDK_KEY_Control_L) || (keyval + == GDK_KEY_Control_R)); + bool shift = shift_tip && (MOD__SHIFT(event) || (keyval == GDK_KEY_Shift_L) || (keyval + == GDK_KEY_Shift_R)); + bool alt = alt_tip && (MOD__ALT(event) || (keyval == GDK_KEY_Alt_L) || (keyval + == GDK_KEY_Alt_R) || (keyval == GDK_KEY_Meta_L) || (keyval == GDK_KEY_Meta_R)); + + gchar *tip = g_strdup_printf("%s%s%s%s%s", (ctrl ? ctrl_tip : ""), (ctrl + && (shift || alt) ? "; " : ""), (shift ? shift_tip : ""), ((ctrl + || shift) && alt ? "; " : ""), (alt ? alt_tip : "")); + + if (strlen(tip) > 0) { + message_context->flash(Inkscape::INFORMATION_MESSAGE, tip); + } + + g_free(tip); +} + +/** + * Return the keyval corresponding to the key event in group 0, i.e., + * in the main (English) layout. + * + * Use this instead of simply event->keyval, so that your keyboard shortcuts + * work regardless of layouts (e.g., in Cyrillic). + */ +guint get_group0_keyval(GdkEventKey *event) { + guint keyval = 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*/, &keyval, + NULL, NULL, NULL); + + return keyval; +} + +/** + * Returns item at point p in desktop. + * + * If state includes alt key mask, cyclically selects under; honors + * into_groups. + */ +SPItem *sp_event_context_find_item(SPDesktop *desktop, Geom::Point const &p, + bool select_under, bool into_groups) +{ + SPItem *item = 0; + + if (select_under) { + SPItem *selected_at_point = desktop->getItemFromListAtPointBottom( + desktop->selection->itemList(), p); + item = desktop->getItemAtPoint(p, into_groups, selected_at_point); + if (item == NULL) { // we may have reached bottom, flip over to the top + item = desktop->getItemAtPoint(p, into_groups, NULL); + } + } else { + item = desktop->getItemAtPoint(p, into_groups, NULL); + } + + return item; +} + +/** + * Returns item if it is under point p in desktop, at any depth; otherwise returns NULL. + * + * Honors into_groups. + */ +SPItem * +sp_event_context_over_item(SPDesktop *desktop, SPItem *item, + Geom::Point const &p) { + GSList *temp = NULL; + temp = g_slist_prepend(temp, item); + SPItem *item_at_point = desktop->getItemFromListAtPointBottom(temp, p); + g_slist_free(temp); + + return item_at_point; +} + +ShapeEditor * +sp_event_context_get_shape_editor(ToolBase *ec) { + return ec->shape_editor; +} + +void event_context_print_event_info(GdkEvent *event, bool print_return) { + switch (event->type) { + case GDK_BUTTON_PRESS: + g_print("GDK_BUTTON_PRESS"); + break; + case GDK_2BUTTON_PRESS: + g_print("GDK_2BUTTON_PRESS"); + break; + case GDK_3BUTTON_PRESS: + g_print("GDK_3BUTTON_PRESS"); + break; + + case GDK_MOTION_NOTIFY: + g_print("GDK_MOTION_NOTIFY"); + break; + case GDK_ENTER_NOTIFY: + g_print("GDK_ENTER_NOTIFY"); + break; + + case GDK_LEAVE_NOTIFY: + g_print("GDK_LEAVE_NOTIFY"); + break; + case GDK_BUTTON_RELEASE: + g_print("GDK_BUTTON_RELEASE"); + break; + + case GDK_KEY_PRESS: + g_print("GDK_KEY_PRESS: %d", get_group0_keyval(&event->key)); + break; + case GDK_KEY_RELEASE: + g_print("GDK_KEY_RELEASE: %d", get_group0_keyval(&event->key)); + break; + default: + //g_print ("even type not recognized"); + break; + } + + if (print_return) { + g_print("\n"); + } +} + +/** + * Analyses the current event, calculates the mouse speed, turns snapping off (temporarily) if the + * mouse speed is above a threshold, and stores the current event such that it can be re-triggered when needed + * (re-triggering is controlled by a watchdog timer). + * + * @param ec Pointer to the event context. + * @param dse_item Pointer that store a reference to a canvas or to an item. + * @param dse_item2 Another pointer, storing a reference to a knot or controlpoint. + * @param event Pointer to the motion event. + * @param origin Identifier (enum) specifying where the delay (and the call to this method) were initiated. + */ +void sp_event_context_snap_delay_handler(ToolBase *ec, + gpointer const dse_item, gpointer const dse_item2, GdkEventMotion *event, + DelayedSnapEvent::DelayedSnapEventOrigin origin) +{ + static guint32 prev_time; + static boost::optional prev_pos; + + if (ec->_dse_callback_in_process) { + return; + } + + // Snapping occurs when dragging with the left mouse button down, or when hovering e.g. in the pen tool with left mouse button up + bool const c1 = event->state & GDK_BUTTON2_MASK; // We shouldn't hold back any events when other mouse buttons have been + bool const c2 = event->state & GDK_BUTTON3_MASK; // pressed, e.g. when scrolling with the middle mouse button; if we do then + // Inkscape will get stuck in an unresponsive state + bool const c3 = tools_isactive(ec->desktop, TOOLS_CALLIGRAPHIC); + // The snap delay will repeat the last motion event, which will lead to + // erroneous points in the calligraphy context. And because we don't snap + // in this context, we might just as well disable the snap delay all together + + if (c1 || c2 || c3) { + // Make sure that we don't send any pending snap events to a context if we know in advance + // that we're not going to snap any way (e.g. while scrolling with middle mouse button) + // Any motion event might affect the state of the context, leading to unexpected behavior + sp_event_context_discard_delayed_snap_event(ec); + } else if (ec->desktop + && ec->desktop->namedview->snap_manager.snapprefs.getSnapEnabledGlobally()) { + // Snap when speed drops below e.g. 0.02 px/msec, or when no motion events have occurred for some period. + // i.e. snap when we're at stand still. A speed threshold enforces snapping for tablets, which might never + // be fully at stand still and might keep spitting out motion events. + ec->desktop->namedview->snap_manager.snapprefs.setSnapPostponedGlobally(true); // put snapping on hold + + Geom::Point event_pos(event->x, event->y); + guint32 event_t = gdk_event_get_time((GdkEvent *) event); + + if (prev_pos) { + Geom::Coord dist = Geom::L2(event_pos - *prev_pos); + guint32 delta_t = event_t - prev_time; + gdouble speed = delta_t > 0 ? dist / delta_t : 1000; + //std::cout << "Mouse speed = " << speed << " px/msec " << std::endl; + if (speed > 0.02) { // Jitter threshold, might be needed for tablets + // We're moving fast, so postpone any snapping until the next GDK_MOTION_NOTIFY event. We + // will keep on postponing the snapping as long as the speed is high. + // We must snap at some point in time though, so set a watchdog timer at some time from + // now, just in case there's no future motion event that drops under the speed limit (when + // stopping abruptly) + delete ec->_delayed_snap_event; + ec->_delayed_snap_event = new DelayedSnapEvent(ec, dse_item, dse_item2, + event, origin); // watchdog is reset, i.e. pushed forward in time + // If the watchdog expires before a new motion event is received, we will snap (as explained + // above). This means however that when the timer is too short, we will always snap and that the + // speed threshold is ineffective. In the extreme case the delay is set to zero, and snapping will + // be immediate, as it used to be in the old days ;-). + } else { // Speed is very low, so we're virtually at stand still + // But if we're really standing still, then we should snap now. We could use some low-pass filtering, + // otherwise snapping occurs for each jitter movement. For this filtering we'll leave the watchdog to expire, + // snap, and set a new watchdog again. + if (ec->_delayed_snap_event == NULL) { // no watchdog has been set + // it might have already expired, so we'll set a new one; the snapping frequency will be limited this way + ec->_delayed_snap_event = new DelayedSnapEvent(ec, dse_item, + dse_item2, event, origin); + } // else: watchdog has been set before and we'll wait for it to expire + } + } else { + // This is the first GDK_MOTION_NOTIFY event, so postpone snapping and set the watchdog + g_assert(ec->_delayed_snap_event == NULL); + ec->_delayed_snap_event = new DelayedSnapEvent(ec, dse_item, dse_item2, + event, origin); + } + + prev_pos = event_pos; + prev_time = event_t; + } +} + +/** + * When the snap delay watchdog timer barks, this method will be called and will re-inject the last motion + * event in an appropriate place, with snapping being turned on again. + */ +gboolean sp_event_context_snap_watchdog_callback(gpointer data) { + // Snap NOW! For this the "postponed" flag will be reset and the last motion event will be repeated + DelayedSnapEvent *dse = reinterpret_cast (data); + + if (dse == NULL) { + // This might occur when this method is called directly, i.e. not through the timer + // E.g. on GDK_BUTTON_RELEASE in sp_event_context_root_handler() + return FALSE; + } + + ToolBase *ec = dse->getEventContext(); + if (ec == NULL) { + delete dse; + return false; + } + if (ec->desktop == NULL) { + ec->_delayed_snap_event = NULL; + delete dse; + return false; + } + + ec->_dse_callback_in_process = true; + + SPDesktop *dt = ec->desktop; + dt->namedview->snap_manager.snapprefs.setSnapPostponedGlobally(false); + + // Depending on where the delayed snap event originated from, we will inject it back at it's origin + // The switch below takes care of that and prepares the relevant parameters + switch (dse->getOrigin()) { + case DelayedSnapEvent::EVENTCONTEXT_ROOT_HANDLER: + sp_event_context_virtual_root_handler(ec, dse->getEvent()); + break; + case DelayedSnapEvent::EVENTCONTEXT_ITEM_HANDLER: { + gpointer item = dse->getItem(); + if (item && SP_IS_ITEM(item)) { + sp_event_context_virtual_item_handler(ec, SP_ITEM(item), dse->getEvent()); + } + } + break; + case DelayedSnapEvent::KNOT_HANDLER: { + gpointer knot = dse->getItem2(); + if (knot && SP_IS_KNOT(knot)) { + sp_knot_handler_request_position(dse->getEvent(), SP_KNOT(knot)); + } + } + break; + case DelayedSnapEvent::CONTROL_POINT_HANDLER: { + using Inkscape::UI::ControlPoint; + gpointer pitem2 = dse->getItem2(); + if (!pitem2) + { + ec->_delayed_snap_event = NULL; + delete dse; + return false; + } + ControlPoint *point = reinterpret_cast (pitem2); + if (point) { + if (point->position().isFinite() && (dt == point->_desktop)) { + point->_eventHandler(ec, dse->getEvent()); + } + else { + //workaround: + //[Bug 781893] Crash after moving a Bezier node after Knot path effect? + // --> at some time, some point with X = 0 and Y = nan (not a number) is created ... + // even so, the desktop pointer is invalid and equal to 0xff + g_warning ("encountered non finite point when evaluating snapping callback"); + } + } + } + break; + case DelayedSnapEvent::GUIDE_HANDLER: { + gpointer item = dse->getItem(); + gpointer item2 = dse->getItem2(); + if (item && item2) { + g_assert(SP_IS_CANVAS_ITEM(item)); + g_assert(SP_IS_GUIDE(item2)); + sp_dt_guide_event(SP_CANVAS_ITEM(item), dse->getEvent(), item2); + } + } + break; + case DelayedSnapEvent::GUIDE_HRULER: + case DelayedSnapEvent::GUIDE_VRULER: { + gpointer item = dse->getItem(); + gpointer item2 = dse->getItem2(); + if (item && item2) { + g_assert(GTK_IS_WIDGET(item)); + g_assert(SP_IS_DESKTOP_WIDGET(item2)); + if (dse->getOrigin() == DelayedSnapEvent::GUIDE_HRULER) { + sp_dt_hruler_event(GTK_WIDGET(item), dse->getEvent(), SP_DESKTOP_WIDGET(item2)); + } else { + sp_dt_vruler_event(GTK_WIDGET(item), dse->getEvent(), SP_DESKTOP_WIDGET(item2)); + } + } + } + break; + default: + g_warning("Origin of snap-delay event has not been defined!;"); + break; + } + + ec->_delayed_snap_event = NULL; + delete dse; + + ec->_dse_callback_in_process = false; + + return FALSE; //Kills the timer and stops it from executing this callback over and over again. +} + +void sp_event_context_discard_delayed_snap_event(ToolBase *ec) { + delete ec->_delayed_snap_event; + ec->_delayed_snap_event = NULL; + ec->desktop->namedview->snap_manager.snapprefs.setSnapPostponedGlobally(false); +} + +} +} +} + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: + */ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/ui/tools/tool-base.h b/src/ui/tools/tool-base.h new file mode 100644 index 000000000..5185f89b1 --- /dev/null +++ b/src/ui/tools/tool-base.h @@ -0,0 +1,244 @@ +#ifndef SEEN_SP_EVENT_CONTEXT_H +#define SEEN_SP_EVENT_CONTEXT_H + +/* + * Authors: + * Lauris Kaplinski + * Frank Felfe + * + * Copyright (C) 1999-2002 authors + * Copyright (C) 2001-2002 Ximian, Inc. + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include +#include +#include "knot.h" + +#include "2geom/forward.h" +#include "preferences.h" + +class GrDrag; +class SPDesktop; +class SPItem; +class ShapeEditor; + +namespace Inkscape { + class MessageContext; + class SelCue; + namespace XML { + class Node; + } +} + +#define SP_EVENT_CONTEXT(obj) (dynamic_cast((Inkscape::UI::Tools::ToolBase*)obj)) +#define SP_IS_EVENT_CONTEXT(obj) (dynamic_cast((const Inkscape::UI::Tools::ToolBase*)obj) != NULL) + +namespace Inkscape { +namespace UI { +namespace Tools { + +class ToolBase; + +gboolean sp_event_context_snap_watchdog_callback(gpointer data); +void sp_event_context_discard_delayed_snap_event(ToolBase *ec); + +class DelayedSnapEvent +{ +public: + enum DelayedSnapEventOrigin { + UNDEFINED_HANDLER = 0, + EVENTCONTEXT_ROOT_HANDLER, + EVENTCONTEXT_ITEM_HANDLER, + KNOT_HANDLER, + CONTROL_POINT_HANDLER, + GUIDE_HANDLER, + GUIDE_HRULER, + GUIDE_VRULER + }; + + DelayedSnapEvent(ToolBase *event_context, gpointer const dse_item, gpointer dse_item2, GdkEventMotion const *event, DelayedSnapEvent::DelayedSnapEventOrigin const origin) + : _timer_id(0), _event(NULL), _item(dse_item), _item2(dse_item2), _origin(origin), _event_context(event_context) + { + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + double value = prefs->getDoubleLimited("/options/snapdelay/value", 0, 0, 1000); + _timer_id = g_timeout_add(value, &sp_event_context_snap_watchdog_callback, this); + _event = gdk_event_copy((GdkEvent*) event); + ((GdkEventMotion *)_event)->time = GDK_CURRENT_TIME; + } + + ~DelayedSnapEvent() { + if (_timer_id > 0) g_source_remove(_timer_id); // Kill the watchdog + if (_event != NULL) gdk_event_free(_event); // Remove the copy of the original event + } + + ToolBase* getEventContext() {return _event_context;} + DelayedSnapEventOrigin getOrigin() {return _origin;} + GdkEvent* getEvent() {return _event;} + gpointer getItem() {return _item;} + gpointer getItem2() {return _item2;} + +private: + guint _timer_id; + GdkEvent* _event; + gpointer _item; + gpointer _item2; + DelayedSnapEventOrigin _origin; + ToolBase* _event_context; +}; + +void sp_event_context_snap_delay_handler(ToolBase *ec, gpointer const dse_item, gpointer const dse_item2, GdkEventMotion *event, DelayedSnapEvent::DelayedSnapEventOrigin origin); + + +/** + * Base class for Event processors. + * + * This is per desktop object, which (its derivatives) implements + * different actions bound to mouse events. + * + * ToolBase is an abstract base class of all tools. As the name + * indicates, event context implementations process UI events (mouse + * movements and keypresses) and take actions (like creating or modifying + * objects). There is one event context implementation for each tool, + * plus few abstract base classes. Writing a new tool involves + * subclassing ToolBase. + */ +class ToolBase { +public: + void enableSelectionCue (bool enable=true); + void enableGrDrag (bool enable=true); + bool deleteSelectedDrag(bool just_one); + + ToolBase(); + virtual ~ToolBase(); + + SPDesktop *desktop; + Inkscape::Preferences::Observer *pref_observer; + gchar const *const *cursor_shape; + gint hot_x, hot_y; ///< indicates the cursor's hot spot + GdkCursor *cursor; + + gint xp, yp; ///< where drag started + gint tolerance; + bool within_tolerance; ///< are we still within tolerance of origin + + SPItem *item_to_select; ///< the item where mouse_press occurred, to + ///< be selected if this is a click not drag + + Inkscape::MessageContext *defaultMessageContext() { + return message_context; + } + + Inkscape::MessageContext *message_context; + + Inkscape::SelCue *_selcue; + + GrDrag *_grdrag; + GrDrag *get_drag () {return _grdrag;} + + ShapeEditor* shape_editor; + + bool space_panning; + + DelayedSnapEvent *_delayed_snap_event; + bool _dse_callback_in_process; + + virtual void setup(); + virtual void finish(); + + // Is called by our pref_observer if a preference has been changed. + virtual void set(const Inkscape::Preferences::Entry& val); + + virtual void activate(); + virtual void deactivate(); + + virtual bool root_handler(GdkEvent* event); + virtual bool item_handler(SPItem* item, GdkEvent* event); + + virtual const std::string& getPrefsPath() = 0; + + /** + * An observer that relays pref changes to the derived classes. + */ + class ToolPrefObserver: public Inkscape::Preferences::Observer { + public: + ToolPrefObserver(Glib::ustring const &path, ToolBase *ec) : + Inkscape::Preferences::Observer(path), ec(ec) { + } + + virtual void notify(Inkscape::Preferences::Entry const &val) { + ec->set(val); + } + + private: + ToolBase * const ec; + }; + +//protected: + void sp_event_context_update_cursor(); + +private: + ToolBase(const ToolBase&); + ToolBase& operator=(const ToolBase&); +}; + +#define SP_EVENT_CONTEXT_DESKTOP(e) (SP_EVENT_CONTEXT(e)->desktop) +#define SP_EVENT_CONTEXT_DOCUMENT(e) ((SP_EVENT_CONTEXT_DESKTOP(e))->doc()) + +#define SP_EVENT_CONTEXT_STATIC 0 + +//ToolBase *sp_event_context_new(GType type, SPDesktop *desktop, gchar const *pref_path, unsigned key); +//void sp_event_context_finish(ToolBase *ec); +void sp_event_context_read(ToolBase *ec, gchar const *key); +void sp_event_context_activate(ToolBase *ec); +//void sp_event_context_deactivate(ToolBase *ec); + +gint sp_event_context_root_handler(ToolBase *ec, GdkEvent *event); +gint sp_event_context_virtual_root_handler(ToolBase *ec, GdkEvent *event); +gint sp_event_context_item_handler(ToolBase *ec, SPItem *item, GdkEvent *event); +gint sp_event_context_virtual_item_handler(ToolBase *ec, SPItem *item, GdkEvent *event); + +void sp_event_root_menu_popup(SPDesktop *desktop, SPItem *item, GdkEvent *event); + +gint gobble_key_events(guint keyval, gint mask); +gint gobble_motion_events(gint mask); + +//void sp_event_context_update_cursor(ToolBase *ec); + +void sp_event_show_modifier_tip(Inkscape::MessageContext *message_context, GdkEvent *event, + gchar const *ctrl_tip, gchar const *shift_tip, gchar const *alt_tip); + +guint get_group0_keyval(GdkEventKey *event); + +SPItem *sp_event_context_find_item (SPDesktop *desktop, Geom::Point const &p, bool select_under, bool into_groups); +SPItem *sp_event_context_over_item (SPDesktop *desktop, SPItem *item, Geom::Point const &p); + +void sp_toggle_dropper(SPDesktop *dt); + +//ShapeEditor *sp_event_context_get_shape_editor (ToolBase *ec); +bool sp_event_context_knot_mouseover(ToolBase *ec); + +//void ec_shape_event_attr_changed(Inkscape::XML::Node *shape_repr, +// gchar const *name, gchar const *old_value, gchar const *new_value, +// bool const is_interactive, gpointer const data); +// +//void event_context_print_event_info(GdkEvent *event, bool print_return = true); + +} +} +} + +#endif // SEEN_SP_EVENT_CONTEXT_H + + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/ui/tools/tweak-tool.cpp b/src/ui/tools/tweak-tool.cpp new file mode 100644 index 000000000..0791eff5a --- /dev/null +++ b/src/ui/tools/tweak-tool.cpp @@ -0,0 +1,1524 @@ +/* + * tweaking paths without node editing + * + * Authors: + * bulia byak + * Jon A. Cruz + * Abhishek Sharma + * + * Copyright (C) 2007 authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include "config.h" + +#include +#include +#include + +#include + +#include "svg/svg.h" + +#include +#include "macros.h" +#include "document.h" +#include "document-undo.h" +#include "selection.h" +#include "desktop.h" +#include "desktop-events.h" +#include "desktop-handles.h" +#include "desktop-style.h" +#include "message-context.h" +#include "pixmaps/cursor-tweak-move.xpm" +#include "pixmaps/cursor-tweak-move-in.xpm" +#include "pixmaps/cursor-tweak-move-out.xpm" +#include "pixmaps/cursor-tweak-move-jitter.xpm" +#include "pixmaps/cursor-tweak-scale-up.xpm" +#include "pixmaps/cursor-tweak-scale-down.xpm" +#include "pixmaps/cursor-tweak-rotate-clockwise.xpm" +#include "pixmaps/cursor-tweak-rotate-counterclockwise.xpm" +#include "pixmaps/cursor-tweak-more.xpm" +#include "pixmaps/cursor-tweak-less.xpm" +#include "pixmaps/cursor-thin.xpm" +#include "pixmaps/cursor-thicken.xpm" +#include "pixmaps/cursor-attract.xpm" +#include "pixmaps/cursor-repel.xpm" +#include "pixmaps/cursor-push.xpm" +#include "pixmaps/cursor-roughen.xpm" +#include "pixmaps/cursor-color.xpm" +#include +#include "xml/repr.h" +#include "context-fns.h" +#include "sp-item.h" +#include "inkscape.h" +#include "color.h" +#include "svg/svg-color.h" +#include "splivarot.h" +#include "sp-item-group.h" +#include "sp-shape.h" +#include "sp-path.h" +#include "path-chemistry.h" +#include "sp-gradient.h" +#include "sp-stop.h" +#include "sp-gradient-reference.h" +#include "sp-linear-gradient.h" +#include "sp-radial-gradient.h" +#include "gradient-chemistry.h" +#include "sp-text.h" +#include "sp-flowtext.h" +#include "display/sp-canvas.h" +#include "display/canvas-bpath.h" +#include "display/canvas-arena.h" +#include "display/curve.h" +#include "livarot/Shape.h" +#include <2geom/transforms.h> +#include <2geom/circle.h> +#include "preferences.h" +#include "style.h" +#include "box3d.h" +#include "sp-item-transform.h" +#include "filter-chemistry.h" +#include "filters/gaussian-blur.h" +#include "verbs.h" + +#include "ui/tools/tweak-tool.h" + +using Inkscape::DocumentUndo; + +#define DDC_RED_RGBA 0xff0000ff + +#define DYNA_MIN_WIDTH 1.0e-6 + +#include "tool-factory.h" + +namespace Inkscape { +namespace UI { +namespace Tools { + +namespace { + ToolBase* createTweakContext() { + return new TweakTool(); + } + + bool tweakContextRegistered = ToolFactory::instance().registerObject("/tools/tweak", createTweakContext); +} + +const std::string& TweakTool::getPrefsPath() { + return TweakTool::prefsPath; +} + +const std::string TweakTool::prefsPath = "/tools/tweak"; + +TweakTool::TweakTool() : ToolBase() { + this->mode = 0; + this->dilate_area = 0; + this->usetilt = 0; + this->usepressure = 0; + this->is_drawing = false; + this->fidelity = 0; + + this->cursor_shape = cursor_push_xpm; + this->hot_x = 4; + this->hot_y = 4; + + /* attributes */ + this->dragging = FALSE; + + this->width = 0.2; + this->force = 0.2; + this->pressure = TC_DEFAULT_PRESSURE; + + this->is_dilating = false; + this->has_dilated = false; + + this->do_h = true; + this->do_s = true; + this->do_l = true; + this->do_o = false; +} + +TweakTool::~TweakTool() { + this->enableGrDrag(false); + + this->style_set_connection.disconnect(); + + if (this->dilate_area) { + sp_canvas_item_destroy(this->dilate_area); + this->dilate_area = NULL; + } +} + +static bool is_transform_mode (gint mode) +{ + return (mode == TWEAK_MODE_MOVE || + mode == TWEAK_MODE_MOVE_IN_OUT || + mode == TWEAK_MODE_MOVE_JITTER || + mode == TWEAK_MODE_SCALE || + mode == TWEAK_MODE_ROTATE || + mode == TWEAK_MODE_MORELESS); +} + +static bool is_color_mode (gint mode) +{ + return (mode == TWEAK_MODE_COLORPAINT || mode == TWEAK_MODE_COLORJITTER || mode == TWEAK_MODE_BLUR); +} + +void TweakTool::update_cursor (bool with_shift) { + guint num = 0; + gchar *sel_message = NULL; + + if (!desktop->selection->isEmpty()) { + num = g_slist_length(const_cast(desktop->selection->itemList())); + sel_message = g_strdup_printf(ngettext("%i object selected","%i objects selected",num), num); + } else { + sel_message = g_strdup_printf("%s", _("Nothing selected")); + } + + switch (this->mode) { + case TWEAK_MODE_MOVE: + this->message_context->setF(Inkscape::NORMAL_MESSAGE, _("%s. Drag to move."), sel_message); + this->cursor_shape = cursor_tweak_move_xpm; + break; + case TWEAK_MODE_MOVE_IN_OUT: + this->message_context->setF(Inkscape::NORMAL_MESSAGE, _("%s. Drag or click to move in; with Shift to move out."), sel_message); + if (with_shift) { + this->cursor_shape = cursor_tweak_move_out_xpm; + } else { + this->cursor_shape = cursor_tweak_move_in_xpm; + } + break; + case TWEAK_MODE_MOVE_JITTER: + this->message_context->setF(Inkscape::NORMAL_MESSAGE, _("%s. Drag or click to move randomly."), sel_message); + this->cursor_shape = cursor_tweak_move_jitter_xpm; + break; + case TWEAK_MODE_SCALE: + this->message_context->setF(Inkscape::NORMAL_MESSAGE, _("%s. Drag or click to scale down; with Shift to scale up."), sel_message); + if (with_shift) { + this->cursor_shape = cursor_tweak_scale_up_xpm; + } else { + this->cursor_shape = cursor_tweak_scale_down_xpm; + } + break; + case TWEAK_MODE_ROTATE: + this->message_context->setF(Inkscape::NORMAL_MESSAGE, _("%s. Drag or click to rotate clockwise; with Shift, counterclockwise."), sel_message); + if (with_shift) { + this->cursor_shape = cursor_tweak_rotate_counterclockwise_xpm; + } else { + this->cursor_shape = cursor_tweak_rotate_clockwise_xpm; + } + break; + case TWEAK_MODE_MORELESS: + this->message_context->setF(Inkscape::NORMAL_MESSAGE, _("%s. Drag or click to duplicate; with Shift, delete."), sel_message); + if (with_shift) { + this->cursor_shape = cursor_tweak_less_xpm; + } else { + this->cursor_shape = cursor_tweak_more_xpm; + } + break; + case TWEAK_MODE_PUSH: + this->message_context->setF(Inkscape::NORMAL_MESSAGE, _("%s. Drag to push paths."), sel_message); + this->cursor_shape = cursor_push_xpm; + break; + case TWEAK_MODE_SHRINK_GROW: + this->message_context->setF(Inkscape::NORMAL_MESSAGE, _("%s. Drag or click to inset paths; with Shift to outset."), sel_message); + if (with_shift) { + this->cursor_shape = cursor_thicken_xpm; + } else { + this->cursor_shape = cursor_thin_xpm; + } + break; + case TWEAK_MODE_ATTRACT_REPEL: + this->message_context->setF(Inkscape::NORMAL_MESSAGE, _("%s. Drag or click to attract paths; with Shift to repel."), sel_message); + if (with_shift) { + this->cursor_shape = cursor_repel_xpm; + } else { + this->cursor_shape = cursor_attract_xpm; + } + break; + case TWEAK_MODE_ROUGHEN: + this->message_context->setF(Inkscape::NORMAL_MESSAGE, _("%s. Drag or click to roughen paths."), sel_message); + this->cursor_shape = cursor_roughen_xpm; + break; + case TWEAK_MODE_COLORPAINT: + this->message_context->setF(Inkscape::NORMAL_MESSAGE, _("%s. Drag or click to paint objects with color."), sel_message); + this->cursor_shape = cursor_color_xpm; + break; + case TWEAK_MODE_COLORJITTER: + this->message_context->setF(Inkscape::NORMAL_MESSAGE, _("%s. Drag or click to randomize colors."), sel_message); + this->cursor_shape = cursor_color_xpm; + break; + case TWEAK_MODE_BLUR: + this->message_context->setF(Inkscape::NORMAL_MESSAGE, _("%s. Drag or click to increase blur; with Shift to decrease."), sel_message); + this->cursor_shape = cursor_color_xpm; + break; + } + + this->sp_event_context_update_cursor(); + g_free(sel_message); +} + +bool TweakTool::set_style(const SPCSSAttr* css) { + if (this->mode == TWEAK_MODE_COLORPAINT) { // intercept color setting only in this mode + // we cannot store properties with uris + css = sp_css_attr_unset_uris(const_cast(css)); + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + prefs->setStyle("/tools/tweak/style", const_cast(css)); + return true; + } + + return false; +} + +void TweakTool::setup() { + ToolBase::setup(); + + { + /* TODO: have a look at sp_dyna_draw_context_setup where the same is done.. generalize? at least make it an arcto! */ + Geom::PathVector path; + Geom::Circle(0, 0, 1).getPath(path); + + SPCurve *c = new SPCurve(path); + + this->dilate_area = sp_canvas_bpath_new(sp_desktop_controls(this->desktop), c); + c->unref(); + sp_canvas_bpath_set_fill(SP_CANVAS_BPATH(this->dilate_area), 0x00000000,(SPWindRule)0); + sp_canvas_bpath_set_stroke(SP_CANVAS_BPATH(this->dilate_area), 0xff9900ff, 1.0, SP_STROKE_LINEJOIN_MITER, SP_STROKE_LINECAP_BUTT); + sp_canvas_item_hide(this->dilate_area); + } + + this->is_drawing = false; + + sp_event_context_read(this, "width"); + sp_event_context_read(this, "mode"); + sp_event_context_read(this, "fidelity"); + sp_event_context_read(this, "force"); + sp_event_context_read(this, "usepressure"); + sp_event_context_read(this, "doh"); + sp_event_context_read(this, "dol"); + sp_event_context_read(this, "dos"); + sp_event_context_read(this, "doo"); + + this->style_set_connection = this->desktop->connectSetStyle( // catch style-setting signal in this tool + //sigc::bind(sigc::ptr_fun(&sp_tweak_context_style_set), this) + sigc::mem_fun(this, &TweakTool::set_style) + ); + + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + if (prefs->getBool("/tools/tweak/selcue")) { + this->enableSelectionCue(); + } + if (prefs->getBool("/tools/tweak/gradientdrag")) { + this->enableGrDrag(); + } +} + +void TweakTool::set(const Inkscape::Preferences::Entry& val) { + Glib::ustring path = val.getEntryName(); + + if (path == "width") { + this->width = CLAMP(val.getDouble(0.1), -1000.0, 1000.0); + } else if (path == "mode") { + this->mode = val.getInt(); + this->update_cursor(false); + } else if (path == "fidelity") { + this->fidelity = CLAMP(val.getDouble(), 0.0, 1.0); + } else if (path == "force") { + this->force = CLAMP(val.getDouble(1.0), 0, 1.0); + } else if (path == "usepressure") { + this->usepressure = val.getBool(); + } else if (path == "doh") { + this->do_h = val.getBool(); + } else if (path == "dos") { + this->do_s = val.getBool(); + } else if (path == "dol") { + this->do_l = val.getBool(); + } else if (path == "doo") { + this->do_o = val.getBool(); + } +} + +static void +sp_tweak_extinput(TweakTool *tc, GdkEvent *event) +{ + if (gdk_event_get_axis (event, GDK_AXIS_PRESSURE, &tc->pressure)) { + tc->pressure = CLAMP (tc->pressure, TC_MIN_PRESSURE, TC_MAX_PRESSURE); + } else { + tc->pressure = TC_DEFAULT_PRESSURE; + } +} + +static double +get_dilate_radius (TweakTool *tc) +{ + // 10 times the pen width: + return 500 * tc->width/SP_EVENT_CONTEXT(tc)->desktop->current_zoom(); +} + +static double +get_path_force (TweakTool *tc) +{ + double force = 8 * (tc->usepressure? tc->pressure : TC_DEFAULT_PRESSURE) + /sqrt(SP_EVENT_CONTEXT(tc)->desktop->current_zoom()); + if (force > 3) { + force += 4 * (force - 3); + } + return force * tc->force; +} + +static double +get_move_force (TweakTool *tc) +{ + double force = (tc->usepressure? tc->pressure : TC_DEFAULT_PRESSURE); + return force * tc->force; +} + +static bool +sp_tweak_dilate_recursive (Inkscape::Selection *selection, SPItem *item, Geom::Point p, Geom::Point vector, gint mode, double radius, double force, double fidelity, bool reverse) +{ + bool did = false; + + if (SP_IS_BOX3D(item) && !is_transform_mode(mode) && !is_color_mode(mode)) { + // convert 3D boxes to ordinary groups before tweaking their shapes + item = box3d_convert_to_group(SP_BOX3D(item)); + selection->add(item); + } + + if (SP_IS_TEXT(item) || SP_IS_FLOWTEXT(item)) { + GSList *items = g_slist_prepend (NULL, item); + GSList *selected = NULL; + GSList *to_select = NULL; + SPDocument *doc = item->document; + sp_item_list_to_curves (items, &selected, &to_select); + g_slist_free (items); + SPObject* newObj = doc->getObjectByRepr(static_cast(to_select->data)); + g_slist_free (to_select); + item = SP_ITEM(newObj); + selection->add(item); + } + + if (SP_IS_GROUP(item) && !SP_IS_BOX3D(item)) { + GSList *children = NULL; + for (SPObject *child = item->firstChild() ; child; child = child->getNext() ) { + if (SP_IS_ITEM(child)) { + children = g_slist_prepend(children, child); + } + } + + for (GSList *i = children; i; i = i->next) { + SPItem *child = SP_ITEM(i->data); + if (sp_tweak_dilate_recursive (selection, SP_ITEM(child), p, vector, mode, radius, force, fidelity, reverse)) + did = true; + } + + g_slist_free(children); + + } else { + if (mode == TWEAK_MODE_MOVE) { + + Geom::OptRect a = item->documentVisualBounds(); + if (a) { + double x = Geom::L2(a->midpoint() - p)/radius; + if (a->contains(p)) x = 0; + if (x < 1) { + Geom::Point move = force * 0.5 * (cos(M_PI * x) + 1) * vector; + sp_item_move_rel(item, Geom::Translate(move[Geom::X], -move[Geom::Y])); + did = true; + } + } + + } else if (mode == TWEAK_MODE_MOVE_IN_OUT) { + + Geom::OptRect a = item->documentVisualBounds(); + if (a) { + double x = Geom::L2(a->midpoint() - p)/radius; + if (a->contains(p)) x = 0; + if (x < 1) { + Geom::Point move = force * 0.5 * (cos(M_PI * x) + 1) * + (reverse? (a->midpoint() - p) : (p - a->midpoint())); + sp_item_move_rel(item, Geom::Translate(move[Geom::X], -move[Geom::Y])); + did = true; + } + } + + } else if (mode == TWEAK_MODE_MOVE_JITTER) { + + Geom::OptRect a = item->documentVisualBounds(); + if (a) { + double dp = g_random_double_range(0, M_PI*2); + double dr = g_random_double_range(0, radius); + double x = Geom::L2(a->midpoint() - p)/radius; + if (a->contains(p)) x = 0; + if (x < 1) { + Geom::Point move = force * 0.5 * (cos(M_PI * x) + 1) * Geom::Point(cos(dp)*dr, sin(dp)*dr); + sp_item_move_rel(item, Geom::Translate(move[Geom::X], -move[Geom::Y])); + did = true; + } + } + + } else if (mode == TWEAK_MODE_SCALE) { + + Geom::OptRect a = item->documentVisualBounds(); + if (a) { + double x = Geom::L2(a->midpoint() - p)/radius; + if (a->contains(p)) x = 0; + if (x < 1) { + double scale = 1 + (reverse? force : -force) * 0.05 * (cos(M_PI * x) + 1); + sp_item_scale_rel(item, Geom::Scale(scale, scale)); + did = true; + } + } + + } else if (mode == TWEAK_MODE_ROTATE) { + + Geom::OptRect a = item->documentVisualBounds(); + if (a) { + double x = Geom::L2(a->midpoint() - p)/radius; + if (a->contains(p)) x = 0; + if (x < 1) { + double angle = (reverse? force : -force) * 0.05 * (cos(M_PI * x) + 1) * M_PI; + sp_item_rotate_rel(item, Geom::Rotate(angle)); + did = true; + } + } + + } else if (mode == TWEAK_MODE_MORELESS) { + + Geom::OptRect a = item->documentVisualBounds(); + if (a) { + double x = Geom::L2(a->midpoint() - p)/radius; + if (a->contains(p)) x = 0; + if (x < 1) { + double prob = force * 0.5 * (cos(M_PI * x) + 1); + double chance = g_random_double_range(0, 1); + if (chance <= prob) { + if (reverse) { // delete + sp_object_ref(item, NULL); + item->deleteObject(true, true); + sp_object_unref(item, NULL); + } else { // duplicate + SPDocument *doc = item->document; + Inkscape::XML::Document* xml_doc = doc->getReprDoc(); + Inkscape::XML::Node *old_repr = item->getRepr(); + SPObject *old_obj = doc->getObjectByRepr(old_repr); + Inkscape::XML::Node *parent = old_repr->parent(); + Inkscape::XML::Node *copy = old_repr->duplicate(xml_doc); + parent->appendChild(copy); + SPObject *new_obj = doc->getObjectByRepr(copy); + if (selection->includes(old_obj)) { + selection->add(new_obj); + } + Inkscape::GC::release(copy); + } + did = true; + } + } + } + + } else if (SP_IS_PATH(item) || SP_IS_SHAPE(item)) { + + Inkscape::XML::Node *newrepr = NULL; + gint pos = 0; + Inkscape::XML::Node *parent = NULL; + char const *id = NULL; + if (!SP_IS_PATH(item)) { + newrepr = sp_selected_item_to_curved_repr(item, 0); + if (!newrepr) { + return false; + } + + // remember the position of the item + pos = item->getRepr()->position(); + // remember parent + parent = item->getRepr()->parent(); + // remember id + id = item->getRepr()->attribute("id"); + } + + // skip those paths whose bboxes are entirely out of reach with our radius + Geom::OptRect bbox = item->documentVisualBounds(); + if (bbox) { + bbox->expandBy(radius); + if (!bbox->contains(p)) { + return false; + } + } + + Path *orig = Path_for_item(item, false); + if (orig == NULL) { + return false; + } + + Path *res = new Path; + res->SetBackData(false); + + Shape *theShape = new Shape; + Shape *theRes = new Shape; + Geom::Affine i2doc(item->i2doc_affine()); + + orig->ConvertWithBackData((0.08 - (0.07 * fidelity)) / i2doc.descrim()); // default 0.059 + orig->Fill(theShape, 0); + + SPCSSAttr *css = sp_repr_css_attr(item->getRepr(), "style"); + gchar const *val = sp_repr_css_property(css, "fill-rule", NULL); + if (val && strcmp(val, "nonzero") == 0) { + theRes->ConvertToShape(theShape, fill_nonZero); + } else if (val && strcmp(val, "evenodd") == 0) { + theRes->ConvertToShape(theShape, fill_oddEven); + } else { + theRes->ConvertToShape(theShape, fill_nonZero); + } + + if (Geom::L2(vector) != 0) { + vector = 1/Geom::L2(vector) * vector; + } + + bool did_this = false; + if (mode == TWEAK_MODE_SHRINK_GROW) { + if (theShape->MakeTweak(tweak_mode_grow, theRes, + reverse? force : -force, + join_straight, 4.0, + true, p, Geom::Point(0,0), radius, &i2doc) == 0) // 0 means the shape was actually changed + did_this = true; + } else if (mode == TWEAK_MODE_ATTRACT_REPEL) { + if (theShape->MakeTweak(tweak_mode_repel, theRes, + reverse? force : -force, + join_straight, 4.0, + true, p, Geom::Point(0,0), radius, &i2doc) == 0) + did_this = true; + } else if (mode == TWEAK_MODE_PUSH) { + if (theShape->MakeTweak(tweak_mode_push, theRes, + 1.0, + join_straight, 4.0, + true, p, force*2*vector, radius, &i2doc) == 0) + did_this = true; + } else if (mode == TWEAK_MODE_ROUGHEN) { + if (theShape->MakeTweak(tweak_mode_roughen, theRes, + force, + join_straight, 4.0, + true, p, Geom::Point(0,0), radius, &i2doc) == 0) + did_this = true; + } + + // the rest only makes sense if we actually changed the path + if (did_this) { + theRes->ConvertToShape(theShape, fill_positive); + + res->Reset(); + theRes->ConvertToForme(res); + + double th_max = (0.6 - 0.59*sqrt(fidelity)) / i2doc.descrim(); + double threshold = MAX(th_max, th_max*force); + res->ConvertEvenLines(threshold); + res->Simplify(threshold / (selection->desktop()->current_zoom())); + + if (newrepr) { // converting to path, need to replace the repr + bool is_selected = selection->includes(item); + if (is_selected) { + selection->remove(item); + } + + // It's going to resurrect, so we delete without notifying listeners. + item->deleteObject(false); + + // restore id + newrepr->setAttribute("id", id); + // add the new repr to the parent + parent->appendChild(newrepr); + // move to the saved position + newrepr->setPosition(pos > 0 ? pos : 0); + + if (is_selected) + selection->add(newrepr); + } + + if (res->descr_cmd.size() > 1) { + gchar *str = res->svg_dump_path(); + if (newrepr) { + newrepr->setAttribute("d", str); + } else { + if (SP_IS_LPE_ITEM(item) && SP_LPE_ITEM(item)->hasPathEffectRecursive()) { + item->getRepr()->setAttribute("inkscape:original-d", str); + } else { + item->getRepr()->setAttribute("d", str); + } + } + g_free(str); + } else { + // TODO: if there's 0 or 1 node left, delete this path altogether + } + + if (newrepr) { + Inkscape::GC::release(newrepr); + newrepr = NULL; + } + } + + delete theShape; + delete theRes; + delete orig; + delete res; + + if (did_this) { + did = true; + } + } + + } + + return did; +} + +static void +tweak_colorpaint (float *color, guint32 goal, double force, bool do_h, bool do_s, bool do_l) +{ + float rgb_g[3]; + + if (!do_h || !do_s || !do_l) { + float hsl_g[3]; + sp_color_rgb_to_hsl_floatv (hsl_g, SP_RGBA32_R_F(goal), SP_RGBA32_G_F(goal), SP_RGBA32_B_F(goal)); + float hsl_c[3]; + sp_color_rgb_to_hsl_floatv (hsl_c, color[0], color[1], color[2]); + if (!do_h) { + hsl_g[0] = hsl_c[0]; + } + if (!do_s) { + hsl_g[1] = hsl_c[1]; + } + if (!do_l) { + hsl_g[2] = hsl_c[2]; + } + sp_color_hsl_to_rgb_floatv (rgb_g, hsl_g[0], hsl_g[1], hsl_g[2]); + } else { + rgb_g[0] = SP_RGBA32_R_F(goal); + rgb_g[1] = SP_RGBA32_G_F(goal); + rgb_g[2] = SP_RGBA32_B_F(goal); + } + + for (int i = 0; i < 3; i++) { + double d = rgb_g[i] - color[i]; + color[i] += d * force; + } +} + +static void +tweak_colorjitter (float *color, double force, bool do_h, bool do_s, bool do_l) +{ + float hsl_c[3]; + sp_color_rgb_to_hsl_floatv (hsl_c, color[0], color[1], color[2]); + + if (do_h) { + hsl_c[0] += g_random_double_range(-0.5, 0.5) * force; + if (hsl_c[0] > 1) { + hsl_c[0] -= 1; + } + if (hsl_c[0] < 0) { + hsl_c[0] += 1; + } + } + if (do_s) { + hsl_c[1] += g_random_double_range(-hsl_c[1], 1 - hsl_c[1]) * force; + } + if (do_l) { + hsl_c[2] += g_random_double_range(-hsl_c[2], 1 - hsl_c[2]) * force; + } + + sp_color_hsl_to_rgb_floatv (color, hsl_c[0], hsl_c[1], hsl_c[2]); +} + +static void +tweak_color (guint mode, float *color, guint32 goal, double force, bool do_h, bool do_s, bool do_l) +{ + if (mode == TWEAK_MODE_COLORPAINT) { + tweak_colorpaint (color, goal, force, do_h, do_s, do_l); + } else if (mode == TWEAK_MODE_COLORJITTER) { + tweak_colorjitter (color, force, do_h, do_s, do_l); + } +} + +static void +tweak_opacity (guint mode, SPIScale24 *style_opacity, double opacity_goal, double force) +{ + double opacity = SP_SCALE24_TO_FLOAT (style_opacity->value); + + if (mode == TWEAK_MODE_COLORPAINT) { + double d = opacity_goal - opacity; + opacity += d * force; + } else if (mode == TWEAK_MODE_COLORJITTER) { + opacity += g_random_double_range(-opacity, 1 - opacity) * force; + } + + style_opacity->value = SP_SCALE24_FROM_FLOAT(opacity); +} + + +static double +tweak_profile (double dist, double radius) +{ + if (radius == 0) { + return 0; + } + double x = dist / radius; + double alpha = 1; + if (x >= 1) { + return 0; + } else if (x <= 0) { + return 1; + } else { + return (0.5 * cos (M_PI * (pow(x, alpha))) + 0.5); + } +} + +static void tweak_colors_in_gradient(SPItem *item, Inkscape::PaintTarget fill_or_stroke, + guint32 const rgb_goal, Geom::Point p_w, double radius, double force, guint mode, + bool do_h, bool do_s, bool do_l, bool /*do_o*/) +{ + SPGradient *gradient = getGradient(item, fill_or_stroke); + + if (!gradient || !SP_IS_GRADIENT(gradient)) { + return; + } + + Geom::Affine i2d (item->i2doc_affine ()); + Geom::Point p = p_w * i2d.inverse(); + p *= (gradient->gradientTransform).inverse(); + // now p is in gradient's original coordinates + + double pos = 0; + double r = 0; + if (SP_IS_LINEARGRADIENT(gradient)) { + SPLinearGradient *lg = SP_LINEARGRADIENT(gradient); + + Geom::Point p1(lg->x1.computed, lg->y1.computed); + Geom::Point p2(lg->x2.computed, lg->y2.computed); + Geom::Point pdiff(p2 - p1); + double vl = Geom::L2(pdiff); + + // This is the matrix which moves and rotates the gradient line + // so it's oriented along the X axis: + Geom::Affine norm = Geom::Affine(Geom::Translate(-p1)) * Geom::Affine(Geom::Rotate(-atan2(pdiff[Geom::Y], pdiff[Geom::X]))); + + // Transform the mouse point by it to find out its projection onto the gradient line: + Geom::Point pnorm = p * norm; + + // Scale its X coordinate to match the length of the gradient line: + pos = pnorm[Geom::X] / vl; + // Calculate radius in lenfth-of-gradient-line units + r = radius / vl; + + } else if (SP_IS_RADIALGRADIENT(gradient)) { + SPRadialGradient *rg = SP_RADIALGRADIENT(gradient); + Geom::Point c (rg->cx.computed, rg->cy.computed); + pos = Geom::L2(p - c) / rg->r.computed; + r = radius / rg->r.computed; + } + + // Normalize pos to 0..1, taking into accound gradient spread: + double pos_e = pos; + if (gradient->getSpread() == SP_GRADIENT_SPREAD_PAD) { + if (pos > 1) { + pos_e = 1; + } + if (pos < 0) { + pos_e = 0; + } + } else if (gradient->getSpread() == SP_GRADIENT_SPREAD_REPEAT) { + if (pos > 1 || pos < 0) { + pos_e = pos - floor(pos); + } + } else if (gradient->getSpread() == SP_GRADIENT_SPREAD_REFLECT) { + if (pos > 1 || pos < 0) { + bool odd = ((int)(floor(pos)) % 2 == 1); + pos_e = pos - floor(pos); + if (odd) { + pos_e = 1 - pos_e; + } + } + } + + SPGradient *vector = sp_gradient_get_forked_vector_if_necessary(gradient, false); + + double offset_l = 0; + double offset_h = 0; + SPObject *child_prev = NULL; + for (SPObject *child = vector->firstChild(); child; child = child->getNext()) { + if (!SP_IS_STOP(child)) { + continue; + } + SPStop *stop = SP_STOP (child); + + offset_h = stop->offset; + + if (child_prev) { + + if (offset_h - offset_l > r && pos_e >= offset_l && pos_e <= offset_h) { + // the summit falls in this interstop, and the radius is small, + // so it only affects the ends of this interstop; + // distribute the force between the two endstops so that they + // get all the painting even if they are not touched by the brush + tweak_color (mode, stop->specified_color.v.c, rgb_goal, + force * (pos_e - offset_l) / (offset_h - offset_l), + do_h, do_s, do_l); + tweak_color (mode, SP_STOP(child_prev)->specified_color.v.c, rgb_goal, + force * (offset_h - pos_e) / (offset_h - offset_l), + do_h, do_s, do_l); + stop->updateRepr(); + child_prev->updateRepr(); + break; + } else { + // wide brush, may affect more than 2 stops, + // paint each stop by the force from the profile curve + if (offset_l <= pos_e && offset_l > pos_e - r) { + tweak_color (mode, SP_STOP(child_prev)->specified_color.v.c, rgb_goal, + force * tweak_profile (fabs (pos_e - offset_l), r), + do_h, do_s, do_l); + child_prev->updateRepr(); + } + + if (offset_h >= pos_e && offset_h < pos_e + r) { + tweak_color (mode, stop->specified_color.v.c, rgb_goal, + force * tweak_profile (fabs (pos_e - offset_h), r), + do_h, do_s, do_l); + stop->updateRepr(); + } + } + } + + offset_l = offset_h; + child_prev = child; + } +} + +static bool +sp_tweak_color_recursive (guint mode, SPItem *item, SPItem *item_at_point, + guint32 fill_goal, bool do_fill, + guint32 stroke_goal, bool do_stroke, + float opacity_goal, bool do_opacity, + bool do_blur, bool reverse, + Geom::Point p, double radius, double force, + bool do_h, bool do_s, bool do_l, bool do_o) +{ + bool did = false; + + if (SP_IS_GROUP(item)) { + for (SPObject *child = item->firstChild() ; child; child = child->getNext() ) { + if (SP_IS_ITEM(child)) { + if (sp_tweak_color_recursive (mode, SP_ITEM(child), item_at_point, + fill_goal, do_fill, + stroke_goal, do_stroke, + opacity_goal, do_opacity, + do_blur, reverse, + p, radius, force, do_h, do_s, do_l, do_o)) { + did = true; + } + } + } + + } else { + SPStyle *style = item->style; + if (!style) { + return false; + } + Geom::OptRect bbox = item->documentGeometricBounds(); + if (!bbox) { + return false; + } + + Geom::Rect brush(p - Geom::Point(radius, radius), p + Geom::Point(radius, radius)); + + Geom::Point center = bbox->midpoint(); + double this_force; + +// if item == item_at_point, use max force + if (item == item_at_point) { + this_force = force; +// else if no overlap of bbox and brush box, skip: + } else if (!bbox->intersects(brush)) { + return false; +//TODO: +// else if object > 1.5 brush: test 4/8/16 points in the brush on hitting the object, choose max + //} else if (bbox->maxExtent() > 3 * radius) { + //} +// else if object > 0.5 brush: test 4 corners of bbox and center on being in the brush, choose max +// else if still smaller, then check only the object center: + } else { + this_force = force * tweak_profile (Geom::L2 (p - center), radius); + } + + if (this_force > 0.002) { + + if (do_blur) { + Geom::OptRect bbox = item->documentGeometricBounds(); + if (!bbox) { + return did; + } + + double blur_now = 0; + Geom::Affine i2dt = item->i2dt_affine (); + if (style->filter.set && style->getFilter()) { + //cycle through filter primitives + SPObject *primitive_obj = style->getFilter()->children; + while (primitive_obj) { + if (SP_IS_FILTER_PRIMITIVE(primitive_obj)) { + SPFilterPrimitive *primitive = SP_FILTER_PRIMITIVE(primitive_obj); + //if primitive is gaussianblur + if(SP_IS_GAUSSIANBLUR(primitive)) { + SPGaussianBlur * spblur = SP_GAUSSIANBLUR(primitive); + float num = spblur->stdDeviation.getNumber(); + blur_now += num * i2dt.descrim(); // sum all blurs in the filter + } + } + primitive_obj = primitive_obj->next; + } + } + double perimeter = bbox->dimensions()[Geom::X] + bbox->dimensions()[Geom::Y]; + blur_now = blur_now / perimeter; + + double blur_new; + if (reverse) { + blur_new = blur_now - 0.06 * force; + } else { + blur_new = blur_now + 0.06 * force; + } + if (blur_new < 0.0005 && blur_new < blur_now) { + blur_new = 0; + } + if (blur_new == 0) { + remove_filter(item, false); + } else { + double radius = blur_new * perimeter; + SPFilter *filter = modify_filter_gaussian_blur_from_item(item->document, item, radius); + sp_style_set_property_url(item, "filter", filter, false); + } + return true; // do not do colors, blur is a separate mode + } + + if (do_fill) { + if (style->fill.isPaintserver()) { + tweak_colors_in_gradient(item, Inkscape::FOR_FILL, fill_goal, p, radius, this_force, mode, do_h, do_s, do_l, do_o); + did = true; + } else if (style->fill.isColor()) { + tweak_color (mode, style->fill.value.color.v.c, fill_goal, this_force, do_h, do_s, do_l); + item->updateRepr(); + did = true; + } + } + if (do_stroke) { + if (style->stroke.isPaintserver()) { + tweak_colors_in_gradient(item, Inkscape::FOR_STROKE, stroke_goal, p, radius, this_force, mode, do_h, do_s, do_l, do_o); + did = true; + } else if (style->stroke.isColor()) { + tweak_color (mode, style->stroke.value.color.v.c, stroke_goal, this_force, do_h, do_s, do_l); + item->updateRepr(); + did = true; + } + } + if (do_opacity && do_o) { + tweak_opacity (mode, &style->opacity, opacity_goal, this_force); + } + } + } + + return did; +} + + +static bool +sp_tweak_dilate (TweakTool *tc, Geom::Point event_p, Geom::Point p, Geom::Point vector, bool reverse) +{ + Inkscape::Selection *selection = sp_desktop_selection(SP_EVENT_CONTEXT(tc)->desktop); + SPDesktop *desktop = SP_EVENT_CONTEXT(tc)->desktop; + + if (selection->isEmpty()) { + return false; + } + + bool did = false; + double radius = get_dilate_radius(tc); + + SPItem *item_at_point = SP_EVENT_CONTEXT(tc)->desktop->getItemAtPoint(event_p, TRUE); + + bool do_fill = false, do_stroke = false, do_opacity = false; + guint32 fill_goal = sp_desktop_get_color_tool(desktop, "/tools/tweak", true, &do_fill); + guint32 stroke_goal = sp_desktop_get_color_tool(desktop, "/tools/tweak", false, &do_stroke); + double opacity_goal = sp_desktop_get_master_opacity_tool(desktop, "/tools/tweak", &do_opacity); + if (reverse) { +#if 0 + // HSL inversion + float hsv[3]; + float rgb[3]; + sp_color_rgb_to_hsv_floatv (hsv, + SP_RGBA32_R_F(fill_goal), + SP_RGBA32_G_F(fill_goal), + SP_RGBA32_B_F(fill_goal)); + sp_color_hsv_to_rgb_floatv (rgb, hsv[0]<.5? hsv[0]+.5 : hsv[0]-.5, 1 - hsv[1], 1 - hsv[2]); + fill_goal = SP_RGBA32_F_COMPOSE(rgb[0], rgb[1], rgb[2], 1); + sp_color_rgb_to_hsv_floatv (hsv, + SP_RGBA32_R_F(stroke_goal), + SP_RGBA32_G_F(stroke_goal), + SP_RGBA32_B_F(stroke_goal)); + sp_color_hsv_to_rgb_floatv (rgb, hsv[0]<.5? hsv[0]+.5 : hsv[0]-.5, 1 - hsv[1], 1 - hsv[2]); + stroke_goal = SP_RGBA32_F_COMPOSE(rgb[0], rgb[1], rgb[2], 1); +#else + // RGB inversion + fill_goal = SP_RGBA32_U_COMPOSE( + (255 - SP_RGBA32_R_U(fill_goal)), + (255 - SP_RGBA32_G_U(fill_goal)), + (255 - SP_RGBA32_B_U(fill_goal)), + (255 - SP_RGBA32_A_U(fill_goal))); + stroke_goal = SP_RGBA32_U_COMPOSE( + (255 - SP_RGBA32_R_U(stroke_goal)), + (255 - SP_RGBA32_G_U(stroke_goal)), + (255 - SP_RGBA32_B_U(stroke_goal)), + (255 - SP_RGBA32_A_U(stroke_goal))); +#endif + opacity_goal = 1 - opacity_goal; + } + + double path_force = get_path_force(tc); + if (radius == 0 || path_force == 0) { + return false; + } + double move_force = get_move_force(tc); + double color_force = MIN(sqrt(path_force)/20.0, 1); + + for (GSList *items = g_slist_copy(const_cast(selection->itemList())); + items != NULL; + items = items->next) { + + SPItem *item = SP_ITEM(items->data); + + if (is_color_mode (tc->mode)) { + if (do_fill || do_stroke || do_opacity) { + if (sp_tweak_color_recursive (tc->mode, item, item_at_point, + fill_goal, do_fill, + stroke_goal, do_stroke, + opacity_goal, do_opacity, + tc->mode == TWEAK_MODE_BLUR, reverse, + p, radius, color_force, tc->do_h, tc->do_s, tc->do_l, tc->do_o)) { + did = true; + } + } + } else if (is_transform_mode(tc->mode)) { + if (sp_tweak_dilate_recursive (selection, item, p, vector, tc->mode, radius, move_force, tc->fidelity, reverse)) { + did = true; + } + } else { + if (sp_tweak_dilate_recursive (selection, item, p, vector, tc->mode, radius, path_force, tc->fidelity, reverse)) { + did = true; + } + } + } + + return did; +} + +static void +sp_tweak_update_area (TweakTool *tc) +{ + double radius = get_dilate_radius(tc); + Geom::Affine const sm (Geom::Scale(radius, radius) * Geom::Translate(SP_EVENT_CONTEXT(tc)->desktop->point())); + sp_canvas_item_affine_absolute(tc->dilate_area, sm); + sp_canvas_item_show(tc->dilate_area); +} + +static void +sp_tweak_switch_mode (TweakTool *tc, gint mode, bool with_shift) +{ + SP_EVENT_CONTEXT(tc)->desktop->setToolboxSelectOneValue ("tweak_tool_mode", mode); + // need to set explicitly, because the prefs may not have changed by the previous + tc->mode = mode; + tc->update_cursor(with_shift); +} + +static void +sp_tweak_switch_mode_temporarily (TweakTool *tc, gint mode, bool with_shift) +{ + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + // Juggling about so that prefs have the old value but tc->mode and the button show new mode: + gint now_mode = prefs->getInt("/tools/tweak/mode", 0); + SP_EVENT_CONTEXT(tc)->desktop->setToolboxSelectOneValue ("tweak_tool_mode", mode); + // button has changed prefs, restore + prefs->setInt("/tools/tweak/mode", now_mode); + // changing prefs changed tc->mode, restore back :) + tc->mode = mode; + tc->update_cursor(with_shift); +} + +bool TweakTool::root_handler(GdkEvent* event) { + gint ret = FALSE; + + switch (event->type) { + case GDK_ENTER_NOTIFY: + sp_canvas_item_show(this->dilate_area); + break; + case GDK_LEAVE_NOTIFY: + sp_canvas_item_hide(this->dilate_area); + break; + case GDK_BUTTON_PRESS: + if (event->button.button == 1 && !this->space_panning) { + + if (Inkscape::have_viable_layer(desktop, this->message_context) == false) { + return TRUE; + } + + Geom::Point const button_w(event->button.x, + event->button.y); + Geom::Point const button_dt(desktop->w2d(button_w)); + this->last_push = desktop->dt2doc(button_dt); + + sp_tweak_extinput(this, event); + + desktop->canvas->forceFullRedrawAfterInterruptions(3); + this->is_drawing = true; + this->is_dilating = true; + this->has_dilated = false; + + ret = TRUE; + } + break; + case GDK_MOTION_NOTIFY: + { + Geom::Point const motion_w(event->motion.x, + event->motion.y); + Geom::Point motion_dt(desktop->w2d(motion_w)); + Geom::Point motion_doc(desktop->dt2doc(motion_dt)); + sp_tweak_extinput(this, event); + + // draw the dilating cursor + double radius = get_dilate_radius(this); + Geom::Affine const sm (Geom::Scale(radius, radius) * Geom::Translate(desktop->w2d(motion_w))); + sp_canvas_item_affine_absolute(this->dilate_area, sm); + sp_canvas_item_show(this->dilate_area); + + guint num = 0; + if (!desktop->selection->isEmpty()) { + num = g_slist_length(const_cast(desktop->selection->itemList())); + } + if (num == 0) { + this->message_context->flash(Inkscape::ERROR_MESSAGE, _("Nothing selected! Select objects to tweak.")); + } + + // dilating: + if (this->is_drawing && ( event->motion.state & GDK_BUTTON1_MASK )) { + sp_tweak_dilate (this, motion_w, motion_doc, motion_doc - this->last_push, event->button.state & GDK_SHIFT_MASK? true : false); + //this->last_push = motion_doc; + this->has_dilated = true; + // it's slow, so prevent clogging up with events + gobble_motion_events(GDK_BUTTON1_MASK); + return TRUE; + } + + } + break; + case GDK_BUTTON_RELEASE: + { + Geom::Point const motion_w(event->button.x, event->button.y); + Geom::Point const motion_dt(desktop->w2d(motion_w)); + + desktop->canvas->endForcedFullRedraws(); + this->is_drawing = false; + + if (this->is_dilating && event->button.button == 1 && !this->space_panning) { + if (!this->has_dilated) { + // if we did not rub, do a light tap + this->pressure = 0.03; + sp_tweak_dilate (this, motion_w, desktop->dt2doc(motion_dt), Geom::Point(0,0), MOD__SHIFT(event)); + } + this->is_dilating = false; + this->has_dilated = false; + switch (this->mode) { + case TWEAK_MODE_MOVE: + DocumentUndo::done(sp_desktop_document(SP_EVENT_CONTEXT(this)->desktop), + SP_VERB_CONTEXT_TWEAK, _("Move tweak")); + break; + case TWEAK_MODE_MOVE_IN_OUT: + DocumentUndo::done(sp_desktop_document(SP_EVENT_CONTEXT(this)->desktop), + SP_VERB_CONTEXT_TWEAK, _("Move in/out tweak")); + break; + case TWEAK_MODE_MOVE_JITTER: + DocumentUndo::done(sp_desktop_document(SP_EVENT_CONTEXT(this)->desktop), + SP_VERB_CONTEXT_TWEAK, _("Move jitter tweak")); + break; + case TWEAK_MODE_SCALE: + DocumentUndo::done(sp_desktop_document(SP_EVENT_CONTEXT(this)->desktop), + SP_VERB_CONTEXT_TWEAK, _("Scale tweak")); + break; + case TWEAK_MODE_ROTATE: + DocumentUndo::done(sp_desktop_document(SP_EVENT_CONTEXT(this)->desktop), + SP_VERB_CONTEXT_TWEAK, _("Rotate tweak")); + break; + case TWEAK_MODE_MORELESS: + DocumentUndo::done(sp_desktop_document(SP_EVENT_CONTEXT(this)->desktop), + SP_VERB_CONTEXT_TWEAK, _("Duplicate/delete tweak")); + break; + case TWEAK_MODE_PUSH: + DocumentUndo::done(sp_desktop_document(SP_EVENT_CONTEXT(this)->desktop), + SP_VERB_CONTEXT_TWEAK, _("Push path tweak")); + break; + case TWEAK_MODE_SHRINK_GROW: + DocumentUndo::done(sp_desktop_document(SP_EVENT_CONTEXT(this)->desktop), + SP_VERB_CONTEXT_TWEAK, _("Shrink/grow path tweak")); + break; + case TWEAK_MODE_ATTRACT_REPEL: + DocumentUndo::done(sp_desktop_document(SP_EVENT_CONTEXT(this)->desktop), + SP_VERB_CONTEXT_TWEAK, _("Attract/repel path tweak")); + break; + case TWEAK_MODE_ROUGHEN: + DocumentUndo::done(sp_desktop_document(SP_EVENT_CONTEXT(this)->desktop), + SP_VERB_CONTEXT_TWEAK, _("Roughen path tweak")); + break; + case TWEAK_MODE_COLORPAINT: + DocumentUndo::done(sp_desktop_document(SP_EVENT_CONTEXT(this)->desktop), + SP_VERB_CONTEXT_TWEAK, _("Color paint tweak")); + break; + case TWEAK_MODE_COLORJITTER: + DocumentUndo::done(sp_desktop_document(SP_EVENT_CONTEXT(this)->desktop), + SP_VERB_CONTEXT_TWEAK, _("Color jitter tweak")); + break; + case TWEAK_MODE_BLUR: + DocumentUndo::done(sp_desktop_document(SP_EVENT_CONTEXT(this)->desktop), + SP_VERB_CONTEXT_TWEAK, _("Blur tweak")); + break; + } + } + break; + } + case GDK_KEY_PRESS: + { + switch (get_group0_keyval (&event->key)) { + case GDK_KEY_m: + case GDK_KEY_M: + case GDK_KEY_0: + if (MOD__SHIFT_ONLY(event)) { + sp_tweak_switch_mode(this, TWEAK_MODE_MOVE, MOD__SHIFT(event)); + ret = TRUE; + } + break; + case GDK_KEY_i: + case GDK_KEY_I: + case GDK_KEY_1: + if (MOD__SHIFT_ONLY(event)) { + sp_tweak_switch_mode(this, TWEAK_MODE_MOVE_IN_OUT, MOD__SHIFT(event)); + ret = TRUE; + } + break; + case GDK_KEY_z: + case GDK_KEY_Z: + case GDK_KEY_2: + if (MOD__SHIFT_ONLY(event)) { + sp_tweak_switch_mode(this, TWEAK_MODE_MOVE_JITTER, MOD__SHIFT(event)); + ret = TRUE; + } + break; + case GDK_KEY_less: + case GDK_KEY_comma: + case GDK_KEY_greater: + case GDK_KEY_period: + case GDK_KEY_3: + if (MOD__SHIFT_ONLY(event)) { + sp_tweak_switch_mode(this, TWEAK_MODE_SCALE, MOD__SHIFT(event)); + ret = TRUE; + } + break; + case GDK_KEY_bracketright: + case GDK_KEY_bracketleft: + case GDK_KEY_4: + if (MOD__SHIFT_ONLY(event)) { + sp_tweak_switch_mode(this, TWEAK_MODE_ROTATE, MOD__SHIFT(event)); + ret = TRUE; + } + break; + case GDK_KEY_d: + case GDK_KEY_D: + case GDK_KEY_5: + if (MOD__SHIFT_ONLY(event)) { + sp_tweak_switch_mode(this, TWEAK_MODE_MORELESS, MOD__SHIFT(event)); + ret = TRUE; + } + break; + case GDK_KEY_p: + case GDK_KEY_P: + case GDK_KEY_6: + if (MOD__SHIFT_ONLY(event)) { + sp_tweak_switch_mode(this, TWEAK_MODE_PUSH, MOD__SHIFT(event)); + ret = TRUE; + } + break; + case GDK_KEY_s: + case GDK_KEY_S: + case GDK_KEY_7: + if (MOD__SHIFT_ONLY(event)) { + sp_tweak_switch_mode(this, TWEAK_MODE_SHRINK_GROW, MOD__SHIFT(event)); + ret = TRUE; + } + break; + case GDK_KEY_a: + case GDK_KEY_A: + case GDK_KEY_8: + if (MOD__SHIFT_ONLY(event)) { + sp_tweak_switch_mode(this, TWEAK_MODE_ATTRACT_REPEL, MOD__SHIFT(event)); + ret = TRUE; + } + break; + case GDK_KEY_r: + case GDK_KEY_R: + case GDK_KEY_9: + if (MOD__SHIFT_ONLY(event)) { + sp_tweak_switch_mode(this, TWEAK_MODE_ROUGHEN, MOD__SHIFT(event)); + ret = TRUE; + } + break; + case GDK_KEY_c: + case GDK_KEY_C: + if (MOD__SHIFT_ONLY(event)) { + sp_tweak_switch_mode(this, TWEAK_MODE_COLORPAINT, MOD__SHIFT(event)); + ret = TRUE; + } + break; + case GDK_KEY_j: + case GDK_KEY_J: + if (MOD__SHIFT_ONLY(event)) { + sp_tweak_switch_mode(this, TWEAK_MODE_COLORJITTER, MOD__SHIFT(event)); + ret = TRUE; + } + break; + case GDK_KEY_b: + case GDK_KEY_B: + if (MOD__SHIFT_ONLY(event)) { + sp_tweak_switch_mode(this, TWEAK_MODE_BLUR, MOD__SHIFT(event)); + ret = TRUE; + } + break; + + case GDK_KEY_Up: + case GDK_KEY_KP_Up: + if (!MOD__CTRL_ONLY(event)) { + this->force += 0.05; + if (this->force > 1.0) { + this->force = 1.0; + } + desktop->setToolboxAdjustmentValue ("tweak-force", this->force * 100); + ret = TRUE; + } + break; + case GDK_KEY_Down: + case GDK_KEY_KP_Down: + if (!MOD__CTRL_ONLY(event)) { + this->force -= 0.05; + if (this->force < 0.0) { + this->force = 0.0; + } + desktop->setToolboxAdjustmentValue ("tweak-force", this->force * 100); + ret = TRUE; + } + break; + case GDK_KEY_Right: + case GDK_KEY_KP_Right: + if (!MOD__CTRL_ONLY(event)) { + this->width += 0.01; + if (this->width > 1.0) { + this->width = 1.0; + } + desktop->setToolboxAdjustmentValue ("altx-tweak", this->width * 100); // the same spinbutton is for alt+x + sp_tweak_update_area(this); + ret = TRUE; + } + break; + case GDK_KEY_Left: + case GDK_KEY_KP_Left: + if (!MOD__CTRL_ONLY(event)) { + this->width -= 0.01; + if (this->width < 0.01) { + this->width = 0.01; + } + desktop->setToolboxAdjustmentValue ("altx-tweak", this->width * 100); + sp_tweak_update_area(this); + ret = TRUE; + } + break; + case GDK_KEY_Home: + case GDK_KEY_KP_Home: + this->width = 0.01; + desktop->setToolboxAdjustmentValue ("altx-tweak", this->width * 100); + sp_tweak_update_area(this); + ret = TRUE; + break; + case GDK_KEY_End: + case GDK_KEY_KP_End: + this->width = 1.0; + desktop->setToolboxAdjustmentValue ("altx-tweak", this->width * 100); + sp_tweak_update_area(this); + ret = TRUE; + break; + case GDK_KEY_x: + case GDK_KEY_X: + if (MOD__ALT_ONLY(event)) { + desktop->setToolboxFocusTo ("altx-tweak"); + ret = TRUE; + } + break; + + case GDK_KEY_Shift_L: + case GDK_KEY_Shift_R: + this->update_cursor(true); + break; + + case GDK_KEY_Control_L: + case GDK_KEY_Control_R: + sp_tweak_switch_mode_temporarily(this, TWEAK_MODE_SHRINK_GROW, MOD__SHIFT(event)); + break; + case GDK_KEY_Delete: + case GDK_KEY_KP_Delete: + case GDK_KEY_BackSpace: + ret = this->deleteSelectedDrag(MOD__CTRL_ONLY(event)); + break; + + default: + break; + } + break; + } + case GDK_KEY_RELEASE: { + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + switch (get_group0_keyval(&event->key)) { + case GDK_KEY_Shift_L: + case GDK_KEY_Shift_R: + this->update_cursor(false); + break; + case GDK_KEY_Control_L: + case GDK_KEY_Control_R: + sp_tweak_switch_mode (this, prefs->getInt("/tools/tweak/mode"), MOD__SHIFT(event)); + this->message_context->clear(); + break; + default: + sp_tweak_switch_mode (this, prefs->getInt("/tools/tweak/mode"), MOD__SHIFT(event)); + break; + } + } + default: + break; + } + + if (!ret) { + ret = ToolBase::root_handler(event); + } + + return ret; +} + +} +} +} + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/ui/tools/tweak-tool.h b/src/ui/tools/tweak-tool.h new file mode 100644 index 000000000..6cbb9aded --- /dev/null +++ b/src/ui/tools/tweak-tool.h @@ -0,0 +1,106 @@ +#ifndef __SP_TWEAK_CONTEXT_H__ +#define __SP_TWEAK_CONTEXT_H__ + +/* + * tweaking paths without node editing + * + * Authors: + * bulia byak + * + * Copyright (C) 2007 authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include "ui/tools/tool-base.h" +#include <2geom/point.h> + +#define SAMPLING_SIZE 8 /* fixme: ?? */ + +#define TC_MIN_PRESSURE 0.0 +#define TC_MAX_PRESSURE 1.0 +#define TC_DEFAULT_PRESSURE 0.35 + +namespace Inkscape { +namespace UI { +namespace Tools { + +enum { + TWEAK_MODE_MOVE, + TWEAK_MODE_MOVE_IN_OUT, + TWEAK_MODE_MOVE_JITTER, + TWEAK_MODE_SCALE, + TWEAK_MODE_ROTATE, + TWEAK_MODE_MORELESS, + TWEAK_MODE_PUSH, + TWEAK_MODE_SHRINK_GROW, + TWEAK_MODE_ATTRACT_REPEL, + TWEAK_MODE_ROUGHEN, + TWEAK_MODE_COLORPAINT, + TWEAK_MODE_COLORJITTER, + TWEAK_MODE_BLUR +}; + +class TweakTool : public ToolBase { +public: + TweakTool(); + virtual ~TweakTool(); + + /* extended input data */ + gdouble pressure; + + /* attributes */ + guint dragging : 1; /* mouse state: mouse is dragging */ + guint usepressure : 1; + guint usetilt : 1; + + double width; + double force; + double fidelity; + + gint mode; + + bool is_drawing; + + bool is_dilating; + bool has_dilated; + Geom::Point last_push; + SPCanvasItem *dilate_area; + + bool do_h; + bool do_s; + bool do_l; + bool do_o; + + sigc::connection style_set_connection; + + static const std::string prefsPath; + + virtual void setup(); + virtual void set(const Inkscape::Preferences::Entry& val); + virtual bool root_handler(GdkEvent* event); + + virtual const std::string& getPrefsPath(); + + void update_cursor(bool with_shift); + +private: + bool set_style(const SPCSSAttr* css); +}; + +} +} +} + +#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:fileencoding=utf-8:textwidth=99 : diff --git a/src/ui/tools/zoom-tool.cpp b/src/ui/tools/zoom-tool.cpp new file mode 100644 index 000000000..d4ede1053 --- /dev/null +++ b/src/ui/tools/zoom-tool.cpp @@ -0,0 +1,250 @@ +/* + * Handy zooming tool + * + * Authors: + * Lauris Kaplinski + * Frank Felfe + * bulia byak + * + * Copyright (C) 1999-2002 Authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + + +#include + +#include "macros.h" +#include "rubberband.h" +#include "display/sp-canvas-item.h" +#include "display/sp-canvas-util.h" +#include "desktop.h" +#include "pixmaps/cursor-zoom.xpm" +#include "pixmaps/cursor-zoom-out.xpm" +#include "preferences.h" +#include "selection-chemistry.h" + +#include "ui/tools/zoom-tool.h" +#include "tool-factory.h" + +namespace Inkscape { +namespace UI { +namespace Tools { + +namespace { + ToolBase* createZoomContext() { + return new ZoomTool(); + } + + bool zoomContextRegistered = ToolFactory::instance().registerObject("/tools/zoom", createZoomContext); +} + +const std::string& ZoomTool::getPrefsPath() { + return ZoomTool::prefsPath; +} + +const std::string ZoomTool::prefsPath = "/tools/zoom"; + +ZoomTool::ZoomTool() : ToolBase() { + this->grabbed = 0; + this->cursor_shape = cursor_zoom_xpm; + this->hot_x = 6; + this->hot_y = 6; + this->escaped = false; +} + +ZoomTool::~ZoomTool() { +} + +void ZoomTool::finish() { + this->enableGrDrag(false); + + if (this->grabbed) { + sp_canvas_item_ungrab(this->grabbed, GDK_CURRENT_TIME); + this->grabbed = NULL; + } +} + +void ZoomTool::setup() { + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + + if (prefs->getBool("/tools/zoom/selcue")) { + this->enableSelectionCue(); + } + + if (prefs->getBool("/tools/zoom/gradientdrag")) { + this->enableGrDrag(); + } + + ToolBase::setup(); +} + +bool ZoomTool::root_handler(GdkEvent* event) { + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + + tolerance = prefs->getIntLimited("/options/dragtolerance/value", 0, 0, 100); + double const zoom_inc = prefs->getDoubleLimited("/options/zoomincrement/value", M_SQRT2, 1.01, 10); + + bool ret = false; + + switch (event->type) { + case GDK_BUTTON_PRESS: + { + Geom::Point const button_w(event->button.x, event->button.y); + Geom::Point const button_dt(desktop->w2d(button_w)); + + if (event->button.button == 1 && !this->space_panning) { + // save drag origin + xp = (gint) event->button.x; + yp = (gint) event->button.y; + within_tolerance = true; + + Inkscape::Rubberband::get(desktop)->start(desktop, button_dt); + + escaped = false; + + ret = true; + } else if (event->button.button == 3) { + double const zoom_rel( (event->button.state & GDK_SHIFT_MASK) + ? zoom_inc + : 1 / zoom_inc ); + + desktop->zoom_relative_keep_point(button_dt, zoom_rel); + ret = true; + } + + sp_canvas_item_grab(SP_CANVAS_ITEM(desktop->acetate), + GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK | GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK, + NULL, event->button.time); + + this->grabbed = SP_CANVAS_ITEM(desktop->acetate); + break; + } + + case GDK_MOTION_NOTIFY: + if ((event->motion.state & GDK_BUTTON1_MASK) && !this->space_panning) { + ret = true; + + if ( within_tolerance + && ( abs( (gint) event->motion.x - xp ) < tolerance ) + && ( abs( (gint) event->motion.y - yp ) < tolerance ) ) { + break; // do not drag if we're within tolerance from origin + } + // Once the user has moved farther than tolerance from the original location + // (indicating they intend to move the object, not click), then always process the + // motion notify coordinates as given (no snapping back to origin) + within_tolerance = false; + + Geom::Point const motion_w(event->motion.x, event->motion.y); + Geom::Point const motion_dt(desktop->w2d(motion_w)); + Inkscape::Rubberband::get(desktop)->move(motion_dt); + gobble_motion_events(GDK_BUTTON1_MASK); + } + break; + + case GDK_BUTTON_RELEASE: + { + Geom::Point const button_w(event->button.x, event->button.y); + Geom::Point const button_dt(desktop->w2d(button_w)); + + if ( event->button.button == 1 && !this->space_panning) { + Geom::OptRect const b = Inkscape::Rubberband::get(desktop)->getRectangle(); + + if (b && !within_tolerance) { + desktop->set_display_area(*b, 10); + } else if (!escaped) { + double const zoom_rel( (event->button.state & GDK_SHIFT_MASK) + ? 1 / zoom_inc + : zoom_inc ); + + desktop->zoom_relative_keep_point(button_dt, zoom_rel); + } + + ret = true; + } + + Inkscape::Rubberband::get(desktop)->stop(); + + if (this->grabbed) { + sp_canvas_item_ungrab(this->grabbed, event->button.time); + this->grabbed = NULL; + } + + xp = yp = 0; + escaped = false; + break; + } + case GDK_KEY_PRESS: + switch (get_group0_keyval (&event->key)) { + case GDK_KEY_Escape: + if (!Inkscape::Rubberband::get(desktop)->is_started()) { + Inkscape::SelectionHelper::selectNone(desktop); + } + + Inkscape::Rubberband::get(desktop)->stop(); + xp = yp = 0; + escaped = true; + ret = true; + break; + + case GDK_KEY_Up: + case GDK_KEY_Down: + case GDK_KEY_KP_Up: + case GDK_KEY_KP_Down: + // prevent the zoom field from activation + if (!MOD__CTRL_ONLY(event)) + ret = true; + break; + + case GDK_KEY_Shift_L: + case GDK_KEY_Shift_R: + this->cursor_shape = cursor_zoom_out_xpm; + this->sp_event_context_update_cursor(); + break; + + case GDK_KEY_Delete: + case GDK_KEY_KP_Delete: + case GDK_KEY_BackSpace: + ret = this->deleteSelectedDrag(MOD__CTRL_ONLY(event)); + break; + + default: + break; + } + break; + case GDK_KEY_RELEASE: + switch (get_group0_keyval (&event->key)) { + case GDK_KEY_Shift_L: + case GDK_KEY_Shift_R: + this->cursor_shape = cursor_zoom_xpm; + this->sp_event_context_update_cursor(); + break; + default: + break; + } + break; + default: + break; + } + + if (!ret) { + ret = ToolBase::root_handler(event); + } + + return ret; +} + +} +} +} + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 : diff --git a/src/ui/tools/zoom-tool.h b/src/ui/tools/zoom-tool.h new file mode 100644 index 000000000..6e5cca7aa --- /dev/null +++ b/src/ui/tools/zoom-tool.h @@ -0,0 +1,47 @@ +#ifndef __SP_ZOOM_CONTEXT_H__ +#define __SP_ZOOM_CONTEXT_H__ + +/* + * Handy zooming tool + * + * Authors: + * Lauris Kaplinski + * Frank Felfe + * + * Copyright (C) 1999-2002 Authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include "ui/tools/tool-base.h" + +#define SP_ZOOM_CONTEXT(obj) (dynamic_cast((Inkscape::UI::Tools::ToolBase*)obj)) +#define SP_IS_ZOOM_CONTEXT(obj) (dynamic_cast((const Inkscape::UI::Tools::ToolBase*)obj) != NULL) + +namespace Inkscape { +namespace UI { +namespace Tools { + +class ZoomTool : public ToolBase { +public: + ZoomTool(); + virtual ~ZoomTool(); + + static const std::string prefsPath; + + virtual void setup(); + virtual void finish(); + virtual bool root_handler(GdkEvent* event); + + virtual const std::string& getPrefsPath(); + +private: + SPCanvasItem *grabbed; + bool escaped; +}; + +} +} +} + +#endif diff --git a/src/ui/widget/rotateable.cpp b/src/ui/widget/rotateable.cpp index 24a21e075..72ec69362 100644 --- a/src/ui/widget/rotateable.cpp +++ b/src/ui/widget/rotateable.cpp @@ -19,7 +19,7 @@ #include #include #include <2geom/point.h> -#include "event-context.h" +#include "ui/tools/tool-base.h" #include "rotateable.h" namespace Inkscape { diff --git a/src/ui/widget/selected-style.cpp b/src/ui/widget/selected-style.cpp index 000fdee4b..aa617353c 100644 --- a/src/ui/widget/selected-style.cpp +++ b/src/ui/widget/selected-style.cpp @@ -39,7 +39,7 @@ #include "sp-gradient.h" #include "svg/svg-color.h" #include "svg/css-ostringstream.h" -#include "event-context.h" +#include "ui/tools/tool-base.h" #include "message-context.h" #include "verbs.h" #include "color.h" diff --git a/src/ui/widget/spinbutton.cpp b/src/ui/widget/spinbutton.cpp index aa8f68ce8..7709a837b 100644 --- a/src/ui/widget/spinbutton.cpp +++ b/src/ui/widget/spinbutton.cpp @@ -16,7 +16,7 @@ #include "unit-menu.h" #include "unit-tracker.h" #include "util/expression-evaluator.h" -#include "event-context.h" +#include "ui/tools/tool-base.h" namespace Inkscape { namespace UI { diff --git a/src/vanishing-point.cpp b/src/vanishing-point.cpp index edc3dd15b..361a7a0de 100644 --- a/src/vanishing-point.cpp +++ b/src/vanishing-point.cpp @@ -19,7 +19,7 @@ #include "desktop.h" #include "display/sp-canvas-item.h" #include "display/sp-ctrlline.h" -#include "event-context.h" +#include "ui/tools/tool-base.h" #include "xml/repr.h" #include "perspective-line.h" #include "shape-editor.h" diff --git a/src/verbs.cpp b/src/verbs.cpp index 4bcfd39fe..329e63115 100644 --- a/src/verbs.cpp +++ b/src/verbs.cpp @@ -44,9 +44,9 @@ #include "desktop-handles.h" #include "display/curve.h" #include "document.h" -#include "draw-context.h" +#include "ui/tools/freehand-base.h" #include "extension/effect.h" -#include "event-context.h" +#include "ui/tools/tool-base.h" #include "file.h" #include "gradient-drag.h" #include "helper/action.h" @@ -59,7 +59,7 @@ #include "message-stack.h" #include "path-chemistry.h" #include "preferences.h" -#include "select-context.h" +#include "ui/tools/select-tool.h" #include "selection-chemistry.h" #include "seltrans.h" #include "shape-editor.h" @@ -86,7 +86,7 @@ #include "ui/dialog/symbols.h" #include "ui/dialog/spellcheck.h" #include "ui/icon-names.h" -#include "ui/tool/node-tool.h" +#include "ui/tools/node-tool.h" #include "selection.h" #include diff --git a/src/widgets/arc-toolbar.cpp b/src/widgets/arc-toolbar.cpp index 5569780e7..69b540762 100644 --- a/src/widgets/arc-toolbar.cpp +++ b/src/widgets/arc-toolbar.cpp @@ -56,7 +56,7 @@ #include "../xml/repr.h" #include "ui/uxmanager.h" #include "../ui/icon-names.h" -#include "../pen-context.h" +#include "ui/tools/pen-tool.h" #include "../sp-ellipse.h" #include "../mod360.h" diff --git a/src/widgets/box3d-toolbar.cpp b/src/widgets/box3d-toolbar.cpp index 91d4ebdec..32516bbfb 100644 --- a/src/widgets/box3d-toolbar.cpp +++ b/src/widgets/box3d-toolbar.cpp @@ -58,9 +58,9 @@ #include "../ui/icon-names.h" -#include "../pen-context.h" +#include "ui/tools/pen-tool.h" -#include "../box3d-context.h" +#include "ui/tools/box3d-tool.h" #include "../box3d.h" using Inkscape::UI::UXManager; diff --git a/src/widgets/calligraphy-toolbar.cpp b/src/widgets/calligraphy-toolbar.cpp index 12228ce56..9f08d3462 100644 --- a/src/widgets/calligraphy-toolbar.cpp +++ b/src/widgets/calligraphy-toolbar.cpp @@ -57,7 +57,7 @@ #include "ui/uxmanager.h" #include "../ui/icon-names.h" -#include "../pen-context.h" +#include "ui/tools/pen-tool.h" using Inkscape::UI::UXManager; using Inkscape::DocumentUndo; diff --git a/src/widgets/connector-toolbar.cpp b/src/widgets/connector-toolbar.cpp index 32a6e8fb9..7230f521c 100644 --- a/src/widgets/connector-toolbar.cpp +++ b/src/widgets/connector-toolbar.cpp @@ -55,10 +55,10 @@ #include "../xml/repr.h" #include "ui/uxmanager.h" #include "../ui/icon-names.h" -#include "../pen-context.h" +#include "ui/tools/pen-tool.h" #include "../sp-namedview.h" #include "../conn-avoid-ref.h" -#include "../connector-context.h" +#include "ui/tools/connector-tool.h" #include "../graphlayout.h" #include "../sp-path.h" diff --git a/src/widgets/desktop-widget.cpp b/src/widgets/desktop-widget.cpp index 3ca433586..7e254cdcd 100644 --- a/src/widgets/desktop-widget.cpp +++ b/src/widgets/desktop-widget.cpp @@ -27,7 +27,7 @@ #include <2geom/rect.h> #include "ui/dialog/dialog-manager.h" -#include "box3d-context.h" +#include "ui/tools/box3d-tool.h" #include "cms-system.h" #include "conn-avoid-ref.h" #include "desktop.h" diff --git a/src/widgets/dropper-toolbar.cpp b/src/widgets/dropper-toolbar.cpp index 054955d8f..991489b86 100644 --- a/src/widgets/dropper-toolbar.cpp +++ b/src/widgets/dropper-toolbar.cpp @@ -54,7 +54,7 @@ #include "../xml/repr.h" #include "ui/uxmanager.h" #include "../ui/icon-names.h" -#include "../pen-context.h" +#include "ui/tools/pen-tool.h" #include "../tools-switch.h" using Inkscape::UI::UXManager; diff --git a/src/widgets/eraser-toolbar.cpp b/src/widgets/eraser-toolbar.cpp index 1af574ed6..5e09521c9 100644 --- a/src/widgets/eraser-toolbar.cpp +++ b/src/widgets/eraser-toolbar.cpp @@ -55,7 +55,7 @@ #include "../xml/repr.h" #include "ui/uxmanager.h" #include "../ui/icon-names.h" -#include "../pen-context.h" +#include "ui/tools/pen-tool.h" using Inkscape::UI::UXManager; diff --git a/src/widgets/gradient-toolbar.cpp b/src/widgets/gradient-toolbar.cpp index a9122e54b..f02185a15 100644 --- a/src/widgets/gradient-toolbar.cpp +++ b/src/widgets/gradient-toolbar.cpp @@ -35,7 +35,7 @@ #include "desktop-handles.h" #include -#include "gradient-context.h" +#include "ui/tools/gradient-tool.h" #include "gradient-drag.h" #include "sp-linear-gradient.h" #include "sp-radial-gradient.h" @@ -54,7 +54,7 @@ #include "svg/css-ostringstream.h" #include "svg/svg-color.h" #include "desktop-style.h" -#include "gradient-context.h" +#include "ui/tools/gradient-tool.h" #include "gradient-toolbar.h" #include "toolbox.h" diff --git a/src/widgets/lpe-toolbar.cpp b/src/widgets/lpe-toolbar.cpp index 9fdb7b036..559f3fc3c 100644 --- a/src/widgets/lpe-toolbar.cpp +++ b/src/widgets/lpe-toolbar.cpp @@ -59,12 +59,12 @@ #include "../helper/action-context.h" #include "util/units.h" #include "ui/widget/unit-tracker.h" -#include "../pen-context.h" +#include "ui/tools/pen-tool.h" #include "../sp-namedview.h" #include "../tools-switch.h" #include "../live_effects/effect.h" #include "../live_effects/lpe-angle_bisector.h" -#include "../lpe-tool-context.h" +#include "ui/tools/lpe-tool.h" using Inkscape::UI::Widget::UnitTracker; using Inkscape::Util::Unit; diff --git a/src/widgets/measure-toolbar.cpp b/src/widgets/measure-toolbar.cpp index 58244566d..a75dd7600 100644 --- a/src/widgets/measure-toolbar.cpp +++ b/src/widgets/measure-toolbar.cpp @@ -52,7 +52,7 @@ #include "../xml/repr.h" #include "ui/uxmanager.h" #include "../ui/icon-names.h" -#include "../pen-context.h" +#include "ui/tools/pen-tool.h" #include "../sp-namedview.h" #include "ui/widget/unit-tracker.h" diff --git a/src/widgets/mesh-toolbar.cpp b/src/widgets/mesh-toolbar.cpp index 926a42f90..15dda94f0 100644 --- a/src/widgets/mesh-toolbar.cpp +++ b/src/widgets/mesh-toolbar.cpp @@ -41,7 +41,7 @@ #include "desktop-handles.h" #include -#include "gradient-context.h" +#include "ui/tools/gradient-tool.h" #include "gradient-drag.h" #include "sp-mesh-gradient.h" #include "gradient-chemistry.h" @@ -59,7 +59,7 @@ #include "svg/css-ostringstream.h" #include "svg/svg-color.h" #include "desktop-style.h" -#include "gradient-context.h" +#include "ui/tools/gradient-tool.h" #include "toolbox.h" diff --git a/src/widgets/node-toolbar.cpp b/src/widgets/node-toolbar.cpp index 8d24fdf82..6a0968424 100644 --- a/src/widgets/node-toolbar.cpp +++ b/src/widgets/node-toolbar.cpp @@ -55,12 +55,12 @@ #include "../xml/repr.h" #include "ui/uxmanager.h" #include "../ui/tool/control-point-selection.h" -#include "../ui/tool/node-tool.h" +#include "ui/tools/node-tool.h" #include "../ui/tool/multi-path-manipulator.h" #include "../ui/icon-names.h" #include "util/units.h" #include "ui/widget/unit-tracker.h" -#include "../lpe-tool-context.h" +#include "ui/tools/lpe-tool.h" #include "../sp-namedview.h" using Inkscape::UI::Widget::UnitTracker; diff --git a/src/widgets/paintbucket-toolbar.cpp b/src/widgets/paintbucket-toolbar.cpp index e0d406c04..8c4de2b32 100644 --- a/src/widgets/paintbucket-toolbar.cpp +++ b/src/widgets/paintbucket-toolbar.cpp @@ -56,9 +56,9 @@ #include "../ui/icon-names.h" #include "util/units.h" #include "ui/widget/unit-tracker.h" -#include "../pen-context.h" +#include "ui/tools/pen-tool.h" #include "../sp-namedview.h" -#include "../flood-context.h" +#include "ui/tools/flood-tool.h" #include diff --git a/src/widgets/pencil-toolbar.cpp b/src/widgets/pencil-toolbar.cpp index 4abff5061..36f2912e9 100644 --- a/src/widgets/pencil-toolbar.cpp +++ b/src/widgets/pencil-toolbar.cpp @@ -63,11 +63,11 @@ #include "ui/uxmanager.h" //#include "../ui/tool/control-point-selection.h" -//#include "../ui/tool/node-tool.h" +//#include "ui/tools/node-tool.h" //#include "../ui/tool/multi-path-manipulator.h" #include "../ui/icon-names.h" -#include "../pen-context.h" +#include "ui/tools/pen-tool.h" //#include "../sp-namedview.h" #include "../tools-switch.h" diff --git a/src/widgets/rect-toolbar.cpp b/src/widgets/rect-toolbar.cpp index 0287a9aeb..f5a509db3 100644 --- a/src/widgets/rect-toolbar.cpp +++ b/src/widgets/rect-toolbar.cpp @@ -55,7 +55,7 @@ #include "../ui/icon-names.h" #include "util/units.h" #include "ui/widget/unit-tracker.h" -#include "../pen-context.h" +#include "ui/tools/pen-tool.h" #include "../sp-namedview.h" #include "../sp-rect.h" diff --git a/src/widgets/sp-color-notebook.cpp b/src/widgets/sp-color-notebook.cpp index 8b42f4ae1..e081f98e0 100644 --- a/src/widgets/sp-color-notebook.cpp +++ b/src/widgets/sp-color-notebook.cpp @@ -39,7 +39,7 @@ #include "color-profile.h" #include "cms-system.h" #include "tools-switch.h" -#include "event-context.h" +#include "ui/tools/tool-base.h" using Inkscape::CMSSystem; diff --git a/src/widgets/spinbutton-events.cpp b/src/widgets/spinbutton-events.cpp index ac5f0040d..0280694f6 100644 --- a/src/widgets/spinbutton-events.cpp +++ b/src/widgets/spinbutton-events.cpp @@ -18,7 +18,7 @@ #include #include -#include "../event-context.h" +#include "ui/tools/tool-base.h" #include "sp-widget.h" #include "widget-sizes.h" diff --git a/src/widgets/spiral-toolbar.cpp b/src/widgets/spiral-toolbar.cpp index b4e8e68a7..6b10bfce9 100644 --- a/src/widgets/spiral-toolbar.cpp +++ b/src/widgets/spiral-toolbar.cpp @@ -53,7 +53,7 @@ #include "../xml/repr.h" #include "ui/uxmanager.h" #include "../ui/icon-names.h" -#include "../pen-context.h" +#include "ui/tools/pen-tool.h" #include "../sp-spiral.h" using Inkscape::UI::UXManager; diff --git a/src/widgets/spray-toolbar.cpp b/src/widgets/spray-toolbar.cpp index 247df53e2..2a8c85475 100644 --- a/src/widgets/spray-toolbar.cpp +++ b/src/widgets/spray-toolbar.cpp @@ -52,8 +52,8 @@ #include "../xml/repr.h" #include "ui/uxmanager.h" #include "../ui/icon-names.h" -#include "../pen-context.h" -#include "../spray-context.h" +#include "ui/tools/pen-tool.h" +#include "ui/tools/spray-tool.h" using Inkscape::UI::UXManager; using Inkscape::DocumentUndo; diff --git a/src/widgets/star-toolbar.cpp b/src/widgets/star-toolbar.cpp index 9e26988ff..bac0271db 100644 --- a/src/widgets/star-toolbar.cpp +++ b/src/widgets/star-toolbar.cpp @@ -53,7 +53,7 @@ #include "../xml/repr.h" #include "ui/uxmanager.h" #include "../ui/icon-names.h" -#include "../pen-context.h" +#include "ui/tools/pen-tool.h" #include "../sp-star.h" diff --git a/src/widgets/text-toolbar.cpp b/src/widgets/text-toolbar.cpp index a9d29ae98..750fa1de6 100644 --- a/src/widgets/text-toolbar.cpp +++ b/src/widgets/text-toolbar.cpp @@ -56,7 +56,7 @@ #include "../xml/repr.h" #include "ui/uxmanager.h" #include "../ui/icon-names.h" -#include "../pen-context.h" +#include "ui/tools/pen-tool.h" #include "../sp-namedview.h" #include "../svg/css-ostringstream.h" #include "../sp-flowtext.h" @@ -64,7 +64,7 @@ #include "../style.h" #include "../libnrtype/font-lister.h" #include "../libnrtype/font-instance.h" -#include "../text-context.h" +#include "ui/tools/text-tool.h" #include "../text-editing.h" #include "widgets/font-selector.h" diff --git a/src/widgets/toolbox.cpp b/src/widgets/toolbox.cpp index bb8d47339..02da805bf 100644 --- a/src/widgets/toolbox.cpp +++ b/src/widgets/toolbox.cpp @@ -96,7 +96,7 @@ #include "toolbox.h" #include -#include "event-context.h" +#include "ui/tools/tool-base.h" //#define DEBUG_TEXT diff --git a/src/widgets/tweak-toolbar.cpp b/src/widgets/tweak-toolbar.cpp index c828373d7..6999b057d 100644 --- a/src/widgets/tweak-toolbar.cpp +++ b/src/widgets/tweak-toolbar.cpp @@ -52,8 +52,8 @@ #include "../xml/repr.h" #include "ui/uxmanager.h" #include "../ui/icon-names.h" -#include "../pen-context.h" -#include "../tweak-context.h" +#include "ui/tools/pen-tool.h" +#include "ui/tools/tweak-tool.h" using Inkscape::UI::UXManager; using Inkscape::DocumentUndo; diff --git a/src/widgets/zoom-toolbar.cpp b/src/widgets/zoom-toolbar.cpp index 9cdbc3d6a..a5ab6e211 100644 --- a/src/widgets/zoom-toolbar.cpp +++ b/src/widgets/zoom-toolbar.cpp @@ -51,8 +51,8 @@ #include "../xml/repr.h" #include "ui/uxmanager.h" #include "../ui/icon-names.h" -#include "../pen-context.h" -#include "../tweak-context.h" +#include "ui/tools/pen-tool.h" +#include "ui/tools/tweak-tool.h" using Inkscape::UI::UXManager; diff --git a/src/zoom-context.cpp b/src/zoom-context.cpp deleted file mode 100644 index fd04f8cd9..000000000 --- a/src/zoom-context.cpp +++ /dev/null @@ -1,250 +0,0 @@ -/* - * Handy zooming tool - * - * Authors: - * Lauris Kaplinski - * Frank Felfe - * bulia byak - * - * Copyright (C) 1999-2002 Authors - * - * Released under GNU GPL, read the file 'COPYING' for more information - */ - - -#include - -#include "macros.h" -#include "rubberband.h" -#include "display/sp-canvas-item.h" -#include "display/sp-canvas-util.h" -#include "desktop.h" -#include "pixmaps/cursor-zoom.xpm" -#include "pixmaps/cursor-zoom-out.xpm" -#include "preferences.h" -#include "selection-chemistry.h" - -#include "zoom-context.h" -#include "tool-factory.h" - -namespace Inkscape { -namespace UI { -namespace Tools { - -namespace { - ToolBase* createZoomContext() { - return new ZoomTool(); - } - - bool zoomContextRegistered = ToolFactory::instance().registerObject("/tools/zoom", createZoomContext); -} - -const std::string& ZoomTool::getPrefsPath() { - return ZoomTool::prefsPath; -} - -const std::string ZoomTool::prefsPath = "/tools/zoom"; - -ZoomTool::ZoomTool() : ToolBase() { - this->grabbed = 0; - this->cursor_shape = cursor_zoom_xpm; - this->hot_x = 6; - this->hot_y = 6; - this->escaped = false; -} - -ZoomTool::~ZoomTool() { -} - -void ZoomTool::finish() { - this->enableGrDrag(false); - - if (this->grabbed) { - sp_canvas_item_ungrab(this->grabbed, GDK_CURRENT_TIME); - this->grabbed = NULL; - } -} - -void ZoomTool::setup() { - Inkscape::Preferences *prefs = Inkscape::Preferences::get(); - - if (prefs->getBool("/tools/zoom/selcue")) { - this->enableSelectionCue(); - } - - if (prefs->getBool("/tools/zoom/gradientdrag")) { - this->enableGrDrag(); - } - - ToolBase::setup(); -} - -bool ZoomTool::root_handler(GdkEvent* event) { - Inkscape::Preferences *prefs = Inkscape::Preferences::get(); - - tolerance = prefs->getIntLimited("/options/dragtolerance/value", 0, 0, 100); - double const zoom_inc = prefs->getDoubleLimited("/options/zoomincrement/value", M_SQRT2, 1.01, 10); - - bool ret = false; - - switch (event->type) { - case GDK_BUTTON_PRESS: - { - Geom::Point const button_w(event->button.x, event->button.y); - Geom::Point const button_dt(desktop->w2d(button_w)); - - if (event->button.button == 1 && !this->space_panning) { - // save drag origin - xp = (gint) event->button.x; - yp = (gint) event->button.y; - within_tolerance = true; - - Inkscape::Rubberband::get(desktop)->start(desktop, button_dt); - - escaped = false; - - ret = true; - } else if (event->button.button == 3) { - double const zoom_rel( (event->button.state & GDK_SHIFT_MASK) - ? zoom_inc - : 1 / zoom_inc ); - - desktop->zoom_relative_keep_point(button_dt, zoom_rel); - ret = true; - } - - sp_canvas_item_grab(SP_CANVAS_ITEM(desktop->acetate), - GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK | GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK, - NULL, event->button.time); - - this->grabbed = SP_CANVAS_ITEM(desktop->acetate); - break; - } - - case GDK_MOTION_NOTIFY: - if ((event->motion.state & GDK_BUTTON1_MASK) && !this->space_panning) { - ret = true; - - if ( within_tolerance - && ( abs( (gint) event->motion.x - xp ) < tolerance ) - && ( abs( (gint) event->motion.y - yp ) < tolerance ) ) { - break; // do not drag if we're within tolerance from origin - } - // Once the user has moved farther than tolerance from the original location - // (indicating they intend to move the object, not click), then always process the - // motion notify coordinates as given (no snapping back to origin) - within_tolerance = false; - - Geom::Point const motion_w(event->motion.x, event->motion.y); - Geom::Point const motion_dt(desktop->w2d(motion_w)); - Inkscape::Rubberband::get(desktop)->move(motion_dt); - gobble_motion_events(GDK_BUTTON1_MASK); - } - break; - - case GDK_BUTTON_RELEASE: - { - Geom::Point const button_w(event->button.x, event->button.y); - Geom::Point const button_dt(desktop->w2d(button_w)); - - if ( event->button.button == 1 && !this->space_panning) { - Geom::OptRect const b = Inkscape::Rubberband::get(desktop)->getRectangle(); - - if (b && !within_tolerance) { - desktop->set_display_area(*b, 10); - } else if (!escaped) { - double const zoom_rel( (event->button.state & GDK_SHIFT_MASK) - ? 1 / zoom_inc - : zoom_inc ); - - desktop->zoom_relative_keep_point(button_dt, zoom_rel); - } - - ret = true; - } - - Inkscape::Rubberband::get(desktop)->stop(); - - if (this->grabbed) { - sp_canvas_item_ungrab(this->grabbed, event->button.time); - this->grabbed = NULL; - } - - xp = yp = 0; - escaped = false; - break; - } - case GDK_KEY_PRESS: - switch (get_group0_keyval (&event->key)) { - case GDK_KEY_Escape: - if (!Inkscape::Rubberband::get(desktop)->is_started()) { - Inkscape::SelectionHelper::selectNone(desktop); - } - - Inkscape::Rubberband::get(desktop)->stop(); - xp = yp = 0; - escaped = true; - ret = true; - break; - - case GDK_KEY_Up: - case GDK_KEY_Down: - case GDK_KEY_KP_Up: - case GDK_KEY_KP_Down: - // prevent the zoom field from activation - if (!MOD__CTRL_ONLY(event)) - ret = true; - break; - - case GDK_KEY_Shift_L: - case GDK_KEY_Shift_R: - this->cursor_shape = cursor_zoom_out_xpm; - this->sp_event_context_update_cursor(); - break; - - case GDK_KEY_Delete: - case GDK_KEY_KP_Delete: - case GDK_KEY_BackSpace: - ret = this->deleteSelectedDrag(MOD__CTRL_ONLY(event)); - break; - - default: - break; - } - break; - case GDK_KEY_RELEASE: - switch (get_group0_keyval (&event->key)) { - case GDK_KEY_Shift_L: - case GDK_KEY_Shift_R: - this->cursor_shape = cursor_zoom_xpm; - this->sp_event_context_update_cursor(); - break; - default: - break; - } - break; - default: - break; - } - - if (!ret) { - ret = ToolBase::root_handler(event); - } - - return ret; -} - -} -} -} - -/* - Local Variables: - mode:c++ - c-file-style:"stroustrup" - c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) - indent-tabs-mode:nil - fill-column:99 - End: -*/ -// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 : diff --git a/src/zoom-context.h b/src/zoom-context.h deleted file mode 100644 index 83fc69a88..000000000 --- a/src/zoom-context.h +++ /dev/null @@ -1,47 +0,0 @@ -#ifndef __SP_ZOOM_CONTEXT_H__ -#define __SP_ZOOM_CONTEXT_H__ - -/* - * Handy zooming tool - * - * Authors: - * Lauris Kaplinski - * Frank Felfe - * - * Copyright (C) 1999-2002 Authors - * - * Released under GNU GPL, read the file 'COPYING' for more information - */ - -#include "event-context.h" - -#define SP_ZOOM_CONTEXT(obj) (dynamic_cast((Inkscape::UI::Tools::ToolBase*)obj)) -#define SP_IS_ZOOM_CONTEXT(obj) (dynamic_cast((const Inkscape::UI::Tools::ToolBase*)obj) != NULL) - -namespace Inkscape { -namespace UI { -namespace Tools { - -class ZoomTool : public ToolBase { -public: - ZoomTool(); - virtual ~ZoomTool(); - - static const std::string prefsPath; - - virtual void setup(); - virtual void finish(); - virtual bool root_handler(GdkEvent* event); - - virtual const std::string& getPrefsPath(); - -private: - SPCanvasItem *grabbed; - bool escaped; -}; - -} -} -} - -#endif -- cgit v1.2.3