Code

Node tool: special case node duplication for endnodes - select new endnode
[inkscape.git] / src / dyna-draw-context.cpp
index 402af6d49797d25fac1643d09b0bc376d3066c13..93d4262cd172512dbfe53d9fe6befc064f398009 100644 (file)
@@ -1,5 +1,3 @@
-#define __SP_DYNA_DRAW_CONTEXT_C__
-
 /*
  * Handwriting-like drawing mode
  *
@@ -8,6 +6,7 @@
  *   Lauris Kaplinski <lauris@kaplinski.com>
  *   bulia byak <buliabyak@users.sf.net>
  *   MenTaLguY <mental@rydia.net>
+ *   Abhishek Sharma
  *
  * The original dynadraw code:
  *   Paul Haeberli <paul@sgi.com>
@@ -34,6 +33,8 @@
 
 #include "svg/svg.h"
 #include "display/canvas-bpath.h"
+#include <2geom/isnan.h>
+#include <2geom/pathvector.h>
 #include <2geom/bezier-utils.h>
 #include "display/curve.h"
 #include <glib/gmem.h>
 #include "desktop.h"
 #include "desktop-events.h"
 #include "desktop-handles.h"
-#include "desktop-affine.h"
 #include "desktop-style.h"
 #include "message-context.h"
 #include "preferences.h"
 #include "pixmaps/cursor-calligraphy.xpm"
-#include "libnr/nr-matrix-ops.h"
-#include "libnr/nr-scale-translate-ops.h"
-#include "libnr/nr-convert2geom.h"
 #include "xml/repr.h"
 #include "context-fns.h"
 #include "sp-item.h"
 #include "display/canvas-bpath.h"
 #include "display/canvas-arena.h"
 #include "livarot/Shape.h"
-#include <2geom/isnan.h>
-#include <2geom/pathvector.h>
 
 #include "dyna-draw-context.h"
 
+using Inkscape::DocumentUndo;
+
 #define DDC_RED_RGBA 0xff0000ff
 
 #define TOLERANCE_CALLIGRAPHIC 0.1
@@ -88,7 +85,7 @@ static void sp_dyna_draw_context_set(SPEventContext *ec, Inkscape::Preferences::
 static gint sp_dyna_draw_context_root_handler(SPEventContext *ec, GdkEvent *event);
 
 static void clear_current(SPDynaDrawContext *dc);
-static void set_to_accumulated(SPDynaDrawContext *dc, bool unionize);
+static void set_to_accumulated(SPDynaDrawContext *dc, bool unionize, bool subtract);
 static void add_cap(SPCurve *curve, Geom::Point const &from, Geom::Point const &to, double rounding);
 static bool accumulate_calligraphic(SPDynaDrawContext *dc);
 
@@ -156,15 +153,17 @@ sp_dyna_draw_context_init(SPDynaDrawContext *ddc)
     ddc->hatch_spacing_step = 0;
     new (&ddc->hatch_pointer_past) std::list<double>();
     new (&ddc->hatch_nearest_past) std::list<double>();
+    new (&ddc->inertia_vectors) std::list<Geom::Point>();
+    new (&ddc->hatch_vectors) std::list<Geom::Point>();
     ddc->hatch_last_nearest = Geom::Point(0,0);
     ddc->hatch_last_pointer = Geom::Point(0,0);
-    ddc->hatch_vector_accumulated = Geom::Point(0,0);
     ddc->hatch_escaped = false;
     ddc->hatch_area = NULL;
     ddc->hatch_item = NULL;
     ddc->hatch_livarot_path = NULL;
 
     ddc->trace_bg = false;
+    ddc->just_started_drawing = false;
 }
 
 static void
@@ -182,6 +181,8 @@ sp_dyna_draw_context_dispose(GObject *object)
 
     ddc->hatch_pointer_past.~list();
     ddc->hatch_nearest_past.~list();
+    ddc->inertia_vectors.~list();
+    ddc->hatch_vectors.~list();
 }
 
 static void
@@ -334,7 +335,7 @@ sp_dyna_draw_apply(SPDynaDrawContext *dc, Geom::Point p)
     // 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. 
+    // 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.
@@ -406,7 +407,7 @@ sp_dyna_draw_apply(SPDynaDrawContext *dc, Geom::Point p)
     // convert to point
     dc->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", NR::L2(force), NR::L2(dc->acc), dc->vel_max, NR::L2(dc->vel), a1, a2, 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 */
     dc->vel *= 1.0 - drag;
