Code

enable copy/paste of objects with filters
[inkscape.git] / src / dyna-draw-context.cpp
index acbd6ae80af7bd128d1e21e08ac0947af5d41206..91079aaa4dd278a287fc7034a6b1a2d32a3a94c3 100644 (file)
@@ -7,6 +7,7 @@
  *   Mitsuru Oka <oka326@parkcity.ne.jp>
  *   Lauris Kaplinski <lauris@kaplinski.com>
  *   bulia byak <buliabyak@users.sf.net>
+ *   MenTaLguY <mental@rydia.net>
  *
  * The original dynadraw code:
  *   Paul Haeberli <paul@sgi.com>
@@ -14,6 +15,8 @@
  * Copyright (C) 1998 The Free Software Foundation
  * Copyright (C) 1999-2005 authors
  * Copyright (C) 2001-2002 Ximian, Inc.
+ * Copyright (C) 2005-2006 bulia byak
+ * Copyright (C) 2006 MenTaLguY
  *
  * Released under GNU GPL, read the file 'COPYING' for more information
  */
 
 #include <gtk/gtk.h>
 #include <gdk/gdkkeysyms.h>
+#include <glibmm/i18n.h>
 
 #include "svg/svg.h"
 #include "display/canvas-bpath.h"
 #include "display/bezier-utils.h"
 
+#include <glib/gmem.h>
 #include "macros.h"
 #include "document.h"
 #include "selection.h"
@@ -53,7 +58,7 @@
 #define SAMPLE_TIMEOUT 10
 #define TOLERANCE_LINE 1.0
 #define TOLERANCE_CALLIGRAPHIC 3.0
-#define DYNA_EPSILON 1.0e-6
+#define DYNA_EPSILON 0.5e-2
 
 #define DYNA_MIN_WIDTH 1.0e-6
 
@@ -71,10 +76,10 @@ static gint sp_dyna_draw_context_root_handler(SPEventContext *ec, GdkEvent *even
 
 static void clear_current(SPDynaDrawContext *dc);
 static void set_to_accumulated(SPDynaDrawContext *dc);
+static void add_cap(SPCurve *curve, NR::Point const &pre, NR::Point const &from, NR::Point const &to, NR::Point const &post, double rounding);
 static void accumulate_calligraphic(SPDynaDrawContext *dc);
 
 static void fit_and_split(SPDynaDrawContext *ddc, gboolean release);
-static void fit_and_split_calligraphics(SPDynaDrawContext *ddc, gboolean release);
 
 static void sp_dyna_draw_reset(SPDynaDrawContext *ddc, NR::Point p);
 static NR::Point sp_dyna_draw_get_npoint(SPDynaDrawContext const *ddc, NR::Point v);
@@ -155,6 +160,7 @@ sp_dyna_draw_context_init(SPDynaDrawContext *ddc)
 
     ddc->vel_thin = 0.1;
     ddc->flatness = 0.9;
+    ddc->cap_rounding = 0.0;
 
     ddc->abs_width = false;
     ddc->keep_selected = true;
@@ -211,7 +217,7 @@ sp_dyna_draw_context_setup(SPEventContext *ec)
     g_signal_connect(G_OBJECT(ddc->currentshape), "event", G_CALLBACK(sp_desktop_root_handler), ec->desktop);
 
     sp_event_context_read(ec, "mass");
-    sp_event_context_read(ec, "drag");
+    sp_event_context_read(ec, "wiggle");
     sp_event_context_read(ec, "angle");
     sp_event_context_read(ec, "width");
     sp_event_context_read(ec, "thinning");
@@ -221,6 +227,7 @@ sp_dyna_draw_context_setup(SPEventContext *ec)
     sp_event_context_read(ec, "usetilt");
     sp_event_context_read(ec, "abs_width");
     sp_event_context_read(ec, "keep_selected");
+    sp_event_context_read(ec, "cap_rounding");
 
     ddc->is_drawing = false;
 
@@ -235,9 +242,9 @@ sp_dyna_draw_context_set(SPEventContext *ec, gchar const *key, gchar const *val)
     if (!strcmp(key, "mass")) {
         double const dval = ( val ? g_ascii_strtod (val, NULL) : 0.2 );
         ddc->mass = CLAMP(dval, -1000.0, 1000.0);
-    } else if (!strcmp(key, "drag")) {
-        double const dval = ( val ? g_ascii_strtod (val, NULL) : DRAG_DEFAULT );
-        ddc->drag = CLAMP(dval, DRAG_MIN, DRAG_MAX);
+    } else if (!strcmp(key, "wiggle")) {
+        double const dval = ( val ? g_ascii_strtod (val, NULL) : (1 - DRAG_DEFAULT));
+        ddc->drag = CLAMP((1 - dval), DRAG_MIN, DRAG_MAX); // drag is inverse to wiggle
     } else if (!strcmp(key, "angle")) {
         double const dval = ( val ? g_ascii_strtod (val, NULL) : 0.0);
         ddc->angle = CLAMP (dval, -90, 90);
@@ -261,6 +268,8 @@ sp_dyna_draw_context_set(SPEventContext *ec, gchar const *key, gchar const *val)
         ddc->abs_width = (val && strcmp(val, "0"));
     } else if (!strcmp(key, "keep_selected")) {
         ddc->keep_selected = (val && strcmp(val, "0"));
+    } else if (!strcmp(key, "cap_rounding")) {
+        ddc->cap_rounding = ( val ? g_ascii_strtod (val, NULL) : 0.0 );
     }
 
     //g_print("DDC: %g %g %g %g\n", ddc->mass, ddc->drag, ddc->angle, ddc->width);
@@ -533,9 +542,14 @@ sp_dyna_draw_context_root_handler(SPEventContext *event_context,
     case GDK_BUTTON_RELEASE:
         sp_canvas_item_ungrab(SP_CANVAS_ITEM(desktop->acetate), event->button.time);
         dc->is_drawing = false;
+
         if ( dc->dragging && event->button.button == 1 ) {
             dc->dragging = FALSE;
 
+            NR::Point const motion_w(event->button.x, event->button.y);
+            NR::Point const motion_dt(desktop->w2d(motion_w));
+            sp_dyna_draw_apply(dc, motion_dt);
+
             /* Remove all temporary line segments */
             while (dc->segments) {
                 gtk_object_destroy(GTK_OBJECT(dc->segments->data));
@@ -668,7 +682,7 @@ set_to_accumulated(SPDynaDrawContext *dc)
         abp = nr_artpath_affine(sp_curve_first_bpath(dc->accumulated), sp_desktop_dt2root_affine(desktop));
         str = sp_svg_write_path(abp);
         g_assert( str != NULL );
-        nr_free(abp);
+        g_free(abp);
         dc->repr->setAttribute("d", str);
         g_free(str);
     } else {
@@ -678,7 +692,38 @@ set_to_accumulated(SPDynaDrawContext *dc)
         dc->repr = NULL;
     }
 
-    sp_document_done(sp_desktop_document(desktop));
+    sp_document_done(sp_desktop_document(desktop), SP_VERB_CONTEXT_CALLIGRAPHIC, 
+                     _("Create calligraphic stroke"));
+}
+
+static void
+add_cap(SPCurve *curve,
+        NR::Point const &pre, NR::Point const &from,
+        NR::Point const &to, NR::Point const &post,
+        double rounding)
+{
+    NR::Point vel = rounding * NR::rot90( to - from ) / sqrt(2.0);
+    double mag = NR::L2(vel);
+
+    NR::Point v_in = from - pre;
+    double mag_in = NR::L2(v_in);
+    if ( mag_in > DYNA_EPSILON ) {
+        v_in = mag * v_in / mag_in;
+    } else {
+        v_in = NR::Point(0, 0);
+    }
+
+    NR::Point v_out = to - post;
+    double mag_out = NR::L2(v_out);
+    if ( mag_out > DYNA_EPSILON ) {
+        v_out = mag * v_out / mag_out;
+    } else {
+        v_out = NR::Point(0, 0);
+    }
+
+    if ( NR::L2(v_in) > DYNA_EPSILON || NR::L2(v_out) > DYNA_EPSILON ) {
+        sp_curve_curveto(curve, from + v_in, to + v_out, to);
+    }
 }
 
 static void
@@ -687,8 +732,24 @@ accumulate_calligraphic(SPDynaDrawContext *dc)
     if ( !sp_curve_empty(dc->cal1) && !sp_curve_empty(dc->cal2) ) {
         sp_curve_reset(dc->accumulated); /*  Is this required ?? */
         SPCurve *rev_cal2 = sp_curve_reverse(dc->cal2);
+
+        g_assert(dc->cal1->end > 1);
+        g_assert(rev_cal2->end > 1);
+        g_assert(SP_CURVE_SEGMENT(dc->cal1, 0)->code == NR_MOVETO_OPEN);
+        g_assert(SP_CURVE_SEGMENT(rev_cal2, 0)->code == NR_MOVETO_OPEN);
+        g_assert(SP_CURVE_SEGMENT(dc->cal1, 1)->code == NR_CURVETO);
+        g_assert(SP_CURVE_SEGMENT(rev_cal2, 1)->code == NR_CURVETO);
+        g_assert(SP_CURVE_SEGMENT(dc->cal1, dc->cal1->end-1)->code == NR_CURVETO);
+        g_assert(SP_CURVE_SEGMENT(rev_cal2, rev_cal2->end-1)->code == NR_CURVETO);
+
         sp_curve_append(dc->accumulated, dc->cal1, FALSE);
+
+        add_cap(dc->accumulated, SP_CURVE_SEGMENT(dc->cal1, dc->cal1->end-1)->c(2), SP_CURVE_SEGMENT(dc->cal1, dc->cal1->end-1)->c(3), SP_CURVE_SEGMENT(rev_cal2, 0)->c(3), SP_CURVE_SEGMENT(rev_cal2, 1)->c(1), dc->cap_rounding);
+
         sp_curve_append(dc->accumulated, rev_cal2, TRUE);
+
+        add_cap(dc->accumulated, SP_CURVE_SEGMENT(rev_cal2, rev_cal2->end-1)->c(2), SP_CURVE_SEGMENT(rev_cal2, rev_cal2->end-1)->c(3), SP_CURVE_SEGMENT(dc->cal1, 0)->c(3), SP_CURVE_SEGMENT(dc->cal1, 1)->c(1), dc->cap_rounding);
+
         sp_curve_closepath(dc->accumulated);
 
         sp_curve_unref(rev_cal2);
@@ -698,20 +759,13 @@ accumulate_calligraphic(SPDynaDrawContext *dc)
     }
 }
 
-static void
-fit_and_split(SPDynaDrawContext *dc,
-              gboolean release)
-{
-    fit_and_split_calligraphics(dc, release);
-}
-
 static double square(double const x)
 {
     return x * x;
 }
 
 static void
-fit_and_split_calligraphics(SPDynaDrawContext *dc, gboolean release)
+fit_and_split(SPDynaDrawContext *dc, gboolean release)
 {
     double const tolerance_sq = square( NR::expansion(SP_EVENT_CONTEXT(dc)->desktop->w2d()) * TOLERANCE_CALLIGRAPHIC );
 
@@ -771,6 +825,10 @@ fit_and_split_calligraphics(SPDynaDrawContext *dc, gboolean release)
                 for (NR::Point *bp2 = b2 + BEZIER_SIZE * ( nb2 - 1 ); bp2 >= b2; bp2 -= BEZIER_SIZE) {
                     sp_curve_curveto(dc->currentcurve, bp2[2], bp2[1], bp2[0]);
                 }
+                // FIXME: dc->segments is always NULL at this point??
+                if (!dc->segments) { // first segment
+                    add_cap(dc->currentcurve, b2[1], b2[0], b1[0], b1[1], dc->cap_rounding);
+                }
                 sp_curve_closepath(dc->currentcurve);
                 sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(dc->currentshape), dc->currentcurve);
             }
@@ -785,7 +843,7 @@ fit_and_split_calligraphics(SPDynaDrawContext *dc, gboolean release)
         } else {
             /* fixme: ??? */
 #ifdef DYNA_DRAW_VERBOSE
-            g_print("[fit_and_split_calligraphics] failed to fit-cubic.\n");
+            g_print("[fit_and_split] failed to fit-cubic.\n");
 #endif
             draw_temporary_box(dc);
 
@@ -845,6 +903,7 @@ draw_temporary_box(SPDynaDrawContext *dc)
     for (gint i = dc->npoints-1; i >= 0; i--) {
         sp_curve_lineto(dc->currentcurve, dc->point2[i]);
     }
+    add_cap(dc->currentcurve, dc->point2[1], dc->point2[0], dc->point1[0], dc->point1[1], dc->cap_rounding);
     sp_curve_closepath(dc->currentcurve);
     sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(dc->currentshape), dc->currentcurve);
 }