@@ -432,7 +433,7 @@ sp_dyna_draw_brush(SPDynaDrawContext *dc)
     // get the real brush point, not the same as pointer (affected by hatch tracking and/or mass
     // drag)
     Geom::Point brush = sp_dyna_draw_get_vpoint(dc, dc->cur);
-    Geom::Point brush_w = SP_EVENT_CONTEXT(dc)->desktop->d2w(brush); 
+    Geom::Point brush_w = SP_EVENT_CONTEXT(dc)->desktop->d2w(brush);
 
     double trace_thick = 1;
     if (dc->trace_bg) {
@@ -544,12 +545,6 @@ sp_dyna_draw_context_root_handler(SPEventContext *event_context,
                     return TRUE;
                 }
 
-                Geom::Point const button_w(event->button.x,
-                                         event->button.y);
-                Geom::Point const button_dt(desktop->w2d(button_w));
-                sp_dyna_draw_reset(dc, button_dt);
-                sp_dyna_draw_extinput(dc, event);
-                sp_dyna_draw_apply(dc, button_dt);
                 dc->accumulated->reset();
                 if (dc->repr) {
                     dc->repr = NULL;
@@ -570,6 +565,7 @@ sp_dyna_draw_context_root_handler(SPEventContext *event_context,
 
                 sp_canvas_force_full_redraw_after_interruptions(desktop->canvas, 3);
                 dc->is_drawing = true;
+                dc->just_started_drawing = true;
             }
             break;
         case GDK_MOTION_NOTIFY:
@@ -604,7 +600,7 @@ sp_dyna_draw_context_root_handler(SPEventContext *event_context,
                     }
 
                     // calculate pointer point in the guide item's coords
-                    motion_to_curve = sp_item_dt2i_affine(selected) * sp_item_i2doc_affine(selected);
+                    motion_to_curve = selected->dt2i_affine() * selected->i2doc_affine();
                     pointer = motion_dt * motion_to_curve;
 
                     // calculate the nearest point on the guide path
@@ -613,7 +609,7 @@ sp_dyna_draw_context_root_handler(SPEventContext *event_context,
 
 
                     // distance from pointer to nearest
-                    hatch_dist = NR::L2(pointer - nearest);
+                    hatch_dist = Geom::L2(pointer - nearest);
                     // unit-length vector
                     hatch_unit_vector = (pointer - nearest)/hatch_dist;
 
@@ -621,16 +617,19 @@ sp_dyna_draw_context_root_handler(SPEventContext *event_context,
                 } else {
                     dc->_message_context->set(Inkscape::NORMAL_MESSAGE, _("<b>Select a guide path</b> to track with <b>Ctrl</b>"));
                 }
-            } 
+            }
 
             if ( dc->is_drawing && (event->motion.state & GDK_BUTTON1_MASK) && !event_context->space_panning) {
                 dc->dragging = TRUE;
 
                 if (event->motion.state & GDK_CONTROL_MASK && dc->hatch_item) { // hatching
 
+#define HATCH_VECTOR_ELEMENTS 12
+#define INERTIA_ELEMENTS 24
 #define SPEED_ELEMENTS 12
-#define SPEED_MIN 0.12
-#define SPEED_NORMAL 0.65
+#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
@@ -675,19 +674,37 @@ sp_dyna_draw_context_root_handler(SPEventContext *event_context,
                         ) {
                         // 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[NR::X], dc->last_nearest[NR::Y], nearest[NR::X], nearest[NR::Y], pointer[NR::X], pointer[NR::Y], position->piece, position->t);
+                        //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
                         dc->hatch_escaped = true;
 
+                        if (dc->inertia_vectors.size() >= INERTIA_ELEMENTS/2) { // move by inertia
+                            Geom::Point moved_past_escape = motion_dt - dc->inertia_vectors.front();
+                            Geom::Point inertia = 
+                                dc->inertia_vectors.front() - dc->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 = dc->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
-                        double dot = NR::dot (pointer - nearest, dc->hatch_vector_accumulated);
-                        dot /= Geom::L2(pointer - nearest) * Geom::L2(dc->hatch_vector_accumulated);
+                        Geom::Point hatch_vector_accumulated = std::accumulate 
+                            (dc->hatch_vectors.begin(), dc->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 (dc->hatch_spacing != 0) { // spacing was already set
                             double target;
@@ -697,7 +714,7 @@ sp_dyna_draw_context_root_handler(SPEventContext *event_context,
                             } else {
                                 // looks like we're starting to lose speed,
                                 // so _gradually_ let go attraction to prevent jerks
-                                target = (dc->hatch_spacing * speed + hatch_dist * (SPEED_NORMAL - speed))/SPEED_NORMAL;                            
+                                target = (dc->hatch_spacing * speed + hatch_dist * (SPEED_NORMAL - speed))/SPEED_NORMAL;
                             }
                             if (!IS_NAN(dot) && dot < -0.5) {// flip
                                 target = -target;
@@ -713,15 +730,24 @@ sp_dyna_draw_context_root_handler(SPEventContext *event_context,
                             // return it to the desktop coords
                             motion_dt = new_pointer * motion_to_curve.inverse();
 
+                            if (speed >= SPEED_NORMAL) {
+                                dc->inertia_vectors.push_front(motion_dt);
+                                if (dc->inertia_vectors.size() > INERTIA_ELEMENTS)
+                                    dc->inertia_vectors.pop_back();
+                            }
+
                         } else {
-                            // this is the first motion event, set the dist 
+                            // this is the first motion event, set the dist
                             dc->hatch_spacing = hatch_dist;
                         }
 
                         // remember last points
                         dc->hatch_last_pointer = pointer;
                         dc->hatch_last_nearest = nearest;
-                        dc->hatch_vector_accumulated += (pointer - nearest);
+
+                        dc->hatch_vectors.push_front(pointer - nearest);
+                        if (dc->hatch_vectors.size() > HATCH_VECTOR_ELEMENTS)
+                            dc->hatch_vectors.pop_back();
                     }
 
                     dc->_message_context->set(Inkscape::NORMAL_MESSAGE, dc->hatch_escaped? _("Tracking: <b>connection to guide path lost!</b>") : _("<b>Tracking</b> a guide path"));
@@ -730,6 +756,11 @@ sp_dyna_draw_context_root_handler(SPEventContext *event_context,
                     dc->_message_context->set(Inkscape::NORMAL_MESSAGE, _("<b>Drawing</b> a calligraphic stroke"));
                 }
 
+                if (dc->just_started_drawing) {
+                    dc->just_started_drawing = false;
+                    sp_dyna_draw_reset(dc, motion_dt);
+                }
+
                 if (!sp_dyna_draw_apply(dc, motion_dt)) {
                     ret = TRUE;
                     break;
@@ -744,25 +775,25 @@ sp_dyna_draw_context_root_handler(SPEventContext *event_context,
             }
 
             // Draw the hatching circle if necessary
-            if (event->motion.state & GDK_CONTROL_MASK) { 
-                if (dc->hatch_spacing == 0 && hatch_dist != 0) { 
+            if (event->motion.state & GDK_CONTROL_MASK) {
+                if (dc->hatch_spacing == 0 && hatch_dist != 0) {
                     // Haven't set spacing yet: gray, center free, update radius live
                     Geom::Point c = desktop->w2d(motion_w);
-                    NR::Matrix const sm (Geom::Scale(hatch_dist, hatch_dist) * Geom::Translate(c));
+                    Geom::Matrix const sm (Geom::Scale(hatch_dist, hatch_dist) * Geom::Translate(c));
                     sp_canvas_item_affine_absolute(dc->hatch_area, sm);
                     sp_canvas_bpath_set_stroke(SP_CANVAS_BPATH(dc->hatch_area), 0x7f7f7fff, 1.0, SP_STROKE_LINEJOIN_MITER, SP_STROKE_LINECAP_BUTT);
                     sp_canvas_item_show(dc->hatch_area);
                 } else if (dc->dragging && !dc->hatch_escaped) {
                     // Tracking: green, center snapped, fixed radius
                     Geom::Point c = motion_dt;
-                    NR::Matrix const sm (Geom::Scale(dc->hatch_spacing, dc->hatch_spacing) * Geom::Translate(c));
+                    Geom::Matrix const sm (Geom::Scale(dc->hatch_spacing, dc->hatch_spacing) * Geom::Translate(c));
                     sp_canvas_item_affine_absolute(dc->hatch_area, sm);
                     sp_canvas_bpath_set_stroke(SP_CANVAS_BPATH(dc->hatch_area), 0x00FF00ff, 1.0, SP_STROKE_LINEJOIN_MITER, SP_STROKE_LINECAP_BUTT);
                     sp_canvas_item_show(dc->hatch_area);
                 } else if (dc->dragging && dc->hatch_escaped) {
                     // Tracking escaped: red, center free, fixed radius
-                    Geom::Point c = desktop->w2d(motion_w);
-                    NR::Matrix const sm (Geom::Scale(dc->hatch_spacing, dc->hatch_spacing) * Geom::Translate(c));
+                    Geom::Point c = motion_dt;
+                    Geom::Matrix const sm (Geom::Scale(dc->hatch_spacing, dc->hatch_spacing) * Geom::Translate(c));
 
                     sp_canvas_item_affine_absolute(dc->hatch_area, sm);
                     sp_canvas_bpath_set_stroke(SP_CANVAS_BPATH(dc->hatch_area), 0xFF0000ff, 1.0, SP_STROKE_LINEJOIN_MITER, SP_STROKE_LINECAP_BUTT);
@@ -771,7 +802,7 @@ sp_dyna_draw_context_root_handler(SPEventContext *event_context,
                     // Not drawing but spacing set: gray, center snapped, fixed radius
                     Geom::Point c = (nearest + dc->hatch_spacing * hatch_unit_vector) * motion_to_curve.inverse();
                     if (!IS_NAN(c[Geom::X]) && !IS_NAN(c[Geom::Y])) {
-                        NR::Matrix const sm (Geom::Scale(dc->hatch_spacing, dc->hatch_spacing) * Geom::Translate(c));
+                        Geom::Matrix const sm (Geom::Scale(dc->hatch_spacing, dc->hatch_spacing) * Geom::Translate(c));
                         sp_canvas_item_affine_absolute(dc->hatch_area, sm);
                         sp_canvas_bpath_set_stroke(SP_CANVAS_BPATH(dc->hatch_area), 0x7f7f7fff, 1.0, SP_STROKE_LINEJOIN_MITER, SP_STROKE_LINECAP_BUTT);
                         sp_canvas_item_show(dc->hatch_area);
@@ -807,7 +838,7 @@ sp_dyna_draw_context_root_handler(SPEventContext *event_context,
             /* Create object */
             fit_and_split(dc, TRUE);
             if (accumulate_calligraphic(dc))
-                set_to_accumulated(dc, event->button.state & GDK_SHIFT_MASK); // performs document_done
+                set_to_accumulated(dc, 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");
 
@@ -821,14 +852,16 @@ sp_dyna_draw_context_root_handler(SPEventContext *event_context,
 
             if (!dc->hatch_pointer_past.empty()) dc->hatch_pointer_past.clear();
             if (!dc->hatch_nearest_past.empty()) dc->hatch_nearest_past.clear();
+            if (!dc->inertia_vectors.empty()) dc->inertia_vectors.clear();
+            if (!dc->hatch_vectors.empty()) dc->hatch_vectors.clear();
             dc->hatch_last_nearest = Geom::Point(0,0);
             dc->hatch_last_pointer = Geom::Point(0,0);
-            dc->hatch_vector_accumulated = Geom::Point(0,0);
             dc->hatch_escaped = false;
             dc->hatch_item = NULL;
             dc->hatch_livarot_path = NULL;
+            dc->just_started_drawing = false;
 
-            if (dc->hatch_spacing != 0 && !dc->keep_selected) { 
+            if (dc->hatch_spacing != 0 && !dc->keep_selected) {
                 // we do not select the newly drawn path, so increase spacing by step
                 if (dc->hatch_spacing_step == 0) {
                     dc->hatch_spacing_step = dc->hatch_spacing;
@@ -963,14 +996,14 @@ clear_current(SPDynaDrawContext *dc)
 }
 
 static void
-set_to_accumulated(SPDynaDrawContext *dc, bool unionize)
+set_to_accumulated(SPDynaDrawContext *dc, bool unionize, bool subtract)
 {
     SPDesktop *desktop = SP_EVENT_CONTEXT(dc)->desktop;
 
     if (!dc->accumulated->is_empty()) {
         if (!dc->repr) {
             /* Create object */
-            Inkscape::XML::Document *xml_doc = sp_document_repr_doc(desktop->doc());
+            Inkscape::XML::Document *xml_doc = desktop->doc()->getReprDoc();
             Inkscape::XML::Node *repr = xml_doc->createElement("svg:path");
 
             /* Set style */
@@ -980,10 +1013,10 @@ set_to_accumulated(SPDynaDrawContext *dc, bool unionize)
 
             SPItem *item=SP_ITEM(desktop->currentLayer()->appendChildRepr(dc->repr));
             Inkscape::GC::release(dc->repr);
-            item->transform = sp_item_i2doc_affine(SP_ITEM(desktop->currentLayer())).inverse();
+            item->transform = SP_ITEM(desktop->currentLayer())->i2doc_affine().inverse();
             item->updateRepr();
         }
-        Geom::PathVector pathv = dc->accumulated->get_pathvector() * sp_desktop_dt2doc_affine(desktop);
+        Geom::PathVector pathv = dc->accumulated->get_pathvector() * desktop->dt2doc();
         gchar *str = sp_svg_write_path(pathv);
         g_assert( str != NULL );
         dc->repr->setAttribute("d", str);
@@ -992,10 +1025,13 @@ set_to_accumulated(SPDynaDrawContext *dc, bool unionize)
         if (unionize) {
             sp_desktop_selection(desktop)->add(dc->repr);
             sp_selected_path_union_skip_undo(desktop);
+        } else if (subtract) {
+            sp_desktop_selection(desktop)->add(dc->repr);
+            sp_selected_path_diff_skip_undo(desktop);
         } else {
             if (dc->keep_selected) {
                 sp_desktop_selection(desktop)->set(dc->repr);
-            } 
+            }
         }
 
     } else {
@@ -1005,21 +1041,21 @@ set_to_accumulated(SPDynaDrawContext *dc, bool unionize)
         dc->repr = NULL;
     }
 
-    sp_document_done(sp_desktop_document(desktop), SP_VERB_CONTEXT_CALLIGRAPHIC, 
-                     _("Draw calligraphic stroke"));
+    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, 
+        Geom::Point const &to,
         double rounding)
 {
     if (Geom::L2( to - from ) > DYNA_EPSILON) {
-        Geom::Point vel = rounding * NR::rot90( to - from ) / sqrt(2.0);
+        Geom::Point vel = rounding * Geom::rot90( to - from ) / sqrt(2.0);
         double mag = Geom::L2(vel);
 
-        Geom::Point v = mag * NR::rot90( to - from ) / Geom::L2( to - from );
+        Geom::Point v = mag * Geom::rot90( to - from ) / Geom::L2( to - from );
         curve->curveto(from + v, to + v, to);
     }
 }
@@ -1031,7 +1067,7 @@ accumulate_calligraphic(SPDynaDrawContext *dc)
             dc->cal1->is_empty() ||
             dc->cal2->is_empty() ||
             (dc->cal1->get_segment_count() <= 0) ||
-            dc->cal1->first_path()->closed() 
+            dc->cal1->first_path()->closed()
             ) {
             dc->cal1->reset();
             dc->cal2->reset();
@@ -1041,7 +1077,7 @@ accumulate_calligraphic(SPDynaDrawContext *dc)
         SPCurve *rev_cal2 = dc->cal2->create_reverse();
         if (
             (rev_cal2->get_segment_count() <= 0) ||
-            rev_cal2->first_path()->closed() 
+            rev_cal2->first_path()->closed()
             ) {
             rev_cal2->unref();
             dc->cal1->reset();
@@ -1058,7 +1094,7 @@ accumulate_calligraphic(SPDynaDrawContext *dc)
             !dc_cal1_firstseg ||
             !rev_cal2_firstseg ||
             !dc_cal1_lastseg ||
-            !rev_cal2_lastseg 
+            !rev_cal2_lastseg
             ) {
             rev_cal2->unref();
             dc->cal1->reset();
@@ -1249,4 +1285,4 @@ draw_temporary_box(SPDynaDrawContext *dc)
   fill-column:99
   End:
 */
-// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :